the browser-facing portion of osu!
at master 5.0 kB view raw
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 Tests\Controllers\Payments; 7 8use App\Libraries\Payments\ShopifySignature; 9use App\Models\Country; 10use App\Models\Store\Order; 11use App\Models\Store\Payment; 12use Tests\TestCase; 13 14class ShopifyControllerTest extends TestCase 15{ 16 public function testWebhookOrdersCancelled() 17 { 18 $order = Order::factory()->paid()->shopify()->create(); 19 $payment = new Payment([ 20 'provider' => Order::PROVIDER_SHOPIFY, 21 'transaction_id' => $order->getProviderReference(), 22 'country_code' => Country::UNKNOWN, 23 'paid_at' => now(), 24 ]); 25 $order->payments()->save($payment); 26 $order->paid($payment); 27 28 $this->setShopifyPayload([ 29 'note_attributes' => [['name' => 'orderId', 'value' => $order->getKey()]], 30 ]); 31 32 $response = $this->sendCallbackRequest(['X-Shopify-Topic' => 'orders/cancelled']); 33 34 $order->refresh(); 35 $response->assertStatus(204); 36 37 $this->assertTrue($order->isCancelled()); 38 $this->assertTrue(Payment::where('order_id', $order->getKey())->where('cancelled', true)->exists()); 39 } 40 41 public function testWebhookOrdersCreate() 42 { 43 $order = Order::factory()->shopify()->processing()->create(); 44 $this->setShopifyPayload([ 45 'note_attributes' => [['name' => 'orderId', 'value' => $order->getKey()]], 46 ]); 47 48 $response = $this->sendCallbackRequest(['X-Shopify-Topic' => 'orders/create']); 49 50 $order->refresh(); 51 $response->assertStatus(204); 52 $this->assertTrue($order->status === 'checkout'); 53 } 54 55 public function testWebhookOrdersFulfilled() 56 { 57 $order = Order::factory()->shopify()->checkout()->create(); 58 $this->setShopifyPayload([ 59 'note_attributes' => [['name' => 'orderId', 'value' => $order->getKey()]], 60 ]); 61 62 $response = $this->sendCallbackRequest(['X-Shopify-Topic' => 'orders/fulfilled']); 63 64 $order->refresh(); 65 $response->assertStatus(204); 66 $this->assertTrue($order->isShipped()); 67 $this->assertNotNull($order->shipped_at); 68 } 69 70 public function testWebhookOrdersPaid() 71 { 72 $order = Order::factory()->shopify()->processing()->create(); 73 $this->setShopifyPayload([ 74 'note_attributes' => [['name' => 'orderId', 'value' => $order->getKey()]], 75 ]); 76 77 $response = $this->sendCallbackRequest(['X-Shopify-Topic' => 'orders/paid']); 78 79 $order->refresh(); 80 $response->assertStatus(204); 81 $this->assertTrue($order->isPaidOrDelivered()); 82 $this->assertTrue(Payment::where('order_id', $order->getKey())->where('cancelled', false)->exists()); 83 } 84 85 public function testReplacementOrdersManuallyCreatedShouldBeIgnored() 86 { 87 $this->setShopifyPayload([ 88 'note_attributes' => [], 89 'gateway' => 'manual', 90 'payment_gateway_names' => ['manual'], 91 'processing_method' => 'manual', 92 ]); 93 94 $response = $this->sendCallbackRequest(); 95 96 $response->assertStatus(204); 97 $this->assertSame(Order::withoutGlobalScopes()->count(), 0); 98 } 99 100 public function testReplacementOrdersCreatedByDuplicatingShopifyOrderShouldBeIgnored() 101 { 102 // Orders are already shipped when the replacement gets created. 103 $order = Order::factory()->shopify()->shipped()->create(); 104 $oldUpdatedAt = $order->updated_at->copy(); 105 106 $this->setShopifyPayload([ 107 'note_attributes' => [['name' => 'orderId', 'value' => $order->getKey()]], 108 'gateway' => 'manual', 109 'payment_gateway_names' => ['manual'], 110 'processing_method' => 'manual', 111 'source_name' => 'shopify_draft_order', 112 ]); 113 114 $response = $this->sendCallbackRequest(); 115 116 $order->refresh(); 117 $response->assertStatus(204); 118 $this->assertSame($order->status, 'shipped'); 119 $this->assertEquals($order->updated_at, $oldUpdatedAt); 120 $this->assertSame(Order::withoutGlobalScopes()->count(), 1); 121 } 122 123 protected function setUp(): void 124 { 125 parent::setUp(); 126 config_set('payments.shopify.webhook_key', 'magic'); 127 128 $this->url = route('payments.shopify.callback'); 129 } 130 131 private function sendCallbackRequest(array $extraHeaders = []) 132 { 133 $validSignature = ShopifySignature::calculateSignature(json_encode($this->payload)); 134 $headers = array_merge(['X-Shopify-Hmac-Sha256' => $validSignature], $extraHeaders); 135 136 return $this->json('POST', $this->url, $this->payload, $headers); 137 } 138 139 private function setShopifyPayload(array $params) 140 { 141 $this->payload = array_merge([ 142 'id' => 1, 143 'order_number' => 1, 144 ], $params); 145 } 146}