From 52b7dd5e51aa3223e9a833cc05d8545e42fbaa41 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 9 Jul 2019 22:39:25 +0200 Subject: [PATCH] Implement Payment Methods These changes replace the old Sources and Token based APIs from Stripe with the new and recommended Payment Methods API. This new API will handle all card based interaction and in the future also new payment methods like IDEAL, Bancontact, etc. Basically all card-based methods and its related class were replayed by payment method methods (wording djeez) and a new PaymentMethod class. New tests for these methods were added. There's one legacy backwards compatibility that's maintained and that's for the defaultPaymentMethod method. This will return an instance of a Stripe\Card or Stripe\BankAccount source if no default payment method was found. This way, older apps migrating to the new Cashier release, can still use these objects to interact with their apps. The subscription creation process now also requires a payment method instead of a Stripe token. --- .../views/emails/confirm_payment.blade.php | 2 +- src/Billable.php | 161 ++++++++++-------- src/Card.php | 66 ------- src/Exceptions/PaymentFailure.php | 4 +- src/Http/Controllers/WebhookController.php | 17 +- src/Payment.php | 12 +- src/PaymentMethod.php | 66 +++++++ src/SubscriptionBuilder.php | 16 +- tests/Integration/ChargesTest.php | 4 +- tests/Integration/InvoicesTest.php | 4 +- tests/Integration/PaymentMethodsTest.php | 103 +++++++++++ tests/Integration/SubscriptionsTest.php | 46 ++--- tests/Integration/WebhooksTest.php | 8 +- tests/Unit/CustomerTest.php | 16 +- 14 files changed, 318 insertions(+), 207 deletions(-) delete mode 100644 src/Card.php create mode 100644 src/PaymentMethod.php create mode 100644 tests/Integration/PaymentMethodsTest.php diff --git a/resources/views/emails/confirm_payment.blade.php b/resources/views/emails/confirm_payment.blade.php index 4547d6db..d0f94293 100644 --- a/resources/views/emails/confirm_payment.blade.php +++ b/resources/views/emails/confirm_payment.blade.php @@ -3,7 +3,7 @@ {{ __('Extra confirmation is needed to process your payment. Please continue to the payment page by clicking on the button below.') }} -@component('mail::button', ['url' => route('cashier.payment', ['id' => $payment->id()])]) +@component('mail::button', ['url' => route('cashier.payment', ['id' => $payment->id])]) {{ __('Confirm Payment') }} @endcomponent diff --git a/src/Billable.php b/src/Billable.php index 39f2a62e..a86b1682 100644 --- a/src/Billable.php +++ b/src/Billable.php @@ -4,7 +4,6 @@ use Exception; use Stripe\Card as StripeCard; -use Stripe\Token as StripeToken; use Illuminate\Support\Collection; use Stripe\Invoice as StripeInvoice; use Stripe\Customer as StripeCustomer; @@ -12,6 +11,7 @@ use Stripe\InvoiceItem as StripeInvoiceItem; use Stripe\Error\Card as StripeCardException; use Stripe\PaymentIntent as StripePaymentIntent; +use Stripe\PaymentMethod as StripePaymentMethod; use Laravel\Cashier\Exceptions\InvalidStripeCustomer; use Stripe\Error\InvalidRequest as StripeErrorInvalidRequest; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -66,16 +66,6 @@ public function refund($paymentIntent, array $options = []) return $intent->charges->data[0]->refund($options); } - /** - * Determines if the customer currently has a card on file. - * - * @return bool - */ - public function hasCardOnFile() - { - return (bool) $this->card_brand; - } - /** * Add an invoice item to the customer's upcoming invoice. * @@ -353,102 +343,116 @@ public function invoicesIncludingPending(array $parameters = []) } /** - * Get a collection of the entity's cards. + * Determines if the customer currently has a payment method. + * + * @return bool + */ + public function hasPaymentMethod() + { + return (bool) $this->card_brand; + } + + /** + * Get a collection of the entity's payment methods. * * @param array $parameters - * @return \Illuminate\Support\Collection + * @return \Illuminate\Support\Collection|\Laravel\Cashier\PaymentMethod[] */ - public function cards($parameters = []) + public function paymentMethods($parameters = []) { $this->assertCustomerExists(); - $cards = []; - $parameters = array_merge(['limit' => 24], $parameters); - $stripeCards = $this->asStripeCustomer()->sources->all( - ['object' => 'card'] + $parameters + // Note that "type" is temporarily required here by Stripe until + // they've rolled out support for bank account. + $paymentMethods = StripePaymentMethod::all( + ['customer' => $this->stripe_id, 'type' => 'card'] + $parameters, + Cashier::stripeOptions() ); - if (! is_null($stripeCards)) { - foreach ($stripeCards->data as $card) { - $cards[] = new Card($this, $card); - } - } - - return new Collection($cards); + return collect($paymentMethods->data)->map(function ($paymentMethod) { + return new PaymentMethod($this, $paymentMethod); + }); } /** - * Get the default card for the entity. + * Get the default payment method for the entity. * - * @return \Stripe\Card|null + * @return \Laravel\Cashier\PaymentMethod|\Stripe\Card|\Stripe\BankAccount|null */ - public function defaultCard() + public function defaultPaymentMethod() { if (! $this->hasStripeId()) { return; } - $customer = $this->asStripeCustomer(); + $customer = StripeCustomer::retrieve( + ['id' => $this->stripe_id, 'expand' => ['invoice_settings.default_payment_method', 'default_source']], + Cashier::stripeOptions() + ); - foreach ($customer->sources->data as $card) { - if ($card->id === $customer->default_source) { - return $card; - } + if ($customer->invoice_settings->default_payment_method) { + return new PaymentMethod($this, $customer->invoice_settings->default_payment_method); } + + // If we can't find a default payment method, we'll try to see if a Card or + // BankAccount source was still set from the legacy Sources implementation. + return $customer->default_source; } /** - * Update customer's credit card. + * Update customer's default payment method. * - * @param string $token + * @param string $paymentMethod * @return void */ - public function updateCard($token) + public function updatePaymentMethod($paymentMethod) { $this->assertCustomerExists(); $customer = $this->asStripeCustomer(); - $token = StripeToken::retrieve($token, Cashier::stripeOptions()); + $paymentMethod = StripePaymentMethod::retrieve($paymentMethod, Cashier::stripeOptions()); - // If the given token already has the card as their default source, we can just - // bail out of the method now. We don't need to keep adding the same card to - // a model's account every time we go through this particular method call. - if ($token[$token->type]->id === $customer->default_source) { + // If the customer already has the payment method as their default, we can bail out + // of the call now. We don't need to keep adding the same payment method to this + // model's account every single time we go through this specific process call. + if ($paymentMethod->id === $customer->invoice_settings->default_payment_method) { return; } - $card = $customer->sources->create(['source' => $token]); + $paymentMethod = $paymentMethod->attach(['customer' => $customer->id], Cashier::stripeOptions()); - $customer->default_source = $card->id; + $customer->invoice_settings = ['default_payment_method' => $paymentMethod->id]; - $customer->save(); + $customer->save(Cashier::stripeOptions()); - // Next we will get the default source for this model so we can update the last - // four digits and the card brand on the record in the database. This allows - // us to display the information on the front-end when updating the cards. - $source = $customer->default_source - ? $customer->sources->retrieve($customer->default_source) - : null; - - $this->fillCardDetails($source); + // Next we'll get the default payment method for this user so we can update + // the payment method details on the record in the database. This allows + // us to show that on the front-end when updating the payment methods. + $this->fillPaymentMethodDetails($paymentMethod); $this->save(); } /** - * Synchronises the customer's card from Stripe back into the database. + * Synchronises the customer's payment method from Stripe back into the database. * * @return $this */ - public function updateCardFromStripe() + public function updatePaymentMethodFromStripe() { - $defaultCard = $this->defaultCard(); + $defaultPaymentMethod = $this->defaultPaymentMethod(); - if ($defaultCard) { - $this->fillCardDetails($defaultCard)->save(); + if ($defaultPaymentMethod) { + if ($defaultPaymentMethod instanceof PaymentMethod) { + $this->fillPaymentMethodDetails( + $defaultPaymentMethod->asStripePaymentMethod() + )->save(); + } else { + $this->fillSourceDetails($defaultPaymentMethod)->save(); + } } else { $this->forceFill([ 'card_brand' => null, @@ -459,37 +463,54 @@ public function updateCardFromStripe() return $this; } + /** + * Fills the model's properties with the payment method from Stripe. + * + * @param \Stripe\PaymentMethod|null $paymentMethod + * @return $this + */ + protected function fillPaymentMethodDetails($paymentMethod) + { + if ($paymentMethod->type === 'card') { + $this->card_brand = $paymentMethod->card->brand; + $this->card_last_four = $paymentMethod->card->last4; + } + + return $this; + } + /** * Fills the model's properties with the source from Stripe. * - * @param \Stripe\Card|\Stripe\BankAccount|null $card + * @param \Stripe\Card|\Stripe\BankAccount|null $source * @return $this + * @deprecated Will be removed in a future Cashier update. You should use the new payment methods api instead. */ - protected function fillCardDetails($card) + protected function fillSourceDetails($source) { - if ($card instanceof StripeCard) { - $this->card_brand = $card->brand; - $this->card_last_four = $card->last4; - } elseif ($card instanceof StripeBankAccount) { + if ($source instanceof StripeCard) { + $this->card_brand = $source->brand; + $this->card_last_four = $source->last4; + } elseif ($source instanceof StripeBankAccount) { $this->card_brand = 'Bank Account'; - $this->card_last_four = $card->last4; + $this->card_last_four = $source->last4; } return $this; } /** - * Deletes the entity's cards. + * Deletes the entity's payment methods. * * @return void */ - public function deleteCards() + public function deletePaymentMethods() { - $this->cards()->each(function ($card) { - $card->delete(); + $this->paymentMethods()->each(function (PaymentMethod $paymentMethod) { + $paymentMethod->delete(); }); - $this->updateCardFromStripe(); + $this->updatePaymentMethodFromStripe(); } /** @@ -610,7 +631,7 @@ public function updateStripeCustomer(array $options = []) } /** - * Get the Stripe customer instance for the current user and token. + * Get the Stripe customer instance for the current user or create one. * * @param array $options * @return \Stripe\Customer diff --git a/src/Card.php b/src/Card.php deleted file mode 100644 index 8be0fd44..00000000 --- a/src/Card.php +++ /dev/null @@ -1,66 +0,0 @@ -owner = $owner; - $this->card = $card; - } - - /** - * Delete the card. - * - * @return \Stripe\Card - */ - public function delete() - { - return $this->card->delete(); - } - - /** - * Get the Stripe card instance. - * - * @return \Stripe\Card - */ - public function asStripeCard() - { - return $this->card; - } - - /** - * Dynamically get values from the Stripe card. - * - * @param string $key - * @return mixed - */ - public function __get($key) - { - return $this->card->{$key}; - } -} diff --git a/src/Exceptions/PaymentFailure.php b/src/Exceptions/PaymentFailure.php index ec770024..98f80b42 100644 --- a/src/Exceptions/PaymentFailure.php +++ b/src/Exceptions/PaymentFailure.php @@ -12,11 +12,11 @@ class PaymentFailure extends IncompletePayment * @param \Laravel\Cashier\Payment $payment * @return self */ - public static function cardError(Payment $payment) + public static function invalidPaymentMethod(Payment $payment) { return new self( $payment, - 'The payment attempt failed because of a card error.' + 'The payment attempt failed because of an invalid payment method.' ); } } diff --git a/src/Http/Controllers/WebhookController.php b/src/Http/Controllers/WebhookController.php index 5a2d6070..591e0e0c 100644 --- a/src/Http/Controllers/WebhookController.php +++ b/src/Http/Controllers/WebhookController.php @@ -135,22 +135,7 @@ protected function handleCustomerSubscriptionDeleted(array $payload) protected function handleCustomerUpdated(array $payload) { if ($user = $this->getUserByStripeId($payload['data']['object']['id'])) { - $user->updateCardFromStripe(); - } - - return new Response('Webhook Handled', 200); - } - - /** - * Handle customer source deleted. - * - * @param array $payload - * @return \Symfony\Component\HttpFoundation\Response - */ - protected function handleCustomerSourceDeleted(array $payload) - { - if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) { - $user->updateCardFromStripe(); + $user->updatePaymentMethodFromStripe(); } return new Response('Webhook Handled', 200); diff --git a/src/Payment.php b/src/Payment.php index 92f8f7d4..2d48af5a 100644 --- a/src/Payment.php +++ b/src/Payment.php @@ -26,16 +26,6 @@ public function __construct(StripePaymentIntent $paymentIntent) $this->paymentIntent = $paymentIntent; } - /** - * The Stripe PaymentIntent ID. - * - * @return string - */ - public function id() - { - return $this->paymentIntent->id; - } - /** * Get the total amount that will be paid. * @@ -117,7 +107,7 @@ public function isSucceeded() public function validate() { if ($this->requiresPaymentMethod()) { - throw PaymentFailure::cardError($this); + throw PaymentFailure::invalidPaymentMethod($this); } elseif ($this->requiresAction()) { throw PaymentActionRequired::incomplete($this); } diff --git a/src/PaymentMethod.php b/src/PaymentMethod.php new file mode 100644 index 00000000..cd918e21 --- /dev/null +++ b/src/PaymentMethod.php @@ -0,0 +1,66 @@ +owner = $owner; + $this->paymentMethod = $paymentMethod; + } + + /** + * Delete the payment method. + * + * @return \Stripe\PaymentMethod + */ + public function delete() + { + return $this->paymentMethod->detach(null, Cashier::stripeOptions()); + } + + /** + * Get the Stripe PaymentMethod instance. + * + * @return \Stripe\PaymentMethod + */ + public function asStripePaymentMethod() + { + return $this->paymentMethod; + } + + /** + * Dynamically get values from the Stripe PaymentMethod. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->paymentMethod->{$key}; + } +} diff --git a/src/SubscriptionBuilder.php b/src/SubscriptionBuilder.php index 1e4bb889..9978c59c 100644 --- a/src/SubscriptionBuilder.php +++ b/src/SubscriptionBuilder.php @@ -193,13 +193,13 @@ public function add(array $options = []) /** * Create a new Stripe subscription. * - * @param string|null $token + * @param string|null $paymentMethod * @param array $options * @return \Laravel\Cashier\Subscription */ - public function create($token = null, array $options = []) + public function create($paymentMethod = null, array $options = []) { - $customer = $this->getStripeCustomer($token, $options); + $customer = $this->getStripeCustomer($paymentMethod, $options); /** @var \Stripe\Subscription $stripeSubscription */ $stripeSubscription = $customer->subscriptions->create($this->buildPayload()); @@ -232,18 +232,18 @@ public function create($token = null, array $options = []) } /** - * Get the Stripe customer instance for the current user and token. + * Get the Stripe customer instance for the current user and payment method. * - * @param string|null $token + * @param string|null $paymentMethod * @param array $options * @return \Stripe\Customer */ - protected function getStripeCustomer($token = null, array $options = []) + protected function getStripeCustomer($paymentMethod = null, array $options = []) { $customer = $this->owner->createOrGetStripeCustomer($options); - if ($token) { - $this->owner->updateCard($token); + if ($paymentMethod) { + $this->owner->updatePaymentMethod($paymentMethod); } return $customer; diff --git a/tests/Integration/ChargesTest.php b/tests/Integration/ChargesTest.php index 6a84757c..9e2206be 100644 --- a/tests/Integration/ChargesTest.php +++ b/tests/Integration/ChargesTest.php @@ -34,7 +34,7 @@ public function test_customer_can_be_charged_and_invoiced_immediately() { $user = $this->createCustomer('customer_can_be_charged_and_invoiced_immediately'); $user->createAsStripeCustomer(); - $user->updateCard('tok_visa'); + $user->updatePaymentMethod('pm_card_visa'); $user->invoiceFor('Laravel Cashier', 1000); @@ -47,7 +47,7 @@ public function test_customer_can_be_refunded() { $user = $this->createCustomer('customer_can_be_refunded'); $user->createAsStripeCustomer(); - $user->updateCard('tok_visa'); + $user->updatePaymentMethod('pm_card_visa'); $invoice = $user->invoiceFor('Laravel Cashier', 1000); $refund = $user->refund($invoice->payment_intent); diff --git a/tests/Integration/InvoicesTest.php b/tests/Integration/InvoicesTest.php index 0ab09495..ceee96c2 100644 --- a/tests/Integration/InvoicesTest.php +++ b/tests/Integration/InvoicesTest.php @@ -20,7 +20,7 @@ public function test_invoicing_fails_with_nothing_to_invoice() { $user = $this->createCustomer('invoicing_fails_with_nothing_to_invoice'); $user->createAsStripeCustomer(); - $user->updateCard('tok_visa'); + $user->updatePaymentMethod('pm_card_visa'); $response = $user->invoice(); @@ -31,7 +31,7 @@ public function test_customer_can_be_invoiced() { $user = $this->createCustomer('customer_can_be_invoiced'); $user->createAsStripeCustomer(); - $user->updateCard('tok_visa'); + $user->updatePaymentMethod('pm_card_visa'); $response = $user->invoiceFor('Laracon', 49900); diff --git a/tests/Integration/PaymentMethodsTest.php b/tests/Integration/PaymentMethodsTest.php new file mode 100644 index 00000000..599802f9 --- /dev/null +++ b/tests/Integration/PaymentMethodsTest.php @@ -0,0 +1,103 @@ +createCustomer('we_can_set_a_default_payment_method'); + $user->createAsStripeCustomer(); + + $user->updatePaymentMethod('pm_card_visa'); + $paymentMethod = $user->defaultPaymentMethod(); + + $this->assertInstanceOf(PaymentMethod::class, $paymentMethod); + $this->assertEquals('visa', $paymentMethod->card->brand); + $this->assertEquals('4242', $paymentMethod->card->last4); + } + + public function test_legacy_we_can_retrieve_an_old_default_source_as_a_default_payment_method() + { + $user = $this->createCustomer('we_can_retrieve_an_old_default_source_as_a_default_payment_method'); + $customer = $user->createAsStripeCustomer(); + + $card = $customer->sources->create(['source' => 'tok_visa']); + $customer->default_source = $card->id; + $customer->save(); + + $paymentMethod = $user->defaultPaymentMethod(); + + $this->assertInstanceOf(StripeCard::class, $paymentMethod); + $this->assertEquals('Visa', $paymentMethod->brand); + $this->assertEquals('4242', $paymentMethod->last4); + } + + public function test_we_can_retrieve_all_payment_methods() + { + $user = $this->createCustomer('we_can_retrieve_all_payment_methods'); + $customer = $user->createAsStripeCustomer(); + + $paymentMethod = StripePaymentMethod::retrieve('pm_card_visa', Cashier::stripeOptions()); + $paymentMethod->attach(['customer' => $customer->id]); + + $paymentMethod = StripePaymentMethod::retrieve('pm_card_mastercard', Cashier::stripeOptions()); + $paymentMethod->attach(['customer' => $customer->id]); + + $paymentMethods = $user->paymentMethods(); + + $this->assertCount(2, $paymentMethods); + $this->assertEquals('mastercard', $paymentMethods->first()->card->brand); + $this->assertEquals('visa', $paymentMethods->last()->card->brand); + } + + public function test_we_can_sync_the_payment_method_from_stripe() + { + $user = $this->createCustomer('we_can_sync_the_payment_method_from_stripe'); + $customer = $user->createAsStripeCustomer(); + + $paymentMethod = StripePaymentMethod::retrieve('pm_card_visa', Cashier::stripeOptions()); + $paymentMethod->attach(['customer' => $customer->id]); + + $customer->invoice_settings = ['default_payment_method' => $paymentMethod->id]; + + $customer->save(); + + $user->refresh(); + + $this->assertNull($user->card_brand); + $this->assertNull($user->card_last_four); + + $user = $user->updatePaymentMethodFromStripe(); + + $this->assertEquals('visa', $user->card_brand); + $this->assertEquals('4242', $user->card_last_four); + } + + public function test_we_delete_all_payment_methods() + { + $user = $this->createCustomer('we_delete_all_payment_methods'); + $customer = $user->createAsStripeCustomer(); + + $paymentMethod = StripePaymentMethod::retrieve('pm_card_visa', Cashier::stripeOptions()); + $paymentMethod->attach(['customer' => $customer->id]); + + $paymentMethod = StripePaymentMethod::retrieve('pm_card_mastercard', Cashier::stripeOptions()); + $paymentMethod->attach(['customer' => $customer->id]); + + $paymentMethods = $user->paymentMethods(); + + $this->assertCount(2, $paymentMethods); + + $user->deletePaymentMethods(); + + $paymentMethods = $user->paymentMethods(); + + $this->assertCount(0, $paymentMethods); + } +} diff --git a/tests/Integration/SubscriptionsTest.php b/tests/Integration/SubscriptionsTest.php index 556e9772..ccacb10c 100644 --- a/tests/Integration/SubscriptionsTest.php +++ b/tests/Integration/SubscriptionsTest.php @@ -110,7 +110,7 @@ public function test_subscriptions_can_be_created() $user = $this->createCustomer('subscriptions_can_be_created'); // Create Subscription - $user->newSubscription('main', static::$planId)->create('tok_visa'); + $user->newSubscription('main', static::$planId)->create('pm_card_visa'); $this->assertEquals(1, count($user->subscriptions)); $this->assertNotNull($user->subscription('main')->stripe_id); @@ -185,7 +185,7 @@ public function test_subscriptions_can_be_created() public function test_swapping_subscription_with_coupon() { $user = $this->createCustomer('swapping_subscription_with_coupon'); - $user->newSubscription('main', static::$planId)->create('tok_visa'); + $user->newSubscription('main', static::$planId)->create('pm_card_visa'); $subscription = $user->subscription('main'); $subscription->swap(static::$otherPlanId, [ @@ -200,11 +200,11 @@ public function test_declined_card_during_subscribing_results_in_an_exception() $user = $this->createCustomer('declined_card_during_subscribing_results_in_an_exception'); try { - $user->newSubscription('main', static::$planId)->create('tok_chargeCustomerFail'); + $user->newSubscription('main', static::$planId)->create('pm_card_chargeCustomerFail'); $this->fail('Expected exception '.PaymentFailure::class.' was not thrown.'); } catch (PaymentFailure $e) { - // Assert that the payment needs a valid card. + // Assert that the payment needs a valid payment method. $this->assertTrue($e->payment->requiresPaymentMethod()); // Assert subscription was added to the billable entity. @@ -220,7 +220,7 @@ public function test_next_action_needed_during_subscribing_results_in_an_excepti $user = $this->createCustomer('next_action_needed_during_subscribing_results_in_an_exception'); try { - $user->newSubscription('main', static::$planId)->create('tok_threeDSecure2Required'); + $user->newSubscription('main', static::$planId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.PaymentActionRequired::class.' was not thrown.'); } catch (PaymentActionRequired $e) { @@ -239,10 +239,10 @@ public function test_declined_card_during_plan_swap_results_in_an_exception() { $user = $this->createCustomer('declined_card_during_plan_swap_results_in_an_exception'); - $subscription = $user->newSubscription('main', static::$planId)->create('tok_visa'); + $subscription = $user->newSubscription('main', static::$planId)->create('pm_card_visa'); - // Set a faulty card as the customer's default card. - $user->updateCard('tok_chargeCustomerFail'); + // Set a faulty card as the customer's default payment method. + $user->updatePaymentMethod('pm_card_chargeCustomerFail'); try { // Attempt to swap and pay with a faulty card. @@ -250,7 +250,7 @@ public function test_declined_card_during_plan_swap_results_in_an_exception() $this->fail('Expected exception '.PaymentFailure::class.' was not thrown.'); } catch (PaymentFailure $e) { - // Assert that the payment needs a valid card. + // Assert that the payment needs a valid payment method. $this->assertTrue($e->payment->requiresPaymentMethod()); // Assert that the plan was swapped anyway. @@ -265,10 +265,10 @@ public function test_next_action_needed_during_plan_swap_results_in_an_exception { $user = $this->createCustomer('next_action_needed_during_plan_swap_results_in_an_exception'); - $subscription = $user->newSubscription('main', static::$planId)->create('tok_visa'); + $subscription = $user->newSubscription('main', static::$planId)->create('pm_card_visa'); - // Set a card that requires a next action as the customer's default card. - $user->updateCard('tok_threeDSecure2Required'); + // Set a card that requires a next action as the customer's default payment method. + $user->updatePaymentMethod('pm_card_threeDSecure2Required'); try { // Attempt to swap and pay with a faulty card. @@ -291,10 +291,10 @@ public function test_downgrade_with_faulty_card_does_not_incomplete_subscription { $user = $this->createCustomer('downgrade_with_faulty_card_does_not_incomplete_subscription'); - $subscription = $user->newSubscription('main', static::$premiumPlanId)->create('tok_visa'); + $subscription = $user->newSubscription('main', static::$premiumPlanId)->create('pm_card_visa'); - // Set a card that requires a next action as the customer's default card. - $user->updateCard('tok_chargeCustomerFail'); + // Set a card that requires a next action as the customer's default payment method. + $user->updatePaymentMethod('pm_card_chargeCustomerFail'); // Attempt to swap and pay with a faulty card. $subscription = $subscription->swap(static::$planId); @@ -310,10 +310,10 @@ public function test_downgrade_with_3d_secure_does_not_incomplete_subscription() { $user = $this->createCustomer('downgrade_with_3d_secure_does_not_incomplete_subscription'); - $subscription = $user->newSubscription('main', static::$premiumPlanId)->create('tok_visa'); + $subscription = $user->newSubscription('main', static::$premiumPlanId)->create('pm_card_visa'); - // Set a card that requires a next action as the customer's default card. - $user->updateCard('tok_threeDSecure2Required'); + // Set a card that requires a next action as the customer's default payment method. + $user->updatePaymentMethod('pm_card_chargeCustomerFail'); // Attempt to swap and pay with a faulty card. $subscription = $subscription->swap(static::$planId); @@ -332,7 +332,7 @@ public function test_creating_subscription_with_coupons() // Create Subscription $user->newSubscription('main', static::$planId) ->withCoupon(static::$couponId) - ->create('tok_visa'); + ->create('pm_card_visa'); $subscription = $user->subscription('main'); @@ -361,7 +361,7 @@ public function test_creating_subscription_with_an_anchored_billing_cycle() // Create Subscription $user->newSubscription('main', static::$planId) ->anchorBillingCycleOn(new DateTime('first day of next month')) - ->create('tok_visa'); + ->create('pm_card_visa'); $subscription = $user->subscription('main'); @@ -395,7 +395,7 @@ public function test_creating_subscription_with_trial() // Create Subscription $user->newSubscription('main', static::$planId) ->trialDays(7) - ->create('tok_visa'); + ->create('pm_card_visa'); $subscription = $user->subscription('main'); @@ -431,7 +431,7 @@ public function test_creating_subscription_with_explicit_trial() // Create Subscription $user->newSubscription('main', static::$planId) ->trialUntil(Carbon::tomorrow()->hour(3)->minute(15)) - ->create('tok_visa'); + ->create('pm_card_visa'); $subscription = $user->subscription('main'); @@ -464,7 +464,7 @@ public function test_applying_coupons_to_existing_customers() { $user = $this->createCustomer('applying_coupons_to_existing_customers'); - $user->newSubscription('main', static::$planId)->create('tok_visa'); + $user->newSubscription('main', static::$planId)->create('pm_card_visa'); $user->applyCoupon(static::$couponId); diff --git a/tests/Integration/WebhooksTest.php b/tests/Integration/WebhooksTest.php index d8c336f3..d62f5c4c 100644 --- a/tests/Integration/WebhooksTest.php +++ b/tests/Integration/WebhooksTest.php @@ -56,7 +56,7 @@ public static function tearDownAfterClass() public function test_cancelled_subscription_is_properly_reactivated() { $user = $this->createCustomer('cancelled_subscription_is_properly_reactivated'); - $subscription = $user->newSubscription('main', static::$planId)->create('tok_visa'); + $subscription = $user->newSubscription('main', static::$planId)->create('pm_card_visa'); $subscription->cancel(); $this->assertTrue($subscription->cancelled()); @@ -79,7 +79,7 @@ public function test_cancelled_subscription_is_properly_reactivated() public function test_subscription_is_marked_as_cancelled_when_deleted_in_stripe() { $user = $this->createCustomer('subscription_is_marked_as_cancelled_when_deleted_in_stripe'); - $subscription = $user->newSubscription('main', static::$planId)->create('tok_visa'); + $subscription = $user->newSubscription('main', static::$planId)->create('pm_card_visa'); $this->assertFalse($subscription->cancelled()); @@ -104,7 +104,7 @@ public function test_payment_action_required_email_is_sent() $user = $this->createCustomer('payment_action_required_email_is_sent'); try { - $user->newSubscription('main', static::$planId)->create('tok_threeDSecure2Required'); + $user->newSubscription('main', static::$planId)->create('pm_card_threeDSecure2Required'); $this->fail('Expected exception '.PaymentActionRequired::class.' was not thrown.'); } catch (PaymentActionRequired $exception) { @@ -117,7 +117,7 @@ public function test_payment_action_required_email_is_sent() 'object' => [ 'id' => 'foo', 'customer' => $user->stripe_id, - 'payment_intent' => $exception->payment->id(), + 'payment_intent' => $exception->payment->id, ], ], ])->assertOk(); diff --git a/tests/Unit/CustomerTest.php b/tests/Unit/CustomerTest.php index ea1befd1..00874d94 100644 --- a/tests/Unit/CustomerTest.php +++ b/tests/Unit/CustomerTest.php @@ -23,10 +23,22 @@ public function test_customer_can_be_put_on_a_generic_trial() $this->assertFalse($user->onGenericTrial()); } - public function test_default_card_returns_null_when_the_user_is_not_a_customer_yet() + public function test_we_can_determine_if_it_has_a_payment_method() { $user = new User; + $user->card_brand = 'visa'; - $this->assertNull($user->defaultCard()); + $this->assertTrue($user->hasPaymentMethod()); + + $user = new User; + + $this->assertFalse($user->hasPaymentMethod()); + } + + public function test_default_payment_method_returns_null_when_the_user_is_not_a_customer_yet() + { + $user = new User; + + $this->assertNull($user->defaultPaymentMethod()); } }