-6
.env.example
-6
.env.example
···
156
156
XSOLLA_PROJECT_ID=
157
157
XSOLLA_SECRET_KEY=
158
158
159
-
CENTILI_API_KEY=
160
-
CENTILI_CONVERSION_RATE=
161
-
CENTILI_ENABLED=false
162
-
CENTILI_SECRET_KEY=
163
-
CENTILI_WIDGET_URL=https://api.centili.com/payment/widget
164
-
165
159
OSU_RUNNING_COST=
166
160
167
161
CLIENT_CHECK_VERSION=false
-61
app/Http/Controllers/Payments/CentiliController.php
-61
app/Http/Controllers/Payments/CentiliController.php
···
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
-
6
-
namespace App\Http\Controllers\Payments;
7
-
8
-
use App\Exceptions\InvalidSignatureException;
9
-
use App\Exceptions\ValidationException;
10
-
use App\Libraries\OrderCheckout;
11
-
use App\Libraries\Payments\CentiliPaymentProcessor;
12
-
use App\Libraries\Payments\CentiliSignature;
13
-
use App\Models\Store\Order;
14
-
use Illuminate\Http\Request as HttpRequest;
15
-
16
-
class CentiliController extends Controller
17
-
{
18
-
public function __construct()
19
-
{
20
-
$this->middleware('auth', ['except' => ['callback']]);
21
-
$this->middleware('check-user-restricted', ['except' => ['callback']]);
22
-
$this->middleware('verify-user', ['except' => ['callback']]);
23
-
24
-
parent::__construct();
25
-
}
26
-
27
-
public function callback(HttpRequest $request)
28
-
{
29
-
$params = static::extractParams($request);
30
-
$signature = new CentiliSignature($request);
31
-
$processor = new CentiliPaymentProcessor($params, $signature);
32
-
33
-
try {
34
-
$processor->run();
35
-
} catch (ValidationException $exception) {
36
-
\Log::error($exception->getMessage());
37
-
38
-
return response(['message' => 'A validation error occured while running the transaction'], 406);
39
-
} catch (InvalidSignatureException $exception) {
40
-
return response(['message' => $exception->getMessage()], 406);
41
-
}
42
-
43
-
return 'ok';
44
-
}
45
-
46
-
public function completed()
47
-
{
48
-
$orderNumber = request()->input('reference') ?? '';
49
-
$order = OrderCheckout::for($orderNumber)->completeCheckout();
50
-
51
-
return redirect(route('store.invoice.show', ['invoice' => $order->order_id, 'thanks' => 1]));
52
-
}
53
-
54
-
public function failed()
55
-
{
56
-
$order = Order::whereOrderNumber(request()->input('reference'))->firstOrFail();
57
-
request()->session()->flash('status', 'An error occured while processing the payment.');
58
-
59
-
return redirect(route('store.checkout.show', $order));
60
-
}
61
-
}
-1
app/Http/Middleware/VerifyCsrfToken.php
-1
app/Http/Middleware/VerifyCsrfToken.php
-31
app/Libraries/OrderCheckout.php
-31
app/Libraries/OrderCheckout.php
···
9
9
use App\Libraries\Payments\InvalidOrderStateException;
10
10
use App\Models\Store\Order;
11
11
use DB;
12
-
use Request;
13
12
14
13
class OrderCheckout
15
14
{
···
64
63
65
64
if ($this->order->getTotal() > 0) {
66
65
$allowed = [Order::PROVIDER_PAYPAL];
67
-
if ($this->allowCentiliPayment()) {
68
-
$allowed[] = Order::PROVIDER_CENTILLI;
69
-
}
70
66
71
67
if ($this->allowXsollaPayment()) {
72
68
$allowed[] = Order::PROVIDER_XSOLLA;
···
76
72
}
77
73
78
74
return [Order::PROVIDER_FREE];
79
-
}
80
-
81
-
/**
82
-
* @return string
83
-
*/
84
-
public function getCentiliPaymentLink()
85
-
{
86
-
$params = [
87
-
'apikey' => $GLOBALS['cfg']['payments']['centili']['api_key'],
88
-
'country' => 'jp',
89
-
'countrylock' => 'true',
90
-
'reference' => $this->order->getOrderNumber(),
91
-
'price' => $this->order->getTotal() * $GLOBALS['cfg']['payments']['centili']['conversion_rate'],
92
-
];
93
-
94
-
return $GLOBALS['cfg']['payments']['centili']['widget_url'].'?'.http_build_query($params);
95
75
}
96
76
97
77
public function beginCheckout()
···
217
197
public static function for(?string $orderNumber): self
218
198
{
219
199
return new static(Order::whereOrderNumber($orderNumber)->firstOrFail());
220
-
}
221
-
222
-
/**
223
-
* @return bool
224
-
*/
225
-
private function allowCentiliPayment()
226
-
{
227
-
return $GLOBALS['cfg']['payments']['centili']['enabled']
228
-
&& strcasecmp(request_country(), 'JP') === 0
229
-
&& !$this->order->requiresShipping()
230
-
&& Request::input('intl') !== '1';
231
200
}
232
201
233
202
/**
-114
app/Libraries/Payments/CentiliPaymentProcessor.php
-114
app/Libraries/Payments/CentiliPaymentProcessor.php
···
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
-
6
-
namespace App\Libraries\Payments;
7
-
8
-
use App\Models\Store\Order;
9
-
use Carbon\Carbon;
10
-
11
-
// FIXME: rename?
12
-
class CentiliPaymentProcessor extends PaymentProcessor
13
-
{
14
-
public function getCountryCode()
15
-
{
16
-
return $this['country'] ?? $this['countryCode'];
17
-
}
18
-
19
-
public function getOrderNumber()
20
-
{
21
-
return $this['reference'] ?? $this['clientid'];
22
-
}
23
-
24
-
public function getPaymentProvider()
25
-
{
26
-
return Order::PROVIDER_CENTILLI;
27
-
}
28
-
29
-
public function getPaymentTransactionId()
30
-
{
31
-
return $this['transactionid'];
32
-
}
33
-
34
-
public function getPaymentAmount()
35
-
{
36
-
// TODO: less floaty
37
-
return $this['enduserprice'] / $GLOBALS['cfg']['payments']['centili']['conversion_rate'];
38
-
}
39
-
40
-
public function getPaymentDate()
41
-
{
42
-
return Carbon::now();
43
-
}
44
-
45
-
public function getNotificationType()
46
-
{
47
-
static $mapping = [
48
-
'success' => NotificationType::PAYMENT,
49
-
'failed' => NotificationType::REJECTED,
50
-
'canceled' => NotificationType::REJECTED, // TODO: verify documentation is correct >_>
51
-
];
52
-
53
-
return $mapping[$this->getNotificationTypeRaw()]
54
-
?? "unknown__{$this->getNotificationTypeRaw()}";
55
-
}
56
-
57
-
public function getNotificationTypeRaw()
58
-
{
59
-
return $this['status'];
60
-
}
61
-
62
-
public function isTest()
63
-
{
64
-
return false; // No sandbox for Centili :(
65
-
}
66
-
67
-
public function validateTransaction()
68
-
{
69
-
$this->ensureValidSignature();
70
-
71
-
$order = $this->getOrder();
72
-
// order should exist
73
-
if ($order === null) {
74
-
$this->validationErrors()->add('order', '.order.invalid');
75
-
76
-
return false;
77
-
}
78
-
79
-
if ($this['service'] !== $GLOBALS['cfg']['payments']['centili']['api_key']) {
80
-
$this->validationErrors()->add('service', '.param.invalid', ['param' => 'service']);
81
-
}
82
-
83
-
// order should be in the correct state
84
-
if (
85
-
$this->getNotificationType() === NotificationType::PAYMENT
86
-
&& $order->isPendingPaymentCapture() === false
87
-
) {
88
-
$this->validationErrors()->add('order.status', '.order.status.not_checkout', ['state' => $order->status]);
89
-
}
90
-
91
-
// All items should be virtual
92
-
if ($order->requiresShipping()) {
93
-
$this->validationErrors()->add(
94
-
'order.items',
95
-
'.order.items.virtual_only',
96
-
['provider' => $this->getPaymentProvider()]
97
-
);
98
-
}
99
-
100
-
if (strcasecmp($this['country'], 'JP') !== 0) {
101
-
$this->validationErrors()->add('country', '.param.invalid', ['param' => 'country']);
102
-
}
103
-
104
-
if (compare_currency($this->getPaymentAmount(), $order->getTotal()) !== 0) {
105
-
$this->validationErrors()->add(
106
-
'purchase.checkout.amount',
107
-
'.purchase.checkout.amount',
108
-
['expected' => $order->getTotal(), 'actual' => $this->getPaymentAmount()]
109
-
);
110
-
}
111
-
112
-
return $this->validationErrors()->isEmpty();
113
-
}
114
-
}
-52
app/Libraries/Payments/CentiliSignature.php
-52
app/Libraries/Payments/CentiliSignature.php
···
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
-
6
-
namespace App\Libraries\Payments;
7
-
8
-
use Illuminate\Http\Request;
9
-
10
-
class CentiliSignature implements PaymentSignature
11
-
{
12
-
public function __construct(private Request $request)
13
-
{
14
-
}
15
-
16
-
public function isValid()
17
-
{
18
-
$received = $this->receivedSignature();
19
-
\Log::debug("CentiliSignature::isValidSignature calc: {$this->calculatedSignature()}, signed: {$received}");
20
-
if ($received === null) {
21
-
return false;
22
-
}
23
-
24
-
return hash_equals($this->calculatedSignature(), $received);
25
-
}
26
-
27
-
public static function calculateSignature(array $params)
28
-
{
29
-
// Centili signature is a HMAC of the concatenation of all params values sorted alphabetically by key name.
30
-
$content = static::stringifyInput($params);
31
-
32
-
return hash_hmac('sha1', $content, $GLOBALS['cfg']['payments']['centili']['secret_key'], false);
33
-
}
34
-
35
-
public static function stringifyInput(array $input)
36
-
{
37
-
ksort($input);
38
-
unset($input['sign']);
39
-
40
-
return implode('', array_values($input)); // array_values might not be needed.
41
-
}
42
-
43
-
private function receivedSignature()
44
-
{
45
-
return $this->request->input('sign');
46
-
}
47
-
48
-
private function calculatedSignature()
49
-
{
50
-
return static::calculateSignature($this->request->input());
51
-
}
52
-
}
-1
app/Models/Store/Order.php
-1
app/Models/Store/Order.php
···
55
55
const ORDER_NUMBER_REGEX = '/^(?<prefix>[A-Za-z]+)-(?<userId>\d+)-(?<orderId>\d+)$/';
56
56
const PENDING_ECHECK = 'PENDING ECHECK';
57
57
58
-
const PROVIDER_CENTILLI = 'centili';
59
58
const PROVIDER_FREE = 'free';
60
59
const PROVIDER_PAYPAL = 'paypal';
61
60
const PROVIDER_SHOPIFY = 'shopify';
-8
config/payments.php
-8
config/payments.php
···
5
5
'running_cost' => (int) presence(env('OSU_RUNNING_COST'), 3141592), // arbritary default >_>
6
6
'sandbox' => get_bool(env('PAYMENT_SANDBOX')) ?? false,
7
7
8
-
'centili' => [
9
-
'api_key' => env('CENTILI_API_KEY'),
10
-
'conversion_rate' => (float) presence(env('CENTILI_CONVERSION_RATE'), 100),
11
-
'enabled' => get_bool(env('CENTILI_ENABLED')) ?? false,
12
-
'secret_key' => env('CENTILI_SECRET_KEY'),
13
-
'widget_url' => env('CENTILI_WIDGET_URL'),
14
-
],
15
-
16
8
'paypal' => [
17
9
'client_id' => env('PAYPAL_CLIENT_ID'),
18
10
'client_secret' => env('PAYPAL_CLIENT_SECRET'),
-5
resources/js/core-legacy/store-checkout.coffee
-5
resources/js/core-legacy/store-checkout.coffee
···
20
20
provider = element.dataset.provider
21
21
orderNumber = element.dataset.orderNumber
22
22
switch provider
23
-
when 'centili' then init['centili'] = Promise.resolve()
24
23
when 'free' then init['free'] = Promise.resolve()
25
24
when 'paypal' then init['paypal'] = Promise.resolve()
26
25
when 'xsolla' then init['xsolla'] = StoreXsolla.promiseInit(orderNumber)
···
43
42
@startPayment: (params) ->
44
43
{ orderId, provider, url } = params
45
44
switch provider
46
-
when 'centili'
47
-
new Promise (resolve) ->
48
-
window.location.href = url
49
-
50
45
when 'paypal'
51
46
StorePaypal.fetchApprovalLink(orderId).then (link) ->
52
47
window.location.href = link
-47
resources/views/store/checkout/_centili.blade.php
-47
resources/views/store/checkout/_centili.blade.php
···
1
-
{{--
2
-
Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
3
-
See the LICENCE file in the repository root for full licence text.
4
-
--}}
5
-
6
-
<div class="store-payment-method">
7
-
<div class="store-payment-method__cell">
8
-
<button type="button"
9
-
class="js-store-checkout-button store-payment-button store-payment-button--centili"
10
-
data-provider="centili"
11
-
data-order-id="{{ $order->order_id }}"
12
-
data-url="{{ $checkout->getCentiliPaymentLink() }}"
13
-
>
14
-
</div>
15
-
16
-
<div class="store-payment-method__cell">
17
-
<div class="store-text store-text--header">Pay with ¥COINS</div>
18
-
19
-
<div class="store-text store-text--block">You can complete your transactions using ¥COINS</div>
20
-
21
-
<div class="store-text store-text--block store-text--emphasis">Powered by</div>
22
-
<div class="store-payment-method__provider-list">
23
-
<img
24
-
class="store-payment-method__provider store-payment-method__provider--light"
25
-
src="/images/store/providers/webmoney.png"
26
-
alt="WebMoney"
27
-
>
28
-
<img
29
-
class="store-payment-method__provider store-payment-method__provider--light"
30
-
src="/images/store/providers/softbank.png"
31
-
alt="Softbank"
32
-
>
33
-
</div>
34
-
<div class="store-payment-method__provider-list">
35
-
<img
36
-
class="store-payment-method__provider"
37
-
src="/images/store/providers/docomo.png"
38
-
alt="Docomo"
39
-
>
40
-
<img
41
-
class="store-payment-method__provider"
42
-
src="/images/store/providers/au.png"
43
-
alt="au"
44
-
>
45
-
</div>
46
-
</div>
47
-
</div>
-6
routes/web.php
-6
routes/web.php
···
371
371
Route::post('callback', 'XsollaController@callback')->name('callback');
372
372
});
373
373
374
-
Route::group(['as' => 'centili.', 'prefix' => 'centili'], function () {
375
-
Route::match(['post', 'get'], 'callback', 'CentiliController@callback')->name('callback');
376
-
Route::get('completed', 'CentiliController@completed')->name('completed');
377
-
Route::get('failed', 'CentiliController@failed')->name('failed');
378
-
});
379
-
380
374
Route::group(['as' => 'shopify.', 'prefix' => 'shopify'], function () {
381
375
Route::post('callback', 'ShopifyController@callback')->name('callback');
382
376
});
-75
tests/Controllers/Payments/CentiliControllerTest.php
-75
tests/Controllers/Payments/CentiliControllerTest.php
···
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
-
6
-
namespace Tests\Controllers\Payments;
7
-
8
-
use App\Libraries\Payments\CentiliSignature;
9
-
use App\Models\Store\Order;
10
-
use App\Models\Store\OrderItem;
11
-
use Tests\TestCase;
12
-
13
-
class CentiliControllerTest extends TestCase
14
-
{
15
-
public function testWhenEverythingIsFine()
16
-
{
17
-
$data = $this->getPostData();
18
-
19
-
$response = $this->call(
20
-
'POST',
21
-
route('payments.centili.callback'),
22
-
$data
23
-
);
24
-
25
-
$response->assertStatus(200);
26
-
}
27
-
28
-
public function testWhenPaymentIsInsufficient()
29
-
{
30
-
$orderItem = OrderItem::factory()->supporterTag()->create(['order_id' => $this->order]);
31
-
32
-
$data = $this->getPostData(['enduserprice' => '479.000']);
33
-
34
-
$response = $this->call(
35
-
'POST',
36
-
route('payments.centili.callback'),
37
-
$data
38
-
);
39
-
40
-
$response->assertStatus(406);
41
-
}
42
-
43
-
protected function setUp(): void
44
-
{
45
-
parent::setUp();
46
-
config_set('payments.centili.secret_key', 'secret_key');
47
-
config_set('payments.centili.api_key', 'api_key');
48
-
config_set('payments.centili.conversion_rate', 120.00);
49
-
$this->order = Order::factory()->checkout()->create();
50
-
}
51
-
52
-
private function getPostData(array $overrides = [])
53
-
{
54
-
$base = [
55
-
'reference' => $this->order->getOrderNumber(),
56
-
'country' => 'jp',
57
-
'enduserprice' => $this->order->getTotal() * $GLOBALS['cfg']['payments']['centili']['conversion_rate'],
58
-
'event_type' => 'one_off',
59
-
'mnocode' => 'BADDOG',
60
-
'phone' => 'test@example.org',
61
-
'revenue' => '4.34',
62
-
'revenuecurrency' => 'MONOPOLY',
63
-
'service' => $GLOBALS['cfg']['payments']['centili']['api_key'],
64
-
'status' => 'success',
65
-
'transactionid' => '111222333',
66
-
];
67
-
68
-
$data = array_merge($base, $overrides);
69
-
70
-
// Generate a fake correct signature :trollface:
71
-
$data['sign'] = CentiliSignature::calculateSignature($data);
72
-
73
-
return $data;
74
-
}
75
-
}
-198
tests/Libraries/Payments/CentiliPaymentProcessorTest.php
-198
tests/Libraries/Payments/CentiliPaymentProcessorTest.php
···
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
-
6
-
namespace Tests\Libraries\Payments;
7
-
8
-
use App\Exceptions\InvalidSignatureException;
9
-
use App\Libraries\Payments\CentiliPaymentProcessor;
10
-
use App\Libraries\Payments\PaymentProcessorException;
11
-
use App\Libraries\Payments\PaymentSignature;
12
-
use App\Libraries\Payments\UnsupportedNotificationTypeException;
13
-
use App\Models\Store\Order;
14
-
use App\Models\Store\OrderItem;
15
-
use Illuminate\Support\Facades\Event;
16
-
use Tests\TestCase;
17
-
18
-
class CentiliPaymentProcessorTest extends TestCase
19
-
{
20
-
public function testWhenEverythingIsFine()
21
-
{
22
-
$params = $this->getTestParams();
23
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
24
-
$subject->run();
25
-
26
-
$this->assertTrue($subject->validationErrors()->isEmpty());
27
-
}
28
-
29
-
public function testWhenPaymentWasCancelled()
30
-
{
31
-
// FIXME: but now we can't see the notification, annoying ?_?
32
-
Event::fake();
33
-
34
-
$params = $this->getTestParams(['status' => 'canceled']);
35
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
36
-
$subject->run();
37
-
Event::assertDispatched('store.payments.rejected.centili');
38
-
}
39
-
40
-
public function testWhenPaymentFailed()
41
-
{
42
-
Event::fake();
43
-
44
-
$params = $this->getTestParams(['status' => 'failed']);
45
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
46
-
$subject->run();
47
-
48
-
Event::assertDispatched('store.payments.rejected.centili');
49
-
}
50
-
51
-
public function testWhenStatusIsUnknown()
52
-
{
53
-
$this->expectException(UnsupportedNotificationTypeException::class);
54
-
55
-
$params = $this->getTestParams(['status' => 'derp']);
56
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
57
-
$subject->run();
58
-
}
59
-
60
-
public function testWhenCountryIsNotJapan()
61
-
{
62
-
$params = $this->getTestParams(['country' => 'au']);
63
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
64
-
$thrown = $this->runSubject($subject);
65
-
66
-
$errors = $subject->validationErrors()->all();
67
-
$this->assertTrue($thrown);
68
-
$this->assertArrayHasKey('country', $errors);
69
-
}
70
-
71
-
public function testWhenPaymentIsInsufficient()
72
-
{
73
-
$orderItem = OrderItem::factory()->supporterTag()->create(['order_id' => $this->order]);
74
-
75
-
$params = $this->getTestParams(['enduserprice' => '479.000']);
76
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
77
-
78
-
// want to examine the contents of validationErrors
79
-
$thrown = $this->runSubject($subject);
80
-
81
-
$errors = $subject->validationErrors()->all();
82
-
$this->assertTrue($thrown);
83
-
$this->assertArrayHasKey('purchase.checkout.amount', $errors);
84
-
}
85
-
86
-
public function testWhenApiKeyIsInvalid()
87
-
{
88
-
$params = $this->getTestParams(['service' => 'not_magic']);
89
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
90
-
91
-
// want to examine the contents of validationErrors
92
-
$thrown = $this->runSubject($subject);
93
-
94
-
$errors = $subject->validationErrors()->all();
95
-
$this->assertTrue($thrown);
96
-
$this->assertArrayHasKey('service', $errors);
97
-
}
98
-
99
-
public function testWhenUserIdMismatch()
100
-
{
101
-
$orderNumber = 'store-'
102
-
.($this->order->user_id + 10) // just make it bigger than whatever the factory generated
103
-
.'-'
104
-
.$this->order->order_id;
105
-
106
-
$params = $this->getTestParams(['reference' => $orderNumber]);
107
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
108
-
109
-
$thrown = $this->runSubject($subject);
110
-
111
-
$errors = $subject->validationErrors()->all();
112
-
$this->assertTrue($thrown);
113
-
$this->assertArrayHasKey('order', $errors);
114
-
}
115
-
116
-
public function testWhenOrderNumberMalformed()
117
-
{
118
-
$orderNumber = "{$this->order->getOrderNumber()}-oops";
119
-
120
-
$params = $this->getTestParams(['reference' => $orderNumber]);
121
-
$subject = new CentiliPaymentProcessor($params, $this->validSignature());
122
-
123
-
$thrown = $this->runSubject($subject);
124
-
125
-
$errors = $subject->validationErrors()->all();
126
-
$this->assertTrue($thrown);
127
-
$this->assertArrayHasKey('order', $errors);
128
-
}
129
-
130
-
public function testWhenSignatureInvalid()
131
-
{
132
-
$params = $this->getTestParams();
133
-
$subject = new CentiliPaymentProcessor($params, $this->invalidSignature());
134
-
135
-
$this->expectException(InvalidSignatureException::class);
136
-
$this->runSubject($subject);
137
-
}
138
-
139
-
protected function setUp(): void
140
-
{
141
-
parent::setUp();
142
-
config_set('payments.centili.api_key', 'api_key');
143
-
config_set('payments.centili.conversion_rate', 120.00);
144
-
$this->order = Order::factory()->checkout()->create();
145
-
}
146
-
147
-
private function getTestParams(array $overrides = [])
148
-
{
149
-
$base = [
150
-
'reference' => $this->order->getOrderNumber(),
151
-
'country' => 'jp',
152
-
'enduserprice' => $this->order->getTotal() * $GLOBALS['cfg']['payments']['centili']['conversion_rate'],
153
-
'event_type' => 'one_off',
154
-
'mnocode' => 'BADDOG',
155
-
'phone' => 'test@example.org',
156
-
'revenue' => '4.34',
157
-
'revenuecurrency' => 'MONOPOLY',
158
-
'service' => $GLOBALS['cfg']['payments']['centili']['api_key'],
159
-
'status' => 'success',
160
-
'transactionid' => '111222333',
161
-
];
162
-
163
-
return array_merge($base, $overrides);
164
-
}
165
-
166
-
// wrapper to catch the exception
167
-
// so that the contents of validationErrors can be examined.
168
-
private function runSubject($subject)
169
-
{
170
-
try {
171
-
$subject->run();
172
-
} catch (PaymentProcessorException $e) {
173
-
return true;
174
-
}
175
-
176
-
return false;
177
-
}
178
-
179
-
private function validSignature()
180
-
{
181
-
return new class implements PaymentSignature {
182
-
public function isValid()
183
-
{
184
-
return true;
185
-
}
186
-
};
187
-
}
188
-
189
-
private function invalidSignature()
190
-
{
191
-
return new class implements PaymentSignature {
192
-
public function isValid()
193
-
{
194
-
return false;
195
-
}
196
-
};
197
-
}
198
-
}
-56
tests/Libraries/Payments/CentiliSignatureTest.php
-56
tests/Libraries/Payments/CentiliSignatureTest.php
···
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
-
6
-
namespace Tests\Libraries\Payments;
7
-
8
-
use App\Libraries\Payments\CentiliSignature;
9
-
use Tests\TestCase;
10
-
11
-
class CentiliSignatureTest extends TestCase
12
-
{
13
-
public function testStringifyInput()
14
-
{
15
-
// sorts params and excludes 'sign'
16
-
static $expected = 'wyzv';
17
-
static $params = [
18
-
'cde' => 'z',
19
-
'ab' => 'y',
20
-
'sign' => 'x',
21
-
'aa' => 'w',
22
-
'ce' => 'v',
23
-
];
24
-
25
-
$this->assertSame($expected, CentiliSignature::stringifyInput($params));
26
-
}
27
-
28
-
public function testCalculateSignature()
29
-
{
30
-
static $expected = '98b26bf9ba67820abb3cc76900c0d47fac52ca4b';
31
-
static $params = [
32
-
'clientid' => 'test-12345-123',
33
-
'country' => 'jp',
34
-
'enduserprice' => '900.000',
35
-
'event_type' => 'one_off',
36
-
'mnocode' => 'THEBEST',
37
-
'phone' => 'best@example.org',
38
-
'revenue' => '12.3456',
39
-
'revenuecurrency' => 'USD',
40
-
'service' => 'adc38aea0cf18391a31e83f0b8a88286',
41
-
'sign' => '98b26bf9ba67820abb3cc76900c0d47fac52ca4b',
42
-
'status' => 'success',
43
-
'transactionid' => '111222333444',
44
-
];
45
-
46
-
$signature = CentiliSignature::calculateSignature($params);
47
-
48
-
$this->assertSame($expected, $signature);
49
-
}
50
-
51
-
protected function setUp(): void
52
-
{
53
-
parent::setUp();
54
-
config_set('payments.centili.secret_key', 'magic');
55
-
}
56
-
}