. Licensed under the GNU Affero General Public License v3.0. // See the LICENCE file in the repository root for full licence text. namespace App\Http\Controllers\Payments; use App\Exceptions\InvalidSignatureException; use App\Exceptions\Store\OrderException; use App\Exceptions\Store\PaymentRejectedException; use App\Libraries\OrderCheckout; use App\Libraries\Payments\NotificationType; use App\Libraries\Payments\PaypalCreatePayment; use App\Libraries\Payments\PaypalExecutePayment; use App\Libraries\Payments\PaypalPaymentProcessor; use App\Libraries\Payments\PaypalSignature; use App\Models\Store\Order; use App\Traits\CheckoutErrorSettable; use Illuminate\Database\QueryException; use Illuminate\Http\Request as HttpRequest; use Lang; use PayPalHttp\HttpException; class PaypalController extends Controller { use CheckoutErrorSettable; public function __construct() { $this->middleware('auth', ['except' => ['ipn']]); $this->middleware('check-user-restricted', ['except' => ['ipn']]); $this->middleware('verify-user', ['except' => ['ipn']]); parent::__construct(); } // When user has approved a payment at Paypal and is redirected back here. public function approved() { // new uses token $params = get_params(request()->all(), null, [ 'order_id:int', 'paymentId:string', 'token:string', ], ['null_missing' => true]); $order = auth()->user() ->orders() ->paymentRequested() ->findOrFail($params['order_id']); if (present($params['paymentId'])) { return $this->setAndRedirectCheckoutError($order, osu_trans('paypal/errors.old_format')); } $token = $params['token']; if (!present($token) || $token !== $order->reference) { return $this->setAndRedirectCheckoutError($order, osu_trans('paypal/errors.invalid_token')); } try { (new PaypalExecutePayment($order))->run(); } catch (HttpException $e) { return $this->setAndRedirectCheckoutError($order, $this->userErrorMessage($e)); } catch (PaymentRejectedException) { return $this->setAndRedirectCheckoutError($order, osu_trans('paypal/errors.unknown')); } return redirect(route('store.invoice.show', ['invoice' => $order->order_id, 'thanks' => 1])); } // Begin process of approving a payment. public function create() { $orderId = get_int(request('order_id')); $order = auth()->user()->orders()->paymentRequested()->findOrFail($orderId); return (new PaypalCreatePayment($order))->run(); } // Payment declined by user. public function declined() { $orderId = get_int(request('order_id')); $order = auth()->user()->orders()->paymentRequested()->find($orderId); if ($order === null) { return ujs_redirect(route('store.cart.show')); } (new OrderCheckout($order, Order::PROVIDER_PAYPAL))->failCheckout(); return $this->setAndRedirectCheckoutError($order, osu_trans('store.checkout.declined')); } // Called by Paypal. public function ipn(HttpRequest $request) { $params = static::extractParams($request); $signature = new PaypalSignature($request); $processor = new PaypalPaymentProcessor($params, $signature); try { $processor->run(); } catch (OrderException $exception) { log_error($exception); return response(['message' => 'A validation error occured while running the transaction'], 406); } catch (InvalidSignatureException $exception) { log_error($exception); return response(['message' => $exception->getMessage()], 406); } catch (QueryException $exception) { // can get multiple cancellations for the same order from paypal. if ( is_sql_unique_exception($exception) && $processor->getNotificationType() === NotificationType::REFUND ) { return 'ok'; } throw $exception; } return 'ok'; } private function userErrorMessage(HttpException $e) { $json = json_decode($e->getMessage()); $key = 'paypal/errors.'.strtolower($json->name ?? 'unknown'); if (!Lang::has($key)) { $key = 'paypal/errors.unknown'; } return osu_trans($key); } }