the browser-facing portion of osu!

remove centili

bakaneko 7cca2130 afead367

Changed files
-665
app
Http
Controllers
Middleware
Libraries
Models
Store
config
resources
css
js
core-legacy
views
store
routes
tests
-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
··· 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
··· 15 15 protected $except = [ 16 16 'home/changelog/github', 17 17 'oauth/authorize', 18 - 'payments/centili/callback', 19 18 'payments/paypal/ipn', 20 19 'payments/shopify/callback', 21 20 'payments/xsolla/callback',
-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
··· 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
··· 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
··· 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
··· 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'),
-4
resources/css/bem/store-payment-button.less
··· 24 24 box-shadow: 0 0 0 3px @osu-colour-l4; 25 25 } 26 26 27 - &--centili { 28 - background-image: url('~@images/store/ycoins.png'); 29 - } 30 - 31 27 &--paypal { 32 28 background-image: url('~@images/store/paypal.png'); 33 29 }
-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
··· 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
··· 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
··· 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
··· 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
··· 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 - }