1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Http\Controllers\Payments;
7
8use App\Exceptions\InvalidSignatureException;
9use App\Exceptions\Store\OrderException;
10use App\Libraries\OrderCheckout;
11use App\Libraries\Payments\XsollaPaymentProcessor;
12use App\Libraries\Payments\XsollaSignature;
13use App\Libraries\Payments\XsollaUserNotFoundException;
14use App\Models\Store\Order;
15use Auth;
16use Exception;
17use Illuminate\Http\Request as HttpRequest;
18use Request;
19use Xsolla\SDK\API\PaymentUI\TokenRequest;
20use Xsolla\SDK\API\XsollaClient;
21
22class XsollaController extends Controller
23{
24 public function __construct()
25 {
26 $this->middleware('auth', ['except' => ['callback']]);
27 $this->middleware('check-user-restricted', ['except' => ['callback']]);
28 $this->middleware('verify-user', ['except' => ['callback']]);
29
30 parent::__construct();
31 }
32
33 public function token()
34 {
35 $projectId = $GLOBALS['cfg']['payments']['xsolla']['project_id'];
36 $user = Auth::user();
37 $order = Order::whereOrderNumber(request('orderNumber'))
38 ->whereCanCheckout()
39 ->first();
40
41 if ($order === null) {
42 return;
43 }
44
45 $tokenRequest = new TokenRequest($projectId, (string) $user->user_id);
46 $tokenRequest
47 ->setSandboxMode($GLOBALS['cfg']['payments']['sandbox'])
48 ->setExternalPaymentId($order->getOrderNumber())
49 ->setUserEmail($user->user_email)
50 ->setUserName($user->username)
51 ->setPurchase($order->getTotal(), 'USD')
52 ->setCustomParameters([
53 'subtotal' => $order->getSubtotal(),
54 'shipping' => $order->shipping,
55 'order_id' => $order['order_id'],
56 ]);
57
58 $xsollaClient = XsollaClient::factory([
59 'merchant_id' => $GLOBALS['cfg']['payments']['xsolla']['merchant_id'],
60 'api_key' => $GLOBALS['cfg']['payments']['xsolla']['api_key'],
61 ]);
62
63 // This will be used for XPayStationWidget options.
64 return [
65 'access_token' => $xsollaClient->createPaymentUITokenFromRequest($tokenRequest),
66 'sandbox' => $GLOBALS['cfg']['payments']['sandbox'],
67 ];
68 }
69
70 // Called by xsolla after payment is approved by user.
71 public function callback(HttpRequest $request)
72 {
73 $params = static::extractParams($request);
74 $signature = new XsollaSignature($request);
75 $processor = new XsollaPaymentProcessor($params, $signature);
76
77 try {
78 if ($processor->isSkipped()) {
79 return ['ok'];
80 }
81
82 $processor->run();
83 } catch (OrderException $exception) {
84 log_error($exception);
85
86 return $this->errorResponse(
87 'A validation error occured while running the transaction',
88 'INVALID',
89 422
90 );
91 } catch (InvalidSignatureException $exception) {
92 log_error($exception);
93 // xsolla expects INVALID_SIGNATURE
94 return $this->errorResponse('The signature is invalid.', 'INVALID_SIGNATURE', 422);
95 } catch (XsollaUserNotFoundException $exception) {
96 return $this->errorResponse('INVALID_USER', 'INVALID_USER', 404);
97 } catch (Exception $exception) {
98 log_error($exception);
99
100 // status code needs to be a 4xx code to make Xsolla an error to the user.
101 return $this->errorResponse('Something went wrong.', 'FATAL_ERROR', 422);
102 }
103 }
104
105 // After user has approved payment and redirected here by xsolla
106 public function completed()
107 {
108 $orderNumber = Request::input('foreignInvoice') ?? '';
109 $order = OrderCheckout::for($orderNumber)->completeCheckout();
110
111 return redirect(route('store.invoice.show', ['invoice' => $order->order_id, 'thanks' => 1]));
112 }
113
114 private function errorResponse(string $message, string $code, int $status)
115 {
116 return response()->json([
117 'error' => [
118 'code' => $code,
119 'message' => $message,
120 ],
121 ], $status);
122 }
123}