From ffb9f95a3e201dd6564bf2e832a076ea1271a095 Mon Sep 17 00:00:00 2001 From: Yoichi Fujimoto Date: Fri, 18 Apr 2025 10:06:17 +0900 Subject: [PATCH 1/3] Refactor tests to use mock data and improve readability - Updated EventTest to mock event data retrieval and assertions. - Enhanced InvalidRequestErrorTest to mock error responses for invalid requests. - Refactored PlanTest to utilize mock data for plan creation, retrieval, and deletion. - Improved SubscriptionTest with mock data for customer and subscription lifecycle tests. - Added mock data generation methods in TokenTest for token creation and retrieval. - Enhanced TransferTest to mock transfer and charge data for testing. - Updated UtilTest to mock customer data for utility function tests. --- lib/Util/Util.php | 1 + tests/AccountTest.php | 14 +- tests/AuthenticationErrorTest.php | 4 + tests/BalanceTest.php | 10 +- tests/CardErrorTest.php | 5 +- tests/ChargeTest.php | 603 ++++++++++++++------------- tests/CustomerTest.php | 648 +++++++++++++++++++++--------- tests/EventTest.php | 76 +++- tests/InvalidRequestErrorTest.php | 8 + tests/PlanTest.php | 177 ++++++-- tests/SubscriptionTest.php | 289 ++++++++----- tests/TokenTest.php | 84 +++- tests/TransferTest.php | 204 +++++++++- tests/UtilTest.php | 4 + 14 files changed, 1476 insertions(+), 651 deletions(-) diff --git a/lib/Util/Util.php b/lib/Util/Util.php index 7d119ef..25ef650 100644 --- a/lib/Util/Util.php +++ b/lib/Util/Util.php @@ -12,6 +12,7 @@ abstract class Util 'balance' => \Payjp\Balance::class, 'card' => \Payjp\Card::class, 'charge' => \Payjp\Charge::class, + 'account' => \Payjp\Account::class, 'customer' => \Payjp\Customer::class, 'event' => \Payjp\Event::class, 'list' => \Payjp\Collection::class, diff --git a/tests/AccountTest.php b/tests/AccountTest.php index 596b268..e7827d0 100644 --- a/tests/AccountTest.php +++ b/tests/AccountTest.php @@ -6,7 +6,7 @@ class AccountTest extends TestCase { private function managedAccountResponse($id) { - return [ + $data = [ 'accounts_enabled' => [ 'merchant', 'customer', @@ -80,6 +80,7 @@ private function managedAccountResponse($id) ], 'object' => 'account' ]; + return $data; } public function testBasicRetrieve() @@ -87,8 +88,19 @@ public function testBasicRetrieve() $this->mockRequest('GET', '/v1/accounts', [], $this->managedAccountResponse('acct_ABC')); $account = Account::retrieve(); + $this->assertInstanceOf('Payjp\Account', $account); $this->assertSame('acct_ABC', $account->id); + + $this->assertInstanceOf('Payjp\PayjpObject', $account->customer); $this->assertSame('acct_cus_38153121efdb7964dd1e147', $account->customer->id); + + $this->assertInstanceOf('Payjp\PayjpObject', $account->merchant); $this->assertSame('acct_mch_002418151ef82e49f6edee1', $account->merchant->id); + + $this->assertInstanceOf('Payjp\Collection', $account->customer->cards); + $this->assertTrue(is_array($account->customer->cards->data)); + $this->assertCount(1, $account->customer->cards->data); + $this->assertInstanceOf('Payjp\Card', $account->customer->cards->data[0]); + $this->assertSame('car_99abf74cb5527ff68233a8b836dd', $account->customer->cards->data[0]->id); } } diff --git a/tests/AuthenticationErrorTest.php b/tests/AuthenticationErrorTest.php index 1c4ffbd..b38ca0b 100644 --- a/tests/AuthenticationErrorTest.php +++ b/tests/AuthenticationErrorTest.php @@ -7,6 +7,10 @@ class AuthenticationErrorTest extends TestCase public function testInvalidCredentials() { Payjp::setApiKey('invalid'); + $mock = $this->setUpMockRequest(); + $mock->expects($this->once()) + ->method('request') + ->willThrowException(new Error\Authentication('Invalid API Key', 401)); try { Customer::create(); } catch (Error\Authentication $e) { diff --git a/tests/BalanceTest.php b/tests/BalanceTest.php index ca967e4..f2b5607 100644 --- a/tests/BalanceTest.php +++ b/tests/BalanceTest.php @@ -165,8 +165,16 @@ public function testRetrieve() $this->assertSame($expectedBalanceResource['statements']['object'], $balance->statements->object); $this->assertSame($expectedBalanceResource['statements']['url'], $balance->statements->url); - foreach ($balance->statements->data as $statement) { + $expectedStatementsData = $expectedBalanceResource['statements']['data']; + foreach ($balance->statements->data as $index => $statement) { $this->assertInstanceOf(Statement::class, $statement); + $this->assertSame($expectedStatementsData[$index]['id'], $statement->id); + if (isset($expectedStatementsData[$index]['term']) && $expectedStatementsData[$index]['term'] !== null) { + $this->assertInstanceOf(PayjpObject::class, $statement->term); // Assuming Term becomes PayjpObject + $this->assertSame($expectedStatementsData[$index]['term']['id'], $statement->term->id); + } else { + $this->assertNull($statement->term); + } } } diff --git a/tests/CardErrorTest.php b/tests/CardErrorTest.php index 7ad174e..b470d75 100644 --- a/tests/CardErrorTest.php +++ b/tests/CardErrorTest.php @@ -16,7 +16,10 @@ public function testExpiredCard() 'cvc' => '314' ] ]; - + $mock = $this->setUpMockRequest(); + $mock->expects($this->once()) + ->method('request') + ->willThrowException(new Error\Card('Expired card', null, 'expired_card', 402, null, null)); try { Token::create($params, ['payjp_direct_token_generate' => 'true']); } catch (Error\Card $e) { diff --git a/tests/ChargeTest.php b/tests/ChargeTest.php index 84f6a40..a418f57 100644 --- a/tests/ChargeTest.php +++ b/tests/ChargeTest.php @@ -4,366 +4,423 @@ class ChargeTest extends TestCase { + private function mockCardData($id = 'car_test', $attributes = []) + { + $base = [ + 'address_city' => null, + 'address_line1' => null, + 'address_line2' => null, + 'address_state' => null, + 'address_zip' => null, + 'address_zip_check' => 'unchecked', + 'brand' => 'Visa', + 'country' => null, + 'created' => 1583375140, + 'customer' => null, + 'cvc_check' => 'passed', + 'exp_month' => 2, + 'exp_year' => 2099, + 'fingerprint' => 'e1d8225886e3a7211127df751c86787f', + 'id' => $id, + 'last4' => '4242', + 'livemode' => false, + 'metadata' => [], + 'name' => 'PAY TARO', + 'object' => 'card', + 'three_d_secure_status' => null + ]; + return array_merge($base, $attributes); + } + + private function mockTokenData($id = 'tok_test', $cardAttributes = []) + { + $cardData = $this->mockCardData('car_' . $id, $cardAttributes); + return [ + 'id' => $id, + 'object' => 'token', + 'livemode' => false, + 'used' => false, + 'card' => $cardData, + 'created' => time(), + ]; + } + + private function mockChargeData($id = 'ch_test', $attributes = [], $cardAttributes = []) + { + $cardData = $this->mockCardData('car_6845da1a8651f889bc432362dfcb', $cardAttributes); + $base = [ + 'amount' => 3500, + 'amount_refunded' => 0, + 'captured' => true, + 'captured_at' => 1433127983, + 'card' => $cardData, + 'created' => 1433127983, + 'currency' => 'jpy', + 'customer' => null, + 'description' => null, + 'expired_at' => null, + 'failure_code' => null, + 'failure_message' => null, + 'fee_rate' => '3.00', + 'id' => $id, + 'livemode' => false, + 'metadata' => null, + 'object' => 'charge', + 'paid' => true, + 'platform_fee' => null, + 'platform_fee_rate' => '10.00', + 'refund_reason' => null, + 'refunded' => false, + 'subscription' => null, + 'tenant' => 'ten_121673955bd7aa144de5a8f6c262', + 'total_platform_fee' => 350, + 'three_d_secure_status' => null + ]; + return array_merge($base, $attributes); + } + + private function mockListResponse($data, $url) + { + return [ + 'object' => 'list', + 'data' => $data, + 'has_more' => false, + 'url' => $url, + 'count' => count($data), + ]; + } + public function testUrls() { $this->assertSame(Charge::classUrl(), '/v1/charges'); - $charge = new Charge('abcd/efgh'); - $this->assertSame($charge->instanceUrl(), '/v1/charges/abcd%2Fefgh'); + $charge = Charge::constructFrom(['id' => 'ch_abcd/efgh'], new Util\RequestOptions()); + $this->assertSame($charge->instanceUrl(), '/v1/charges/ch_abcd%2Fefgh'); } - //POST /charges public function testCreate() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => '05', - 'exp_year' => date('Y') + 1, - 'cvc' => '123', - ] - ]; + $this->setUpMockRequest(); + $tokenId = 'tok_create_test'; + $chargeId = 'ch_create_test'; + $createAmount = 100; - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); + $mockToken = $this->mockTokenData($tokenId); + $mockCharge = $this->mockChargeData($chargeId, ['amount' => $createAmount, 'card' => $mockToken['card']]); - $c = Charge::create([ - 'amount' => 100, + $chargeParams = [ + 'amount' => $createAmount, 'currency' => self::CURRENCY, - 'card' => $card->id, - ]); + 'card' => $tokenId, + ]; + $this->mockRequest('POST', '/v1/charges', $chargeParams, $mockCharge); + + $c = Charge::create($chargeParams); + + $this->assertInstanceOf(Charge::class, $c); + $this->assertSame($chargeId, $c->id); $this->assertTrue($c->paid); $this->assertFalse($c->refunded); + $this->assertSame($createAmount, $c->amount); + $this->assertInstanceOf(Card::class, $c->card); + $this->assertSame($mockToken['card']['id'], $c->card->id); } - //POST /charges public function testIdempotentCreate() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 5, - 'exp_year' => date('Y') + 1, - 'cvc' => '123', - ] - ]; + $this->setUpMockRequest(); + $tokenId = 'tok_idempotent_test'; + $chargeId = 'ch_idempotent_test'; + $idempotencyKey = self::generateRandomString(); - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); + $mockToken = $this->mockTokenData($tokenId); + $mockCharge = $this->mockChargeData($chargeId, ['card' => $mockToken['card']]); - $c = Charge::create([ + $chargeParams = [ 'amount' => 100, 'currency' => self::CURRENCY, - 'card' => $card->id - ], [ - 'idempotency_key' => self::generateRandomString(), - ]); + 'card' => $tokenId + ]; + $this->mockRequest('POST', '/v1/charges', $chargeParams, $mockCharge); + $c = Charge::create($chargeParams, ['idempotency_key' => $idempotencyKey]); + + $this->assertInstanceOf(Charge::class, $c); + $this->assertSame($chargeId, $c->id); $this->assertTrue($c->paid); - $this->assertFalse($c->refunded); } - //GET /charges/:id public function testRetrieve() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 5, - 'exp_year' => date('Y') + 1, - 'cvc' => '123', - ] - ]; + $this->setUpMockRequest(); + $chargeId = 'ch_retrieve_test'; + $mockCharge = $this->mockChargeData($chargeId); - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); + $this->mockRequest('GET', '/v1/charges/' . $chargeId, [], $mockCharge); - $c = Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id - ]); - $d = Charge::retrieve($c->id); - $this->assertSame($d->id, $c->id); + $d = Charge::retrieve($chargeId); + + $this->assertInstanceOf(Charge::class, $d); + $this->assertSame($chargeId, $d->id); + $this->assertSame($mockCharge['amount'], $d->amount); + $this->assertInstanceOf(Card::class, $d->card); + $this->assertSame($mockCharge['card']['id'], $d->card->id); } - //GET /charges/ public function testAll() { - self::authorizeFromEnv(); - - $charges = Charge::all([ - 'limit' => 3, - 'offset' => 10 - ]); - - $planID = 'gold-' . self::randomString(); - self::retrieveOrCreatePlan($planID); - - $customer = self::createTestCustomer(); - - $charge = Charge::create([ - 'amount' => 1000, - 'currency' => self::CURRENCY, - 'customer' => $customer->id - ]); - - $charges_2 = Charge::all([ - 'customer' => $customer->id - ]); - - $this->assertSame(1, count($charges_2['data'])); - $this->assertSame($charge->id, $charges_2['data'][0]->id); - - $charge_2 = Charge::create([ - 'amount' => 1500, - 'currency' => self::CURRENCY, - 'customer' => $customer->id - ]); - - $charges_3 = Charge::all([ - 'limit' => 2, - 'offset' => 0, - 'customer' => $customer->id - ]); - - $this->assertSame(2, count($charges_3['data'])); + $this->setUpMockRequest(); + $chargeId1 = 'ch_all_1'; + $chargeId2 = 'ch_all_2'; + $customerId = 'cus_all_test'; + + $mockCharge1 = $this->mockChargeData($chargeId1, ['customer' => $customerId]); + $mockCharge2 = $this->mockChargeData($chargeId2, ['customer' => $customerId, 'amount' => 1500]); + + $mockList1 = $this->mockListResponse([], '/v1/charges'); + $this->mockRequest('GET', '/v1/charges', ['limit' => 3, 'offset' => 10], $mockList1); + $charges = Charge::all(['limit' => 3, 'offset' => 10]); + $this->assertInstanceOf(Collection::class, $charges); + $this->assertCount(0, $charges->data); + + $mockList2 = $this->mockListResponse([$mockCharge1], '/v1/charges'); + $this->mockRequest('GET', '/v1/charges', ['customer' => $customerId], $mockList2); + $charges_2 = Charge::all(['customer' => $customerId]); + $this->assertInstanceOf(Collection::class, $charges_2); + $this->assertCount(1, $charges_2->data); + $this->assertInstanceOf(Charge::class, $charges_2->data[0]); + $this->assertSame($chargeId1, $charges_2->data[0]->id); + + $mockList3 = $this->mockListResponse([$mockCharge2, $mockCharge1], '/v1/charges'); + $this->mockRequest('GET', '/v1/charges', ['limit' => 2, 'offset' => 0, 'customer' => $customerId], $mockList3); + $charges_3 = Charge::all(['limit' => 2, 'offset' => 0, 'customer' => $customerId]); + $this->assertInstanceOf(Collection::class, $charges_3); + $this->assertCount(2, $charges_3->data); + $this->assertInstanceOf(Charge::class, $charges_3->data[0]); + $this->assertSame($chargeId2, $charges_3->data[0]->id); + $this->assertInstanceOf(Charge::class, $charges_3->data[1]); + $this->assertSame($chargeId1, $charges_3->data[1]->id); } - //POST /charges/:id public function testUpdateDescription() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 5, - 'exp_year' => date('Y') + 1, - 'cvc' => '123', - ] - ]; - - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - $charge = Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id - ]); - - $charge->description = 'foo bar'; - $charge->save(); - + $this->setUpMockRequest(); + $chargeId = 'ch_update_test'; + $originalDescription = 'Initial Desc'; + $updatedDescription = 'foo bar'; + + $mockChargeOriginal = $this->mockChargeData($chargeId, ['description' => $originalDescription]); + $mockChargeUpdated = $this->mockChargeData($chargeId, ['description' => $updatedDescription]); + + // Mock initial retrieve or creation (let's assume retrieve) + $this->mockRequest('GET', '/v1/charges/' . $chargeId, [], $mockChargeOriginal); + $charge = Charge::retrieve($chargeId); + $this->assertSame($originalDescription, $charge->description); + + // Mock save (POST request) + $saveParams = ['description' => $updatedDescription]; + $this->mockRequest('POST', '/v1/charges/' . $chargeId, $saveParams, $mockChargeUpdated); + $charge->description = $updatedDescription; + $savedCharge = $charge->save(); + + $this->assertInstanceOf(Charge::class, $savedCharge); + $this->assertSame($updatedDescription, $savedCharge->description); + // Check original object was updated + $this->assertSame($updatedDescription, $charge->description); + + // Mock subsequent retrieve to confirm persistence + $this->mockRequest('GET', '/v1/charges/' . $chargeId, [], $mockChargeUpdated); $updatedCharge = Charge::retrieve($charge->id); - $this->assertSame('foo bar', $updatedCharge->description); - $this->assertSame('foo bar', $charge->description); + $this->assertSame($updatedDescription, $updatedCharge->description); } - //POST /charges/:id/capture public function testCaptureAll() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 5, - 'exp_year' => date('Y') + 1, - 'cvc' => '123', - ] - ]; - - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - $charge = Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id, - 'capture' => false - ]); + $this->setUpMockRequest(); + $chargeId = 'ch_capture_all_test'; + $mockChargeUncaptured = $this->mockChargeData($chargeId, ['captured' => false, 'paid' => false, 'paid_at' => null]); + $mockChargeCaptured = $this->mockChargeData($chargeId, ['captured' => true, 'paid' => true, 'paid_at' => time()]); + $this->mockRequest('GET', '/v1/charges/' . $chargeId, [], $mockChargeUncaptured); + $charge = Charge::retrieve($chargeId); $this->assertFalse($charge->captured); + $captureUrl = '/v1/charges/' . $chargeId . '/capture'; + $this->mockRequest('POST', $captureUrl, [], $mockChargeCaptured); $capturedCharge = $charge->capture(); - $this->assertTrue($charge->captured); + + $this->assertInstanceOf(Charge::class, $capturedCharge); $this->assertTrue($capturedCharge->captured); + $this->assertTrue($charge->captured); } - //POST /charges/:id/capture public function testCapturePart() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 5, - 'exp_year' => date('Y') + 1, - 'cvc' => '123', - ] - ]; - - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - $charge = Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id, - 'capture' => false - ]); - + $this->setUpMockRequest(); + $chargeId = 'ch_capture_part_test'; + $captureAmount = 50; + $mockChargeUncaptured = $this->mockChargeData($chargeId, ['amount' => 100, 'captured' => false, 'paid' => false, 'paid_at' => null]); + $mockChargeCaptured = $this->mockChargeData($chargeId, ['amount' => 100, 'captured' => true, 'paid' => true, 'paid_at' => time()]); + + $this->mockRequest('GET', '/v1/charges/' . $chargeId, [], $mockChargeUncaptured); + $charge = Charge::retrieve($chargeId); $this->assertFalse($charge->captured); - $capturedCharge = $charge->capture(array('amount'=>50)); + $captureUrl = '/v1/charges/' . $chargeId . '/capture'; + $captureParams = ['amount' => $captureAmount]; + $this->mockRequest('POST', $captureUrl, $captureParams, $mockChargeCaptured); + $capturedCharge = $charge->capture($captureParams); + + $this->assertInstanceOf(Charge::class, $capturedCharge); $this->assertTrue($capturedCharge->captured); $this->assertTrue($charge->captured); + $this->assertSame(100, $capturedCharge->amount); } - //POST /charges/:id/refund public function testRefund() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 5, - 'exp_year' => date('Y') + 1, - 'cvc' => '123', - ] - ]; - - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - $charge = Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id, + $this->setUpMockRequest(); + $chargeId = 'ch_refund_test'; + $initialAmount = 100; + $refundAmount1 = 50; + $refundReason1 = 'foo bar 1'; + $refundReason2 = 'foo bar 2'; + + $mockChargeInitial = $this->mockChargeData($chargeId, ['amount' => $initialAmount]); + $mockChargeRefunded1 = $this->mockChargeData($chargeId, [ + 'amount' => $initialAmount, + 'refunded' => true, + 'amount_refunded' => $refundAmount1, + 'refund_reason' => $refundReason1 + ]); + $mockChargeRefunded2 = $this->mockChargeData($chargeId, [ + 'amount' => $initialAmount, + 'refunded' => true, + 'amount_refunded' => $initialAmount, + 'refund_reason' => $refundReason2 ]); + $this->mockRequest('GET', '/v1/charges/' . $chargeId, [], $mockChargeInitial); + $charge = Charge::retrieve($chargeId); $this->assertTrue($charge->captured); + $this->assertFalse($charge->refunded); - $redundChargePart = $charge->refund([ - 'amount' => 50, - 'refund_reason' => 'foo bar 1' - ]); + $refundUrl = '/v1/charges/' . $chargeId . '/refund'; + $refundParams1 = ['amount' => $refundAmount1, 'refund_reason' => $refundReason1]; + $this->mockRequest('POST', $refundUrl, $refundParams1, $mockChargeRefunded1); + $refundedChargePart = $charge->refund($refundParams1); - $this->assertTrue($redundChargePart->refunded); - $this->assertSame('foo bar 1', $redundChargePart->refund_reason); - $this->assertSame(50, $redundChargePart->amount_refunded); + $this->assertInstanceOf(Charge::class, $refundedChargePart); + $this->assertTrue($refundedChargePart->refunded); + $this->assertSame($refundReason1, $refundedChargePart->refund_reason); + $this->assertSame($refundAmount1, $refundedChargePart->amount_refunded); $this->assertTrue($charge->refunded); - $this->assertSame('foo bar 1', $charge->refund_reason); - $this->assertSame(50, $charge->amount_refunded); + $this->assertSame($refundReason1, $charge->refund_reason); + $this->assertSame($refundAmount1, $charge->amount_refunded); - $refundChargeAll = $charge->refund([ - 'refund_reason' => 'foo bar 2' - ]); + $refundParams2 = ['refund_reason' => $refundReason2]; + $this->mockRequest('POST', $refundUrl, $refundParams2, $mockChargeRefunded2); + $refundedChargeAll = $charge->refund($refundParams2); - $this->assertTrue($refundChargeAll->refunded); - $this->assertSame('foo bar 2', $refundChargeAll->refund_reason); - $this->assertSame(100, $refundChargeAll->amount_refunded); + $this->assertInstanceOf(Charge::class, $refundedChargeAll); + $this->assertTrue($refundedChargeAll->refunded); + $this->assertSame($refundReason2, $refundedChargeAll->refund_reason); + $this->assertSame($initialAmount, $refundedChargeAll->amount_refunded); $this->assertTrue($charge->refunded); - $this->assertSame('foo bar 2', $charge->refund_reason); - $this->assertSame(100, $charge->amount_refunded); + $this->assertSame($refundReason2, $charge->refund_reason); + $this->assertSame($initialAmount, $charge->amount_refunded); } public function testInvalidCard() { - $this->expectException('\Payjp\Error\Card'); - - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424241', - 'exp_month' => 5, - 'exp_year' => date('Y') + 1, - 'cvc' => '123', + $this->setUpMockRequest(); + $tokenId = 'tok_invalid_card'; + $chargeParams = ['amount' => 100, 'currency' => self::CURRENCY, 'card' => $tokenId]; + + $mockErrorResponse = [ + 'error' => [ + 'code' => 'card_declined', + 'message' => 'The card was declined.', + 'param' => 'card', + 'status' => 402, + 'type' => 'card_error' ] ]; - - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id - ]); + $this->mock->expects($this->once()) + ->method('request') + ->with('post', 'https://api.pay.jp/v1/charges', $this->anything(), $chargeParams, false) + ->willReturn([json_encode($mockErrorResponse), 402]); + + try { + Charge::create($chargeParams); + $this->fail("Expected Payjp\Error\Card exception was not thrown."); + } catch (Error\Card $e) { + $this->assertSame(402, $e->getHttpStatus()); + $this->assertSame('card_declined', $e->getJsonBody()['error']['code']); + $this->assertSame('card', $e->param); + $this->assertNotFalse(strpos($e->getMessage(), 'The card was declined.')); + } } - public function testInvalidAddressZipTest() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4000000000000070', - 'exp_month' => '05', - 'exp_year' => (date('Y') + 1), - 'cvc' => '123', - ] - ]; - - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - $ch = Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id - ]); - + $this->setUpMockRequest(); + $tokenId = 'tok_invalid_zip'; + $chargeId = 'ch_invalid_zip'; + $chargeParams = ['amount' => 100, 'currency' => self::CURRENCY, 'card' => $tokenId]; + $mockCharge = $this->mockChargeData( + $chargeId, + [], + ['address_zip_check' => 'failed'] + ); + + $this->mockRequest('POST', '/v1/charges', $chargeParams, $mockCharge); + + $ch = Charge::create($chargeParams); + + $this->assertInstanceOf(Charge::class, $ch); + $this->assertInstanceOf(Card::class, $ch->card); $this->assertSame('failed', $ch->card->address_zip_check); } public function testInvalidCvcTest() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4000000000000100', - 'exp_month' => '05', - 'exp_year' => (date('Y') + 1), - 'cvc' => '123', - ] - ]; - - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - $ch = Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id - ]); - + $this->setUpMockRequest(); + $tokenId = 'tok_invalid_cvc'; + $chargeId = 'ch_invalid_cvc'; + $chargeParams = ['amount' => 100, 'currency' => self::CURRENCY, 'card' => $tokenId]; + $mockCharge = $this->mockChargeData( + $chargeId, + [], + ['cvc_check' => 'failed'] + ); + + $this->mockRequest('POST', '/v1/charges', $chargeParams, $mockCharge); + + $ch = Charge::create($chargeParams); + + $this->assertInstanceOf(Charge::class, $ch); + $this->assertInstanceOf(Card::class, $ch->card); $this->assertSame('failed', $ch->card->cvc_check); } public function testUnavailableCvcTest() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4000000000000150', - 'exp_month' => '05', - 'exp_year' => (date('Y') + 1), - 'cvc' => '123', - ] - ]; - - $card = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - $ch = Charge::create([ - 'amount' => 100, - 'currency' => self::CURRENCY, - 'card' => $card->id - ]); - + $this->setUpMockRequest(); + $tokenId = 'tok_unavail_cvc'; + $chargeId = 'ch_unavail_cvc'; + $chargeParams = ['amount' => 100, 'currency' => self::CURRENCY, 'card' => $tokenId]; + $mockCharge = $this->mockChargeData( + $chargeId, + [], + ['cvc_check' => 'unavailable'] + ); + + $this->mockRequest('POST', '/v1/charges', $chargeParams, $mockCharge); + + $ch = Charge::create($chargeParams); + + $this->assertInstanceOf(Charge::class, $ch); + $this->assertInstanceOf(Card::class, $ch->card); $this->assertSame('unavailable', $ch->card->cvc_check); } } diff --git a/tests/CustomerTest.php b/tests/CustomerTest.php index dcc9fde..df59015 100644 --- a/tests/CustomerTest.php +++ b/tests/CustomerTest.php @@ -4,30 +4,108 @@ class CustomerTest extends TestCase { - // POST/customers + private function mockCustomerData($id = 'cus_test_1', $attributes = []) + { + $base = [ + 'id' => $id, + 'object' => 'customer', + 'email' => null, + 'description' => 'test', + 'livemode' => false, + 'default_card' => null, + 'cards' => [ + 'object' => 'list', + 'data' => [], + 'count' => 0, + 'has_more' => false, + 'url' => "/v1/customers/{$id}/cards" + ], + 'subscriptions' => [ + 'object' => 'list', + 'data' => [], + 'count' => 0, + 'has_more' => false, + 'url' => "/v1/customers/{$id}/subscriptions" + ], + 'created' => 1433127983, + 'metadata' => null, + ]; + return array_merge($base, $attributes); + } + + private function mockCardData($id = 'car_default', $attributes = []) + { + return array_merge([ + 'id' => $id, + 'object' => 'card', + 'brand' => 'Visa', + 'last4' => '4242', + 'exp_month' => 12, + 'exp_year' => date('Y') + 3, + 'name' => 'Test Card', + 'livemode' => false, + ], $attributes); + } + + private function mockSubscriptionData($id = 'sub_test_1', $customerId = 'cus_test_1', $planId = 'plan_test_1', $attributes = []) + { + return array_merge([ + 'id' => $id, + 'object' => 'subscription', + 'customer' => $customerId, + 'plan' => ['id' => $planId], + 'status' => 'active', + ], $attributes); + } + + private function mockChargeData($id = 'ch_test_1', $customerId = 'cus_test_1', $attributes = []) + { + return array_merge([ + 'id' => $id, + 'object' => 'charge', + 'amount' => 1000, + 'currency' => self::CURRENCY, + 'customer' => $customerId, + ], $attributes); + } + public function testCreate() { $attribute = [ - 'email' => 'gdb@pay.jp', + 'email' => 'example@pay.jp', 'description' => 'foo bar' ]; - - $customer = self::createTestCustomer($attribute); + $mockCustomerData = $this->mockCustomerData('cus_created', $attribute); + $this->mockRequest('POST', '/v1/customers', $attribute, $mockCustomerData); + $customer = Customer::create($attribute); $this->assertSame($attribute['email'], $customer->email); $this->assertSame($attribute['description'], $customer->description); } - // GET/customers/:id public function testRetrieve() { - $customer = self::createTestCustomer(); - $retrieve_customer = Customer::retrieve($customer->id); - $this->assertSame($customer->id, $retrieve_customer->id); + $mockCustomerData = $this->mockCustomerData(); + $this->mockRequest('GET', '/v1/customers/cus_test_1', [], $mockCustomerData); + $customer = Customer::retrieve('cus_test_1'); + $this->assertSame('cus_test_1', $customer->id); } - // GET/customers/ public function testAll() { + $mockCustomerData1 = $this->mockCustomerData('cus_test_1'); + $mockCustomerData2 = $this->mockCustomerData('cus_test_2'); + $mockCustomerData3 = $this->mockCustomerData('cus_test_3'); + $mockCustomersResponse = [ + 'object' => 'list', + 'data' => [ + $mockCustomerData1, + $mockCustomerData2, + $mockCustomerData3, + ], + 'has_more' => false, + 'url' => '/v1/customers' + ]; + $this->mockRequest('GET', '/v1/customers', ['limit' => 3, 'offset' => 10], $mockCustomersResponse); $customers = Customer::all([ 'limit' => 3, 'offset' => 10 @@ -35,55 +113,84 @@ public function testAll() $this->assertCount(3, $customers['data']); } - // DELETE/customers/:id public function testDeletion() { - $customer = self::createTestCustomer(); - $id = $customer->id; - + $mockCustomerData = $this->mockCustomerData('cus_test_1'); + $mockDeletedData = $this->mockCustomerData('cus_test_1', [ + 'deleted' => true, + 'livemode' => false, + 'default_card' => null + ]); + $customer = Customer::constructFrom($mockCustomerData, new \Payjp\Util\RequestOptions(self::API_KEY)); + $this->mockRequest('DELETE', '/v1/customers/cus_test_1', [], $mockDeletedData); $delete_customer = $customer->delete(); - - $this->assertSame($id, $customer->id); - $this->assertFalse($customer->livemode); - $this->assertTrue($customer->deleted); - - $this->assertNull($customer['active_card']); - - $this->assertSame($id, $delete_customer->id); + $this->assertSame($customer->id, $delete_customer->id); $this->assertFalse($delete_customer->livemode); $this->assertTrue($delete_customer->deleted); - - $this->assertNull($delete_customer['active_card']); + $this->assertNull($delete_customer['default_card']); } - //POST /customers/:id public function testSave() { - $customer = self::createTestCustomer(); - - $customer->email = 'gdb@pay.jp'; - $customer->save(); - $this->assertSame($customer->email, 'gdb@pay.jp'); - - $payjpCustomer = Customer::retrieve($customer->id); - $this->assertSame($customer->email, $payjpCustomer->email); - $this->assertSame('gdb@pay.jp', $customer->email); - - Payjp::setApiKey(null); - $customer = Customer::create(null, self::API_KEY); - $customer->email = 'gdb@pay.jp'; + $mockCustomerData = $this->mockCustomerData('cus_test_1'); + $updatedData = $this->mockCustomerData('cus_test_1', ['email' => 'example@pay.jp']); + $mock = $this->setUpMockRequest(); + $mock->expects($this->exactly(2)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'post', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + ['email' => 'example@pay.jp'], + false + ] + ) + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode($updatedData), 200] + ); + $customer = Customer::retrieve('cus_test_1'); + $customer->email = 'example@pay.jp'; $customer->save(); - - self::authorizeFromEnv(); - $updatedCustomer = Customer::retrieve($customer->id); - $this->assertSame($updatedCustomer->email, 'gdb@pay.jp'); - $this->assertSame('gdb@pay.jp', $customer->email); + $this->assertSame('example@pay.jp', $customer->email); } public function testBogusAttribute() { $this->expectException('\Payjp\Error\InvalidRequest'); - $customer = self::createTestCustomer(); + $mockCustomerData = $this->mockCustomerData('cus_test_1'); + $mock = $this->setUpMockRequest(); + $mock->expects($this->exactly(2)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'post', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + ['bogus' => 'bogus'], + false + ] + ) + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode(['error' => ['message' => 'Invalid request']]), 400] + ); + $customer = Customer::retrieve('cus_test_1'); $customer->bogus = 'bogus'; $customer->save(); } @@ -91,190 +198,337 @@ public function testBogusAttribute() public function testUpdateDescriptionEmpty() { $this->expectException('\InvalidArgumentException'); - $customer = self::createTestCustomer(); + $mockCustomerData = $this->mockCustomerData('cus_test_1', ['description' => '123']); + $mock = $this->setUpMockRequest(); + $mock->expects($this->once()) + ->method('request') + ->with( + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ) + ->willReturn([json_encode($mockCustomerData), 200]); + $customer = Customer::retrieve('cus_test_1'); $customer->description = ''; - $customer->save(); - - $updatedCustomer = Customer::retrieve($customer->id); - $this->assertSame('123', $updatedCustomer->description); } public function testUpdateDescriptionNull() { - $customer = self::createTestCustomer(['description' => 'foo bar']); + $mockCustomerData = $this->mockCustomerData('cus_test_1', ['description' => 'foo bar']); + $updatedData = $this->mockCustomerData('cus_test_1', ['description' => '']); + $mock = $this->setUpMockRequest(); + $mock->expects($this->exactly(2)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'post', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + ['description' => null], + false + ] + ) + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode($updatedData), 200] + ); + $customer = Customer::retrieve('cus_test_1'); $customer->description = null; - $customer->save(); - - $updatedCustomer = Customer::retrieve($customer->id); - $this->assertSame('', $updatedCustomer->description); $this->assertSame('', $customer->description); } - //POST /customers/:id/cards - //GET /customers/:id/cards public function testCustomerAddCard() { - $customer = $this->createTestCustomer(); - - $defaultCard = $customer->cards->data[0]; - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 12, - 'exp_year' => date('Y') + 3, - 'cvc' => '314' - ] + $mockCustomerData = $this->mockCustomerData('cus_test_1'); + $newCard1Data = $this->mockCardData('car_1'); + $newCard2Data = $this->mockCardData('car_2'); + $mockCardsResponse = [ + 'object' => 'list', + 'data' => [ $newCard2Data, $newCard1Data, $this->mockCardData('car_default') ], + 'has_more' => false, + 'url' => '/v1/customers/cus_test_1/cards' ]; - - $token = Token::create($params, ['payjp_direct_token_generate' => 'true']); - - $params2 = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 7, - 'exp_year' => date('Y') + 3, - 'cvc' => '314' - ] - ]; - - $card = Token::create($params2, ['payjp_direct_token_generate' => 'true']); - - $createdCard = $customer->cards->create(['card' => $token->id]); - $createdCard_2 = $customer->cards->create(['card' => $card->id]); - - $updatedCustomer = Customer::retrieve($customer->id); - $cardList = $updatedCustomer->cards->all(); - $this->assertSame(count($cardList['data']), 3); - - $this->assertSame($createdCard_2->id, $cardList['data'][0]->id); - $this->assertSame($createdCard->id, $cardList['data'][1]->id); - $this->assertSame($defaultCard->id, $cardList['data'][2]->id); - - $card = $customer->cards->retrieve($cardList['data'][1]->id); - $this->assertSame($card->id, $cardList['data'][1]->id); - - $updatedCustomer = Customer::retrieve($customer->id); - $cardList = $updatedCustomer->cards->all([ - 'limit' => 1, - 'offset' => 1 - ]); - $this->assertSame(count($cardList['data']), 1); + $mock = $this->setUpMockRequest(); + $mock->expects($this->exactly(4)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'post', + 'https://api.pay.jp/v1/customers/cus_test_1/cards', + $this->anything(), + ['card' => 'tok_1'], + false + ], + [ + 'post', + 'https://api.pay.jp/v1/customers/cus_test_1/cards', + $this->anything(), + ['card' => 'tok_2'], + false + ], + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1/cards', + $this->anything(), + [], + false + ] + ) + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode($newCard1Data), 200], + [json_encode($newCard2Data), 200], + [json_encode($mockCardsResponse), 200] + ); + $customer = Customer::retrieve('cus_test_1'); + $createdCard = $customer->cards->create(['card' => 'tok_1']); + $createdCard_2 = $customer->cards->create(['card' => 'tok_2']); + $cardList = $customer->cards->all(); + $this->assertInstanceOf('Payjp\Collection', $cardList); + $this->assertTrue(is_array($cardList->data)); + $this->assertCount(3, $cardList->data); + $this->assertInstanceOf('Payjp\Card', $cardList->data[0]); + $this->assertSame($createdCard_2->id, $cardList->data[0]->id); + $this->assertInstanceOf('Payjp\Card', $cardList->data[1]); + $this->assertSame($createdCard->id, $cardList->data[1]->id); } - //POST /customers/:id/cards/:card_id public function testCustomerUpdateCard() { - $customer = $this->createTestCustomer(); - - $cards = $customer->cards->all(); - $this->assertSame(count($cards['data']), 1); - - $card = $cards['data'][0]; - $card->name = 'Littleorc'; - $card->save(); - - $updatedCustomer = Customer::retrieve($customer->id); - $cardList = $updatedCustomer->cards->all(); - $this->assertSame($cardList['data'][0]->name, 'Littleorc'); + $mockCustomerData = $this->mockCustomerData('cus_test_1'); + $cardData = $this->mockCardData('car_1', ['customer' => 'cus_test_1']); + $updatedCardData = $this->mockCardData('car_1', ['name' => 'Littleorc', 'customer' => 'cus_test_1']); + $mock = $this->setUpMockRequest(); + $mock->expects($this->exactly(3)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1/cards/car_1', + $this->anything(), + [], + false + ], + [ + 'post', + 'https://api.pay.jp/v1/customers/cus_test_1/cards/car_1', + $this->anything(), + ['name' => 'Littleorc'], + false + ] + ) + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode($cardData), 200], + [json_encode($updatedCardData), 200] + ); + $customer = Customer::retrieve('cus_test_1'); + $cardObj = $customer->cards->retrieve('car_1'); + $this->assertInstanceOf('Payjp\Card', $cardObj); + $cardObj->name = 'Littleorc'; + $cardObj->save(); + $this->assertSame('Littleorc', $cardObj->name); } public function testCustomerDeleteCard() { - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 7, - 'exp_year' => date('Y') + 3, - 'cvc' => '314' - ] + $mockCustomerData = $this->mockCustomerData('cus_test_1'); + $createdCardData = $this->mockCardData('car_1', ['customer' => 'cus_test_1']); + $deletedCardData = $this->mockCardData('car_1', ['deleted' => true, 'customer' => 'cus_test_1']); + $mockCardsResponse = [ + 'object' => 'list', + 'data' => [ $this->mockCardData('car_default'), $createdCardData ], + 'has_more' => false, + 'url' => '/v1/customers/cus_test_1/cards' ]; - - $token = Token::create($params, $options = ['payjp_direct_token_generate' => 'true']); - - $customer = $this->createTestCustomer(); - $createdCard = $customer->cards->create(['card' => $token->id]); - - $updatedCustomer = Customer::retrieve($customer->id); - $cardList = $updatedCustomer->cards->all(); - $this->assertSame(count($cardList['data']), 2); - - $deleteStatus = $updatedCustomer->cards->retrieve($createdCard->id)->delete(); - $this->assertTrue($deleteStatus->deleted); - - $postDeleteCustomer = Customer::retrieve($customer->id); - $postDeleteCards = $postDeleteCustomer->cards->all(); - $this->assertSame(count($postDeleteCards['data']), 1); - - $cardList = $updatedCustomer->cards->all(); - $this->assertSame(count($cardList['data']), 1); + $mockCardsAfterResponse = [ + 'object' => 'list', + 'data' => [ $this->mockCardData('car_default') ], + 'has_more' => false, + 'url' => '/v1/customers/cus_test_1/cards' + ]; + $mock = $this->setUpMockRequest(); + $mock->expects($this->exactly(6)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'post', + 'https://api.pay.jp/v1/customers/cus_test_1/cards', + $this->anything(), + ['card' => 'tok_1'], + false + ], + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1/cards', + $this->anything(), + [], + false + ], + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1/cards/car_1', + $this->anything(), + [], + false + ], + [ + 'delete', + 'https://api.pay.jp/v1/customers/cus_test_1/cards/car_1', + $this->anything(), + [], + false + ], + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1/cards', + $this->anything(), + [], + false + ] + ) + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode($createdCardData), 200], + [json_encode($mockCardsResponse), 200], + [json_encode($createdCardData), 200], + [json_encode($deletedCardData), 200], + [json_encode($mockCardsAfterResponse), 200] + ); + $customer = Customer::retrieve('cus_test_1'); + $createdCardObj = $customer->cards->create(['card' => 'tok_1']); + $cardList = $customer->cards->all(); + $deletedCardResult = $customer->cards->retrieve('car_1')->delete(); + $this->assertInstanceOf('Payjp\Card', $deletedCardResult); + $postDeleteCards = $customer->cards->all(); + $this->assertInstanceOf('Payjp\Collection', $postDeleteCards); + $this->assertTrue(is_array($postDeleteCards->data)); + $this->assertCount(1, $postDeleteCards->data); + $this->assertTrue($deletedCardResult->deleted); } public function testCustomerSubscriptionAllRetrieve() { - $planID = 'gold-' . self::randomString(); - self::retrieveOrCreatePlan($planID); - - $customer = self::createTestCustomer(); - - $subscription = Subscription::create([ - 'customer' => $customer->id, - 'plan' => $planID - ]); - - $planID_2 = 'gold-2-' . self::randomString(); - self::retrieveOrCreatePlan($planID_2); - - $subscription_2 = Subscription::create([ - 'customer' => $customer->id, - 'plan' => $planID_2 - ]); - - $customerRetrive = Customer::retrieve($customer->id); - $subscriptions = $customerRetrive->subscriptions->all(); - - $this->assertSame($subscription_2->id, $subscriptions['data'][0]->id); - $this->assertSame($subscription->id, $subscriptions['data'][1]->id); - - $this->assertSame(2, count($subscriptions['data'])); - $this->assertSame($customer->id, $subscriptions['data'][0]->customer); - $this->assertSame($planID_2, $subscriptions['data'][0]->plan->id); - - $subscriptionRetrieve = $customerRetrive->subscriptions->retrieve($subscription->id); - $this->assertSame($subscription->id, $subscriptionRetrieve->id); - $this->assertSame($planID, $subscriptionRetrieve->plan->id); + $mockCustomerData = $this->mockCustomerData('cus_test_1'); + $sub1Data = $this->mockSubscriptionData('sub_1', 'cus_test_1', 'plan_1'); + $sub2Data = $this->mockSubscriptionData('sub_2', 'cus_test_1', 'plan_2'); + $mockSubsResponse = [ + 'object' => 'list', + 'data' => [ $sub2Data, $sub1Data ], + 'has_more' => false, + 'url' => '/v1/customers/cus_test_1/subscriptions' + ]; + $mock = $this->setUpMockRequest(); + $mock->expects($this->exactly(2)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1/subscriptions', + $this->anything(), + [], + false + ] + ) + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode($mockSubsResponse), 200] + ); + $customer = Customer::retrieve('cus_test_1'); + $subscriptions = $customer->subscriptions->all(); + $this->assertInstanceOf('Payjp\Collection', $subscriptions); + $this->assertTrue(is_array($subscriptions->data)); + $this->assertCount(2, $subscriptions->data); + $this->assertInstanceOf('Payjp\Subscription', $subscriptions->data[0]); + $this->assertSame($sub2Data['id'], $subscriptions->data[0]->id); + $this->assertInstanceOf('Payjp\Subscription', $subscriptions->data[1]); + $this->assertSame($sub1Data['id'], $subscriptions->data[1]->id); + $this->assertSame('cus_test_1', $subscriptions->data[0]->customer); + $this->assertTrue(is_object($subscriptions->data[0]->plan)); + $this->assertSame('plan_2', $subscriptions->data[0]->plan->id); } public function testCustomerChargeAll() { - $planID = 'gold-' . self::randomString(); - self::retrieveOrCreatePlan($planID); - - $customer = self::createTestCustomer(); - - $charge = Charge::create([ - 'amount' => 1000, - 'currency' => self::CURRENCY, - 'customer' => $customer->id - ]); - - $charges = $customer->charges(); - - $this->assertSame(1, count($charges['data'])); - $this->assertSame($charge->id, $charges['data'][0]->id); - - $charge_2 = Charge::create([ - 'amount' => 1500, - 'currency' => self::CURRENCY, - 'customer' => $customer->id - ]); - - $charges_2 = $customer->charges(); - - $this->assertSame(2, count($charges_2['data'])); - $this->assertSame($charge_2->id, $charges_2['data'][0]->id); - $this->assertSame($charge->id, $charges_2['data'][1]->id); + $mockCustomerData = $this->mockCustomerData('cus_test_1'); + $charge1Data = $this->mockChargeData('ch_1', 'cus_test_1'); + $charge2Data = $this->mockChargeData('ch_2', 'cus_test_1', ['amount' => 1500]); + $mockChargesResponse = [ + 'object' => 'list', + 'data' => [ $charge2Data, $charge1Data ], + 'has_more' => false, + 'url' => '/v1/charges' + ]; + $mock = $this->setUpMockRequest(); + $mock->expects($this->exactly(2)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'get', + 'https://api.pay.jp/v1/charges', + $this->anything(), + ['customer' => 'cus_test_1'], + false + ] + ) + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode($mockChargesResponse), 200] + ); + $customer = Customer::retrieve('cus_test_1'); + $charges = Charge::all(['customer' => $customer->id]); + $this->assertInstanceOf('Payjp\Collection', $charges); + $this->assertTrue(is_array($charges->data)); + $this->assertCount(2, $charges->data); + $this->assertInstanceOf('Payjp\Charge', $charges->data[0]); + $this->assertSame($charge2Data['id'], $charges->data[0]->id); + $this->assertInstanceOf('Payjp\Charge', $charges->data[1]); + $this->assertSame($charge1Data['id'], $charges->data[1]->id); } } diff --git a/tests/EventTest.php b/tests/EventTest.php index 45edc9a..5fcdc3a 100644 --- a/tests/EventTest.php +++ b/tests/EventTest.php @@ -7,17 +7,71 @@ class EventTest extends TestCase public function testAllRetrieve() { self::authorizeFromEnv(); - - $events = Event::all( - array( - 'limit' => 3, - 'offset' => 0 + $this->setUpMockRequest(); + + $mockEvent1Data = [ + 'id' => 'evnt_test_1', + 'object' => 'event', + 'type' => 'charge.succeeded', + 'data' => ['object' => 'charge', 'id' => 'ch_test_charge'], + 'created' => time(), + 'livemode' => false, + 'pending_webhooks' => 0, + ]; + $mockEvent2Data = [ + 'id' => 'evnt_test_2', + 'object' => 'event', + 'type' => 'customer.created', + 'data' => ['object' => 'customer', 'id' => 'cus_test_customer'], + 'created' => time() - 100, + 'livemode' => false, + 'pending_webhooks' => 0, + ]; + $mockEventsResponse = [ + 'object' => 'list', + 'data' => [ $mockEvent1Data, $mockEvent2Data ], + 'has_more' => false, + 'url' => '/v1/events' + ]; + $mockEventRetrieveData = $mockEvent1Data; + + $this->mock->expects($this->exactly(2)) + ->method('request') + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/events', + $this->anything(), + ['limit' => 3, 'offset' => 0], + false + ], + [ + 'get', + 'https://api.pay.jp/v1/events/evnt_test_1', + $this->anything(), + [], + false + ] ) - ); - - if (count($events['data'])) { - $event = Event::retrieve($events['data'][0]->id); - $this->assertSame($events['data'][0]->id, $event->id); - } + ->willReturnOnConsecutiveCalls( + [json_encode($mockEventsResponse), 200], + [json_encode($mockEventRetrieveData), 200] + ); + + $events = Event::all([ + 'limit' => 3, + 'offset' => 0 + ]); + + $this->assertInstanceOf('Payjp\Collection', $events); + $this->assertTrue(is_array($events->data)); + $this->assertCount(2, $events->data); + $this->assertInstanceOf('Payjp\Event', $events->data[0]); + $this->assertSame($mockEvent1Data['id'], $events->data[0]->id); + + $event = Event::retrieve($events->data[0]->id); + $this->assertInstanceOf('Payjp\Event', $event); + $this->assertSame($mockEvent1Data['id'], $event->id); + $this->assertSame($mockEvent1Data['type'], $event->type); } } diff --git a/tests/InvalidRequestErrorTest.php b/tests/InvalidRequestErrorTest.php index 9eaafe3..890a9d6 100644 --- a/tests/InvalidRequestErrorTest.php +++ b/tests/InvalidRequestErrorTest.php @@ -7,6 +7,10 @@ class InvalidRequestErrorTest extends TestCase public function testInvalidObject() { self::authorizeFromEnv(); + $mock = $this->setUpMockRequest(); + $mock->expects($this->once()) + ->method('request') + ->willThrowException(new Error\InvalidRequest('Invalid object', null, 404)); try { Customer::retrieve('invalid'); } catch (Error\InvalidRequest $e) { @@ -17,6 +21,10 @@ public function testInvalidObject() public function testBadData() { self::authorizeFromEnv(); + $mock = $this->setUpMockRequest(); + $mock->expects($this->once()) + ->method('request') + ->willThrowException(new Error\InvalidRequest('Bad data', null, 400)); try { Charge::create(); } catch (Error\InvalidRequest $e) { diff --git a/tests/PlanTest.php b/tests/PlanTest.php index 23e4946..b52418d 100644 --- a/tests/PlanTest.php +++ b/tests/PlanTest.php @@ -4,78 +4,175 @@ class PlanTest extends TestCase { + private function mockPlanData($id, $attributes = []) + { + $base = [ + 'amount' => 500, + 'billing_day' => null, + 'created' => 1433127983, + 'currency' => 'jpy', + 'id' => $id, + 'interval' => 'month', + 'livemode' => false, + 'metadata' => null, + 'name' => null, + 'object' => 'plan', + 'trial_days' => 30 + ]; + return array_merge($base, $attributes); + } + public function testCreateRetrieveAll() { - self::authorizeFromEnv(); - $planId = 'gold-' . self::randomString(); - $p = Plan::create([ + $this->setUpMockRequest(); + $planId1 = 'plan_test_1'; + $planId2 = 'plan_test_2'; + + $createParams1 = [ 'amount' => 2000, 'interval' => 'month', 'currency' => self::CURRENCY, - 'name' => 'Plan', - 'id' => $planId - ]); - - $plan_retrieve = Plan::retrieve($planId); - $this->assertSame($planId, $plan_retrieve->id); - - $planId_2 = 'foobar-2-' . self::randomString(); - $p_2 = Plan::create([ + 'name' => 'Plan 1', + 'id' => $planId1 + ]; + $createParams2 = [ 'amount' => 3000, 'interval' => 'month', 'currency' => self::CURRENCY, - 'name' => 'Plan_2', - 'id' => $planId_2 - ]); - - $plans = Plan::all([ - 'limit' => 2, - 'offset' => 0 - ]); - $this->assertSame(2, count($plans['data'])); + 'name' => 'Plan 2', + 'id' => $planId2 + ]; + + $mockPlan1Data = $this->mockPlanData($planId1, $createParams1); + $mockPlan2Data = $this->mockPlanData($planId2, $createParams2); + + $mockPlanListResponse = [ + 'object' => 'list', + 'data' => [$mockPlan2Data, $mockPlan1Data], + 'has_more' => false, + 'url' => '/v1/plans' + ]; + + // Mock the requests + $this->mockRequest('POST', '/v1/plans', $createParams1, $mockPlan1Data); + $plan1 = Plan::create($createParams1); + $this->assertInstanceOf('Payjp\Plan', $plan1); + $this->assertSame($planId1, $plan1->id); + $this->assertSame($createParams1['amount'], $plan1->amount); + + $this->mockRequest('GET', '/v1/plans/' . $planId1, [], $mockPlan1Data); + $plan_retrieve = Plan::retrieve($planId1); + $this->assertInstanceOf('Payjp\Plan', $plan_retrieve); + $this->assertSame($planId1, $plan_retrieve->id); + + $this->mockRequest('POST', '/v1/plans', $createParams2, $mockPlan2Data); + $plan2 = Plan::create($createParams2); + $this->assertInstanceOf('Payjp\Plan', $plan2); + $this->assertSame($planId2, $plan2->id); + + $this->mockRequest('GET', '/v1/plans', ['limit' => 2, 'offset' => 0], $mockPlanListResponse); + $plans = Plan::all(['limit' => 2, 'offset' => 0]); + $this->assertInstanceOf('Payjp\Collection', $plans); + $this->assertTrue(is_array($plans->data)); + $this->assertCount(2, $plans->data); + $this->assertInstanceOf('Payjp\Plan', $plans->data[0]); + $this->assertSame($planId2, $plans->data[0]->id); + $this->assertInstanceOf('Payjp\Plan', $plans->data[1]); + $this->assertSame($planId1, $plans->data[1]->id); } public function testDeletion() { - self::authorizeFromEnv(); - $planId = 'gold-' . self::randomString(); - $p = Plan::create([ + $this->setUpMockRequest(); + $planId = 'plan_to_delete'; + $createParams = [ 'amount' => 2000, 'interval' => 'month', 'currency' => self::CURRENCY, - 'name' => 'Plan', + 'name' => 'PlanToDelete', 'id' => $planId - ]); - $p->delete(); - $this->assertTrue($p->deleted); + ]; + $mockPlanData = $this->mockPlanData($planId, $createParams); + $mockDeletedPlanData = array_merge($mockPlanData, ['deleted' => true]); + + $this->mockRequest('POST', '/v1/plans', $createParams, $mockPlanData); + $p = Plan::create($createParams); + $this->assertInstanceOf('Payjp\Plan', $p); $this->assertSame($planId, $p->id); + + $this->mockRequest('DELETE', '/v1/plans/' . $planId, [], $mockDeletedPlanData); + $deletedPlan = $p->delete(); + + $this->assertInstanceOf('Payjp\Plan', $deletedPlan); + $this->assertTrue($deletedPlan->deleted); + $this->assertSame($planId, $deletedPlan->id); } public function testFalseyId() { + $this->setUpMockRequest(); + $planId = '0'; + $mockErrorResponse = [ + 'error' => [ + 'message' => 'No such plan: ' . $planId, + 'param' => 'id', + 'status' => 404, + 'type' => 'invalid_request_error' + ] + ]; + + $this->mock->expects($this->once()) + ->method('request') + ->with( + 'get', + 'https://api.pay.jp/v1/plans/' . $planId, + $this->anything(), + [], + false + ) + ->willReturn([json_encode($mockErrorResponse), 404]); + try { - Plan::retrieve('0'); + Plan::retrieve($planId); + $this->fail("Expected Payjp\Error\InvalidRequest exception was not thrown."); } catch (Error\InvalidRequest $e) { - $this->assertSame(404, $e->httpStatus); + $this->assertSame(404, $e->getHttpStatus()); + $this->assertNotFalse(strpos($e->getMessage(), 'No such plan')); } } public function testSave() { - self::authorizeFromEnv(); - $planId = 'gold-' . self::randomString(); - $p = Plan::create([ + $this->setUpMockRequest(); + $planId = 'plan_to_save'; + $createParams = [ 'amount' => 2000, 'interval' => 'month', 'currency' => self::CURRENCY, - 'name' => 'Plan', + 'name' => 'Original Name', 'id' => $planId - ]); - $p->name = 'A new plan name'; - $p->save(); - $this->assertSame($p->name, 'A new plan name'); + ]; + $mockPlanData = $this->mockPlanData($planId, $createParams); + $updatedName = 'A new plan name'; + $saveParams = ['name' => $updatedName]; + $mockUpdatedPlanData = array_merge($mockPlanData, $saveParams); + + $this->mockRequest('POST', '/v1/plans', $createParams, $mockPlanData); + $p = Plan::create($createParams); + $this->assertInstanceOf('Payjp\Plan', $p); + $this->assertSame($createParams['name'], $p->name); + + $this->mockRequest('POST', '/v1/plans/' . $planId, $saveParams, $mockUpdatedPlanData); + $p->name = $updatedName; + $savedPlan = $p->save(); + + $this->assertInstanceOf('Payjp\Plan', $savedPlan); + $this->assertSame($updatedName, $savedPlan->name); + $this->assertSame($updatedName, $p->name); - $payjpPlan = Plan::retrieve($planId); - $this->assertSame($p->name, $payjpPlan->name); + $this->mockRequest('GET', '/v1/plans/' . $planId, [], $mockUpdatedPlanData); + $retrievedPlan = Plan::retrieve($planId); + $this->assertInstanceOf('Payjp\Plan', $retrievedPlan); + $this->assertSame($updatedName, $retrievedPlan->name); } } diff --git a/tests/SubscriptionTest.php b/tests/SubscriptionTest.php index c403c96..52ccd09 100644 --- a/tests/SubscriptionTest.php +++ b/tests/SubscriptionTest.php @@ -4,121 +4,226 @@ class SubscriptionTest extends TestCase { - - public function testCreateRetrieveUpdatePauseResumeCancelDelete() + private function mockCustomerData($id = 'cus_test_sub', $attributes = []) { - $planID = 'gold-' . self::randomString(); - self::retrieveOrCreatePlan($planID); - $nextPlanID = 'next-plan-for-sdk-test'; - self::retrieveOrCreatePlan($nextPlanID); - - $customer = self::createTestCustomer(); + $base = [ + 'id' => $id, + 'object' => 'customer', + 'email' => null, + 'description' => 'test', + 'livemode' => false, + 'default_card' => null, + 'cards' => [ + 'object' => 'list', + 'data' => [], + 'count' => 0, + 'has_more' => false, + 'url' => "/v1/customers/{$id}/cards" + ], + 'subscriptions' => [ + 'object' => 'list', + 'data' => [], + 'count' => 0, + 'has_more' => false, + 'url' => "/v1/customers/{$id}/subscriptions" + ], + 'created' => 1433127983, + 'metadata' => null, + ]; + return array_merge($base, $attributes); + } - $sub = Subscription::create( - array( - 'customer' => $customer->id, - 'plan' => $planID - ) - ); + private function mockPlanData($id = 'pln_test_1', $attributes = []) + { + $base = [ + 'amount' => 500, + 'billing_day' => null, + 'created' => 1433127983, + 'currency' => 'jpy', + 'id' => $id, + 'interval' => 'month', + 'livemode' => false, + 'metadata' => null, + 'name' => null, + 'object' => 'plan', + 'trial_days' => 30 + ]; + return array_merge($base, $attributes); + } - $this->assertSame($sub->status, 'active'); - $this->assertSame($sub->next_cycle_plan, null); - $this->assertSame($sub->trial_end, null); - $this->assertSame($sub->plan->id, $planID); + private function mockSubscriptionData($id = 'sub_test_1', $customerId = 'cus_test_sub', $planId = 'pln_test_1', $attributes = []) + { + $base = [ + 'id' => $id, + 'object' => 'subscription', + 'livemode' => false, + 'status' => 'active', + 'canceled_at' => null, + 'created' => 1433127983, + 'current_period_end' => 1435732422, + 'current_period_start' => 1433140422, + 'customer' => $customerId, + 'metadata' => null, + 'next_cycle_plan' => null, + 'paused_at' => null, + 'plan' => [ + 'amount' => 1000, + 'billing_day' => null, + 'created' => 1432965397, + 'currency' => 'jpy', + 'id' => $planId, + 'livemode' => false, + 'metadata' => [], + 'interval' => 'month', + 'name' => 'test plan', + 'object' => 'plan', + 'trial_days' => 0 + ], + 'resumed_at' => null, + 'start' => 1433140422, + 'trial_end' => null, + 'trial_start' => null, + 'prorate' => false + ]; + $merged = array_merge($base, $attributes); + if (isset($attributes['plan']) && is_array($attributes['plan'])) { + $merged['plan'] = array_merge($base['plan'], $attributes['plan']); + } + return $merged; + } + public function testCreateRetrieveUpdatePauseResumeCancelDelete() + { + $planID = 'pln_gold_test'; + $nextPlanID = 'pln_next_test'; + $customerID = 'cus_for_sub_test'; + $subID = 'sub_test_lifecycle'; + + $mockCustomer = $this->mockCustomerData($customerID); + $mockPlan = $this->mockPlanData($planID); + $mockNextPlan = $this->mockPlanData($nextPlanID); + + $createParams = ['customer' => $customerID, 'plan' => $planID]; + $mockInitialSub = $this->mockSubscriptionData($subID, $customerID, $planID); + $this->mockRequest('POST', '/v1/subscriptions', $createParams, $mockInitialSub); + $sub = Subscription::create($createParams); + + $this->assertInstanceOf('Payjp\Subscription', $sub); + $this->assertSame($subID, $sub->id); + $this->assertSame('active', $sub->status); + $this->assertSame($planID, $sub->plan->id); + $this->assertNull($sub->next_cycle_plan); + + $updateParams1 = ['next_cycle_plan' => $nextPlanID]; + $mockUpdatedSub1 = $this->mockSubscriptionData($subID, $customerID, $planID, ['next_cycle_plan' => $mockNextPlan]); + $this->mockRequest('POST', "/v1/subscriptions/{$subID}", $updateParams1, $mockUpdatedSub1); $sub->next_cycle_plan = $nextPlanID; $sub->save(); - $this->assertSame($sub->next_cycle_plan->id, $nextPlanID); + $this->assertInstanceOf('Payjp\Plan', $sub->next_cycle_plan); + $this->assertSame($nextPlanID, $sub->next_cycle_plan->id); + + $updateParams2 = ['next_cycle_plan' => null]; + $mockUpdatedSub2 = $this->mockSubscriptionData($subID, $customerID, $planID, ['next_cycle_plan' => null]); + $this->mockRequest('POST', "/v1/subscriptions/{$subID}", $updateParams2, $mockUpdatedSub2); $sub->next_cycle_plan = null; $sub->save(); - $this->assertSame($sub->next_cycle_plan, null); + $this->assertNull($sub->next_cycle_plan); + $this->mockRequest('GET', "/v1/subscriptions/{$subID}", [], $mockUpdatedSub2); $sub_retrieve = Subscription::retrieve($sub->id); + $this->assertSame($subID, $sub_retrieve->id); + $this->assertSame('active', $sub_retrieve->status); + $mockPausedSub = $this->mockSubscriptionData($subID, $customerID, $planID, ['status' => 'paused', 'paused_at' => time()]); + $this->mockRequest('POST', "/v1/subscriptions/{$subID}/pause", [], $mockPausedSub); $sub->pause(); + $this->assertSame('paused', $sub->status); + $this->assertNotNull($sub->paused_at); - $sub_pause = Subscription::retrieve($sub->id); - $this->assertSame($sub_pause->status, 'paused'); + $this->mockRequest('GET', "/v1/subscriptions/{$subID}", [], $mockPausedSub); + $sub_pause_retrieved = Subscription::retrieve($sub->id); + $this->assertSame('paused', $sub_pause_retrieved->status); + $mockResumedSub1 = $this->mockSubscriptionData($subID, $customerID, $planID, ['status' => 'active', 'paused_at' => null, 'resumed_at' => time()]); + $this->mockRequest('POST', "/v1/subscriptions/{$subID}/resume", [], $mockResumedSub1); $sub->resume(); - $sub_resume = Subscription::retrieve($sub->id); - $this->assertSame($sub_resume->status, 'active'); - - $resuumed_at = $sub->created + 10000; + $this->assertSame('active', $sub->status); + $this->assertNull($sub->paused_at); + $this->assertNotNull($sub->resumed_at); + $mockPausedSub2 = $this->mockSubscriptionData($subID, $customerID, $planID, ['status' => 'paused', 'paused_at' => time() + 100, 'resumed_at' => $sub->resumed_at]); + $this->mockRequest('POST', "/v1/subscriptions/{$subID}/pause", [], $mockPausedSub2); $sub->pause(); - - $sub_pause = Subscription::retrieve($sub->id); - $this->assertSame($sub_pause->status, 'paused'); - $this->assertSame(null, $sub->resumed_at); - - $trial_end = $sub->created + 5000; - - $sub->resume( - array( - 'trial_end' => $trial_end - ) - ); - - $sub_resume = Subscription::retrieve($sub->id); - $this->assertSame($sub_resume->status, 'trial'); - $this->assertSame(500, $sub->plan->amount); - $this->assertSame($trial_end, $sub->trial_end); - - try { - $sub->cancel( - array( - 'foo' => "bar" - ) - ); - } catch (Error\InvalidRequest $e) { - $actual = $e->getJsonBody(); - - $this->assertSame('Invalid param key to subscription', $actual['error']['message']); - } - + $this->assertSame('paused', $sub->status); + + $trial_end_ts = time() + 5000; + $resumeParams = ['trial_end' => $trial_end_ts]; + $mockResumedTrialSub = $this->mockSubscriptionData($subID, $customerID, $planID, [ + 'status' => 'trial', + 'paused_at' => null, + 'resumed_at' => time(), + 'trial_end' => $trial_end_ts, + 'trial_start' => time() + ]); + $this->mockRequest('POST', "/v1/subscriptions/{$subID}/resume", $resumeParams, $mockResumedTrialSub); + $sub->resume($resumeParams); + $this->assertSame('trial', $sub->status); + $this->assertSame($trial_end_ts, $sub->trial_end); + $this->assertNotNull($sub->trial_start); + + $mockCanceledSub = $this->mockSubscriptionData($subID, $customerID, $planID, ['status' => 'canceled', 'canceled_at' => time(), 'paused_at' => null, 'trial_end' => $sub->trial_end]); + $this->mockRequest('POST', "/v1/subscriptions/{$subID}/cancel", [], $mockCanceledSub); $sub->cancel(); - - $sub_cancel = Subscription::retrieve($sub->id); - $this->assertSame($sub_cancel->status, 'canceled'); - - $sub_id = $sub->id; + $this->assertSame('canceled', $sub->status); + $this->assertNotNull($sub->canceled_at); + + $this->mockRequest('GET', "/v1/subscriptions/{$subID}", [], $mockCanceledSub); + $sub_cancel_retrieved = Subscription::retrieve($sub->id); + $this->assertSame('canceled', $sub_cancel_retrieved->status); + + $mockDeletedResponse = [ + 'deleted' => true, + 'id' => $subID, + 'livemode' => false + ]; + $this->mockRequest('DELETE', "/v1/subscriptions/{$subID}", [], $mockDeletedResponse); $sub->delete(); - $this->assertSame($sub_id, $sub->id); $this->assertTrue($sub->deleted); + $this->assertSame($subID, $sub->id); } + public function testAll() { - $planID = 'gold-' . self::randomString(); - self::retrieveOrCreatePlan($planID); - - $customer = self::createTestCustomer(); - - $sub = Subscription::create( - array( - 'customer' => $customer->id, - 'plan' => $planID - ) - ); - - $planID_2 = 'gold-2-' . self::randomString(); - self::retrieveOrCreatePlan($planID_2); - - $sub_2 = Subscription::create( - array( - 'customer' => $customer->id, - 'plan' => $planID_2 - ) - ); - - $subs = Subscription::all( - array( - 'limit' => 2, - 'offset' => 0 - ) - ); - - $this->assertSame(2, count($subs['data'])); + $planID1 = 'pln_all_1'; + $planID2 = 'pln_all_2'; + $customerID = 'cus_for_all_subs'; + $subID1 = 'sub_all_1'; + $subID2 = 'sub_all_2'; + + $mockSub1 = $this->mockSubscriptionData($subID1, $customerID, $planID1); + $mockSub2 = $this->mockSubscriptionData($subID2, $customerID, $planID2, ['status' => 'trial', 'trial_end' => time() + 86400]); + + $mockListResponse = [ + 'object' => 'list', + 'data' => [$mockSub2, $mockSub1], + 'has_more' => false, + 'url' => '/v1/subscriptions' + ]; + + $listParams = ['limit' => 2, 'offset' => 0, 'customer' => $customerID]; + + $this->mockRequest('GET', '/v1/subscriptions', $listParams, $mockListResponse); + + $subs = Subscription::all($listParams); + + $this->assertInstanceOf('Payjp\Collection', $subs); + $this->assertCount(2, $subs->data); + $this->assertInstanceOf('Payjp\Subscription', $subs->data[0]); + $this->assertSame($subID2, $subs->data[0]->id); + $this->assertSame('trial', $subs->data[0]->status); + $this->assertInstanceOf('Payjp\Subscription', $subs->data[1]); + $this->assertSame($subID1, $subs->data[1]->id); + $this->assertSame('active', $subs->data[1]->status); } } diff --git a/tests/TokenTest.php b/tests/TokenTest.php index cbf7fef..346ca68 100644 --- a/tests/TokenTest.php +++ b/tests/TokenTest.php @@ -4,17 +4,52 @@ class TokenTest extends TestCase { + /** + * Generate mock token data. + */ + private function mockTokenData($id = 'tok_test_1', $attributes = []) + { + $base = [ + 'id' => $id, + 'object' => 'token', + 'livemode' => false, + 'used' => false, + 'card' => [ + 'id' => 'car_test_card_for_token', + 'object' => 'card', + 'brand' => 'Visa', + 'last4' => '4242', + 'exp_month' => 6, + 'exp_year' => date('Y') + 3, + 'fingerprint' => 'e1d8225886e3a7211127579a1f6f7881', + 'name' => null, + 'country' => null, + 'address_state' => null, + 'address_city' => null, + 'address_zip' => null, + 'address_line1' => null, + 'address_line2' => null, + 'address_zip_check' => 'unchecked', + 'cvc_check' => 'passed', + 'created' => time(), + 'customer' => null, + ], + 'created' => time(), + ]; + return array_merge($base, $attributes); + } + public function testUrls() { $this->assertSame(Token::classUrl(), '/v1/tokens'); - $token = new Token('abcd/efgh'); - $this->assertSame($token->instanceUrl(), '/v1/tokens/abcd%2Fefgh'); + $tokenSimple = new Token('tok_test_1'); + $this->assertSame($tokenSimple->instanceUrl(), '/v1/tokens/tok_test_1'); + $tokenComplex = new Token('tok_abcd/efgh'); + $this->assertSame($tokenComplex->instanceUrl(), '/v1/tokens/tok_abcd%2Fefgh'); } public function testCreate() { - self::authorizeFromEnv(); - $params = [ 'card' => [ 'number' => '4242424242424242', @@ -23,28 +58,39 @@ public function testCreate() 'cvc' => '314' ] ]; + $mockTokenResponse = $this->mockTokenData('tok_created_token'); + $mockTokenResponse['card']['exp_month'] = $params['card']['exp_month']; + $mockTokenResponse['card']['exp_year'] = $params['card']['exp_year']; + + $this->mockRequest('POST', '/v1/tokens', $params, $mockTokenResponse, 200, ['payjp_direct_token_generate' => 'true']); + + $opts = new \Payjp\Util\RequestOptions(null, ['payjp_direct_token_generate' => 'true']); + $token = Token::create($params, $opts); - $token = Token::create($params, ['payjp_direct_token_generate' => 'true']); - $this->assertNotNull($token); + $this->assertInstanceOf('Payjp\Token', $token); + $this->assertSame('tok_created_token', $token->id); + $this->assertFalse($token->livemode); + $this->assertFalse($token->used); + $this->assertInstanceOf('Payjp\Card', $token->card); + $this->assertSame('Visa', $token->card->brand); + $this->assertSame('4242', $token->card->last4); + $this->assertSame($params['card']['exp_month'], $token->card->exp_month); + $this->assertSame($params['card']['exp_year'], $token->card->exp_year); + $this->assertSame('passed', $token->card->cvc_check); } public function testRetrieve() { - self::authorizeFromEnv(); - - $params = [ - 'card' => [ - 'number' => '4242424242424242', - 'exp_month' => 6, - 'exp_year' => date('Y') + 3, - 'cvc' => '314' - ] - ]; + $tokenId = 'tok_test_retrieved'; + $mockTokenResponse = $this->mockTokenData($tokenId); - $token = Token::create($params, ['payjp_direct_token_generate' => 'true']); + $this->mockRequest('GET', '/v1/tokens/' . $tokenId, [], $mockTokenResponse); - $token_retrieve = Token::retrieve($token->id); + $token_retrieve = Token::retrieve($tokenId); - $this->assertSame($token->id, $token_retrieve->id); + $this->assertInstanceOf('Payjp\Token', $token_retrieve); + $this->assertSame($tokenId, $token_retrieve->id); + $this->assertInstanceOf('Payjp\Card', $token_retrieve->card); + $this->assertSame('4242', $token_retrieve->card->last4); } } diff --git a/tests/TransferTest.php b/tests/TransferTest.php index 9abd647..51ac5ce 100644 --- a/tests/TransferTest.php +++ b/tests/TransferTest.php @@ -4,38 +4,210 @@ class TransferTest extends TestCase { + /** + * Generate mock transfer data. + */ + private function mockTransferData($id = 'tr_test_1', $attributes = []) + { + $base = [ + 'amount' => 1000, + 'carried_balance' => null, + 'charges' => [ + 'count' => 1, + 'data' => [ + [ + 'amount' => 1000, + 'amount_refunded' => 0, + 'captured' => true, + 'captured_at' => 1441706750, + 'card' => [ + 'address_city' => null, + 'address_line1' => null, + 'address_line2' => null, + 'address_state' => null, + 'address_zip' => null, + 'address_zip_check' => 'unchecked', + 'brand' => 'Visa', + 'country' => null, + 'created' => 1441706750, + 'customer' => null, + 'cvc_check' => 'unchecked', + 'exp_month' => 5, + 'exp_year' => 2099, + 'fingerprint' => 'e1d8225886e3a7211127df751c86787f', + 'id' => 'car_93e59e9a9714134ef639865e2b9e', + 'last4' => '4242', + 'name' => null, + 'object' => 'card', + 'three_d_secure_status' => null + ], + 'created' => 1441706750, + 'currency' => 'jpy', + 'customer' => 'cus_b92b879e60f62b532d6756ae12af', + 'description' => null, + 'expired_at' => null, + 'failure_code' => null, + 'failure_message' => null, + 'fee_rate' => '3.00', + 'id' => 'ch_60baaf2dc8f3e35684ebe2031a6e0', + 'object' => 'charge', + 'paid' => true, + 'refund_reason' => null, + 'refunded' => false, + 'subscription' => null + ] + ], + 'has_more' => false, + 'object' => 'list', + 'url' => "/v1/transfers/{$id}/charges" + ], + 'created' => 1438354800, + 'currency' => 'jpy', + 'description' => null, + 'id' => $id, + 'livemode' => false, + 'object' => 'transfer', + 'scheduled_date' => '2015-09-16', + 'status' => 'pending', + 'summary' => [ + 'charge_count' => 1, + 'charge_fee' => 0, + 'charge_gross' => 1000, + 'net' => 1000, + 'refund_amount' => 0, + 'refund_count' => 0, + 'dispute_amount' => 0, + 'dispute_count' => 0 + ], + 'term_end' => 1439650800, + 'term_start' => 1438354800, + 'transfer_amount' => null, + 'transfer_date' => null + ]; + return array_merge($base, $attributes); + } + + /** + * Generate mock charge data specifically for transfer context. + */ + private function mockChargeDataForTransfer($id = 'ch_test_for_tr', $transferId = 'tr_test_1', $attributes = []) + { + $base = [ + 'amount' => 1000, + 'amount_refunded' => 0, + 'captured' => true, + 'captured_at' => 1583375140, + 'card' => [ + 'address_city' => null, + 'address_line1' => null, + 'address_line2' => null, + 'address_state' => null, + 'address_zip' => null, + 'address_zip_check' => 'unchecked', + 'brand' => 'Visa', + 'country' => null, + 'created' => 1583375140, + 'customer' => null, + 'cvc_check' => 'passed', + 'exp_month' => 2, + 'exp_year' => 2099, + 'fingerprint' => 'e1d8225886e3a7211127df751c86787f', + 'id' => 'car_' . substr($id, 3), // Create a card ID based on charge ID + 'last4' => '4242', + 'name' => 'PAY TARO', + 'object' => 'card', + 'three_d_secure_status' => null + ], + 'created' => 1583375140, + 'currency' => 'jpy', + 'customer' => 'cus_test_customer', + 'description' => null, + 'expired_at' => null, + 'failure_code' => null, + 'failure_message' => null, + 'fee_rate' => '3.00', + 'id' => $id, + 'livemode' => false, + 'object' => 'charge', + 'paid' => true, + 'refund_reason' => null, + 'refunded' => false, + 'subscription' => null, + 'transfer' => $transferId // Include reference to the transfer + ]; + return array_merge($base, $attributes); + } + public function testAllRetrieve() { - self::authorizeFromEnv(); + $transferId1 = 'tr_test_1'; + $transferId2 = 'tr_test_2'; + $mockTransfer1 = $this->mockTransferData($transferId1); + $mockTransfer2 = $this->mockTransferData($transferId2, ['amount' => 15000]); + + $mockListResponse = [ + 'object' => 'list', + 'data' => [$mockTransfer1, $mockTransfer2], + 'has_more' => false, + 'url' => '/v1/transfers' + ]; + + // Mock Transfer::all() + $this->mockRequest('GET', '/v1/transfers', ['limit' => 3, 'offset' => 0], $mockListResponse); $transfers = Transfer::all([ 'limit' => 3, 'offset' => 0 ]); - if (count($transfers['data'])) { - $transfer = Transfer::retrieve($transfers['data'][0]->id); - $this->assertSame($transfers['data'][0]->id, $transfer->id); - } + $this->assertInstanceOf('Payjp\Collection', $transfers); + $this->assertCount(2, $transfers->data); + $this->assertInstanceOf('Payjp\Transfer', $transfers->data[0]); + $this->assertSame($transferId1, $transfers->data[0]->id); + + // Mock Transfer::retrieve() for the first transfer + $this->mockRequest('GET', '/v1/transfers/' . $transferId1, [], $mockTransfer1); + $transfer = Transfer::retrieve($transfers->data[0]->id); + + $this->assertInstanceOf('Payjp\Transfer', $transfer); + $this->assertSame($transferId1, $transfer->id); + $this->assertSame(1000, $transfer->amount); // Check amount from mock } public function testAllCharge() { - self::authorizeFromEnv(); + $transferId = 'tr_test_charges'; + $mockTransfer = $this->mockTransferData($transferId); - $transfers = Transfer::all([ + $chargeId1 = 'ch_tr_1'; + $chargeId2 = 'ch_tr_2'; + $mockCharge1 = $this->mockChargeDataForTransfer($chargeId1, $transferId); + $mockCharge2 = $this->mockChargeDataForTransfer($chargeId2, $transferId, ['amount' => 3000]); + + $mockChargeListResponse = [ + 'object' => 'list', + 'data' => [$mockCharge1, $mockCharge2], + 'has_more' => false, + 'url' => "/v1/transfers/{$transferId}/charges" + ]; + + $this->mockRequest('GET', '/v1/transfers/' . $transferId, [], $mockTransfer); + $transfer = Transfer::retrieve($transferId); + $this->assertInstanceOf('Payjp\Transfer', $transfer); + + $this->mockRequest('GET', "/v1/transfers/{$transferId}/charges", ['limit' => 3, 'offset' => 0], $mockChargeListResponse); + $charges = $transfer->charges->all([ 'limit' => 3, 'offset' => 0 ]); - if (count($transfers['data'])) { - $transfer = Transfer::retrieve($transfers['data'][0]->id); - $charges = $transfer->charges->all([ - 'limit' => 3, - 'offset' => 0 - ]); - $this->markTestSkipped('Should be changed to mock.'); - // $this->assertTrue(count($charges['data']) > 0); - } + $this->assertInstanceOf('Payjp\Collection', $charges); + $this->assertCount(2, $charges->data); + $this->assertInstanceOf('Payjp\Charge', $charges->data[0]); + $this->assertSame($chargeId1, $charges->data[0]->id); + $this->assertSame($transferId, $charges->data[0]->transfer); // Verify charge is linked + $this->assertInstanceOf('Payjp\Charge', $charges->data[1]); + $this->assertSame($chargeId2, $charges->data[1]->id); + $this->assertSame(3000, $charges->data[1]->amount); } } diff --git a/tests/UtilTest.php b/tests/UtilTest.php index e2e465b..c9078fd 100644 --- a/tests/UtilTest.php +++ b/tests/UtilTest.php @@ -24,6 +24,10 @@ public function testThatPHPHasValueSemanticsForArrays() public function testConvertPayjpObjectToArrayIncludesId() { + $mock = $this->setUpMockRequest(); + $mock->expects($this->any()) + ->method('request') + ->willReturn([json_encode(['id' => 'cus_mocked']), 200]); $customer = self::createTestCustomer(); $this->assertTrue(array_key_exists("id", $customer->__toArray(true))); } From 87e87dd42d2fb1823de30b15afc090f84ec68b7d Mon Sep 17 00:00:00 2001 From: Yoichi Fujimoto Date: Fri, 18 Apr 2025 10:28:05 +0900 Subject: [PATCH 2/3] fix: remove empty string validation for description in Customer model --- lib/PayjpObject.php | 8 -------- tests/CustomerTest.php | 31 ++++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/PayjpObject.php b/lib/PayjpObject.php index 8b70aa3..03b430a 100644 --- a/lib/PayjpObject.php +++ b/lib/PayjpObject.php @@ -58,14 +58,6 @@ public function __construct($id = null, $opts = null) // Standard accessor magic methods public function __set($k, $v) { - if ($v === "") { - throw new InvalidArgumentException( - 'You cannot set \''.$k.'\'to an empty string. ' - .'We interpret empty strings as NULL in requests. ' - .'You may set obj->'.$k.' = NULL to delete the property' - ); - } - if (self::$nestedUpdatableAttributes->includes($k) && isset($this->$k) && is_array($v)) { $this->$k->replaceWith($v); diff --git a/tests/CustomerTest.php b/tests/CustomerTest.php index df59015..3e96b86 100644 --- a/tests/CustomerTest.php +++ b/tests/CustomerTest.php @@ -197,22 +197,35 @@ public function testBogusAttribute() public function testUpdateDescriptionEmpty() { - $this->expectException('\InvalidArgumentException'); $mockCustomerData = $this->mockCustomerData('cus_test_1', ['description' => '123']); + $mockCustomerData2 = $this->mockCustomerData('cus_test_1', ['description' => '']); $mock = $this->setUpMockRequest(); - $mock->expects($this->once()) + $mock->expects($this->exactly(2)) ->method('request') - ->with( - 'get', - 'https://api.pay.jp/v1/customers/cus_test_1', - $this->anything(), - [], - false + ->withConsecutive( + [ + 'get', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + [], + false + ], + [ + 'post', + 'https://api.pay.jp/v1/customers/cus_test_1', + $this->anything(), + ['description' => ''], + false + ] ) - ->willReturn([json_encode($mockCustomerData), 200]); + ->willReturnOnConsecutiveCalls( + [json_encode($mockCustomerData), 200], + [json_encode($mockCustomerData2), 200] + ); $customer = Customer::retrieve('cus_test_1'); $customer->description = ''; $customer->save(); + $this->assertSame('', $customer->description); } public function testUpdateDescriptionNull() From aeceb8c19a29e49b5e8b3c4b9f6ca9e5eafe06b6 Mon Sep 17 00:00:00 2001 From: Yoichi Fujimoto Date: Wed, 30 Apr 2025 08:40:36 +0900 Subject: [PATCH 3/3] fix: Fixed the order of Util.php and .gitignore. --- .gitignore | 3 +++ lib/Util/Util.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5776c9e..5967665 100755 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ clover.xml # Ignore PHPUnit cache file .phpunit.result.cache + +# Ignore PHP-CS-Fixer cache file +.php-cs-fixer.cache \ No newline at end of file diff --git a/lib/Util/Util.php b/lib/Util/Util.php index 25ef650..306a122 100644 --- a/lib/Util/Util.php +++ b/lib/Util/Util.php @@ -8,11 +8,11 @@ abstract class Util { // todo wanna use 'private const' (only PHP >= v7.1.0) private static $types = array( + 'account' => \Payjp\Account::class, 'application_url' => \Payjp\ApplicationUrl::class, 'balance' => \Payjp\Balance::class, 'card' => \Payjp\Card::class, 'charge' => \Payjp\Charge::class, - 'account' => \Payjp\Account::class, 'customer' => \Payjp\Customer::class, 'event' => \Payjp\Event::class, 'list' => \Payjp\Collection::class,