Closed driesvints closed 5 years ago
Instead of being reliant on webhooks we could maybe use the Manual confirmation? The code doesn't look to hard to write. You would have to have some type of API that handles the different statuses and send back responses to the front-end. Here is the docs: https://stripe.com/docs/payments/payment-intents/quickstart#flow-manual-confirmation
@MarGul nice. They only recently posted that I believe. Gonna look at this in detail when I get to this issue.
I too am looking into this, not only as a way to be SCA compliant but also to help prevent bank disputes. It's a pretty critical feature.
The flow seems a bit ambiougs to me. If anyone can make sense of it / clarify could you comment? It would also help Cashier implement a flow that works with plans/subscriptions.
$plan = \Stripe\Plan::retrieve('your-subscription-plan-id', [
'api_key' => config('services.stripe.secret'),
]);
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => $plan->amount,
'currency' => $plan->currency,
'payment_method_types' => ['card'],
], [
'api_key' => config('services.stripe.secret'),
]);
You can then pass the payment intent details onto your view / form so that it can be used in js when you eventually make a call to handleCardPayment
stripe.createToken()
instead you call stripe.handleCardPayment()
:stripe.handleCardPayment(document.querySelector('input[name="payment_intent_secret"]').value, card).then(function(result) {
if (result.error) {
// Payment failed.
} else {
// Payment successful.
}
});
This is where I get a bit confused, but I'm assuming at that point if the payment was successful, you can create a token for the user and susbcribe them to the plan. I would imagine you would want it to skip the first payment as you have already paid for the first month in the payment intent? Maybe this is where trial period comes into play? I'm also not sure where the web hook comes into play here, as I would assume if the callback is successful then you don't need to do any further checks?
At that point on the server you can clear the payment intent - maybe this is where the web hook comes into play? Is it needed though when you can just get the payment intent when you hit the checkout page, check its status to see if its "expired" and instead use a new one:
$paymentIntent = null;
if (session('paymentIntentId')) {
$paymentIntent = \Stripe\PaymentIntent::retrieve(session('paymentIntentId'), [
'api_key' => config('services.stripe.secret'),
]);
}
$expiredPaymentIntent = $paymentIntent && in_array($paymentIntent->status, ['canceled', 'succeeded']);
if (! $paymentIntent || $expiredPaymentIntent) {
// Create new payment intent here, as its either not created or old one has expired.
// ...
// ..
// Remember it in session so we don't create a new payment intent everytime checkout page is reloaded:
session()->put('paymentIntentId', $paymentIntent->id, now()->addDay());
}
If anyone can shed light on the flow / clarify how they've got it working in their app that would be much appreciated.
Hey @garygreen, thanks for helping out! I'll look into your reply as soon as I get to the issue.
Ok so I spoke with Stripe and they have informed me this is the best place to understand the workflow for payment intents with subscriptions: https://stripe.com/docs/billing/subscriptions/payment
(that docmentation only went live on Thursday, so its still a WIP but it's much better place to understand than the other docs because they focus on one-off payments rather than subscriptions)
The key factor is a subscription may now enter a new status called incomplete
which means it will have a payment_intent
object and a client secret - you then need to use something like Stripe.js
to go back to the browser with the payment intent secret and call the Stripe.js with:
var stripe = Stripe('key here');
// This can be found on invoice.payment_intent.client_secret
var paymentIntentSecret = 'pi_91_secret_W9';
stripe.handleCardPayment(
paymentIntentSecret
).then(function (result) {
if (result.error) {
// Display error.message in your UI.
} else {
// The payment has succeeded. Display a success message.
}
})
... that will then prompt for any 3D secure payments / other things needed to complete the payment. A webhook will then fire to note that the payment went thru invoice.payment_succeeded
Hopefully that helps. The docs explain it better but that's how I've understood it.
@garygreen that helps! We actually need to revert the behavior I added in https://github.com/laravel/cashier/pull/631 because of the incomplete
status you mentioned above. It was a good fix for 9.x because it reverted Cashier to the same behavior as before but with Payment Intents we indeed need to accommodate for a window where the customer is making the payment with 3D secure, etc. Webhooks will later update the subscription if payment failed.
Bit more info - you can also pass a expand
parameter during the creation of the subscription which allows you to access the PaymentIntent object without creating multiple API requests. This will be very useful when needing to go back to the browser/client with the payment intent secret to do any nessscary steps to complete the payment.
Example in node, but hopefully get the idea:
let subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [
{
plan: "plan_DQYe83yUGgx1LE",
},
],
expand : ["latest_invoice.payment_intent"]
});
Also I've had to override the SubcriptionBuilder
payload method in order for Stripe to not fail payment instantly if needing 3D check:
<?php
namespace App\Cashier;
use Laravel\Cashier\SubscriptionBuilder as LaravelSubscriptionBuilder;
class SubscriptionBuilder extends LaravelSubscriptionBuilder
{
/**
* Build the payload for subscription creation.
*
* @return array
*/
protected function buildPayload()
{
return array_merge(parent::buildPayload(), [
'enable_incomplete_payments' => true
]);
}
}
I've got payment intents mostly working locally now with current version of Cashier. Still a work in progress (understanding the hooks etc), but it's looking good so far.
Just wanted to share information about upcoming changes being added to Stripe on Juli 1, 2019 regarding Subscriptions.
If you are based in Europe and preparing for Strong Customer Authentication (SCA) is a new regulatory requirement coming into effect on September 14, 2019 which will impact many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase, you will need to make further changes after July 1 in order to perform authentication when saving a card for subsequent off-session payments to qualify for off-session exemptions. This API will be available by July 1. Source
@bilfeldt thanks for noting that. I'll create a separate issue for this. We'll release an update for this once the API has been released.
I've been working on a PR for this and it's coming along nicely. Follow along here: https://github.com/laravel/cashier/pull/667
The PR was merged 🎉
I too am looking into this, not only as a way to be SCA compliant but also to help prevent bank disputes. It's a pretty critical feature.
The flow seems a bit ambiougs to me. If anyone can make sense of it / clarify could you comment? It would also help Cashier implement a flow that works with plans/subscriptions.
The Payment Intent Flow (as I understand it) - using stripe js / php and with a subscription / plan
- You create a payment intent on server side when you hit the checkout page (and store this in session for future use) using the details of your plan:
$plan = \Stripe\Plan::retrieve('your-subscription-plan-id', [ 'api_key' => config('services.stripe.secret'), ]); $paymentIntent = \Stripe\PaymentIntent::create([ 'amount' => $plan->amount, 'currency' => $plan->currency, 'payment_method_types' => ['card'], ], [ 'api_key' => config('services.stripe.secret'), ]);
You can then pass the payment intent details onto your view / form so that it can be used in js when you eventually make a call to
handleCardPayment
- In javascript, you collect card details / mount etc in the usual way and instead of making a call to
stripe.createToken()
instead you callstripe.handleCardPayment()
:stripe.handleCardPayment(document.querySelector('input[name="payment_intent_secret"]').value, card).then(function(result) { if (result.error) { // Payment failed. } else { // Payment successful. } });
- This is where I get a bit confused, but I'm assuming at that point if the payment was successful, you can create a token for the user and susbcribe them to the plan. I would imagine you would want it to skip the first payment as you have already paid for the first month in the payment intent? Maybe this is where trial period comes into play? I'm also not sure where the web hook comes into play here, as I would assume if the callback is successful then you don't need to do any further checks?
- At that point on the server you can clear the payment intent - maybe this is where the web hook comes into play? Is it needed though when you can just get the payment intent when you hit the checkout page, check its status to see if its "expired" and instead use a new one:
$paymentIntent = null; if (session('paymentIntentId')) { $paymentIntent = \Stripe\PaymentIntent::retrieve(session('paymentIntentId'), [ 'api_key' => config('services.stripe.secret'), ]); } $expiredPaymentIntent = $paymentIntent && in_array($paymentIntent->status, ['canceled', 'succeeded']); if (! $paymentIntent || $expiredPaymentIntent) { // Create new payment intent here, as its either not created or old one has expired. // ... // .. // Remember it in session so we don't create a new payment intent everytime checkout page is reloaded: session()->put('paymentIntentId', $paymentIntent->id, now()->addDay()); }
If anyone can shed light on the flow / clarify how they've got it working in their app that would be much appreciated.
show this error after upgrade cashier 10 from 9
How to solve it?
Thanks in advance!
From the upgrade guide:
The
useCurrency
method has been replaced by a configuration option in the new Cashier configuration file and theusesCurrency
method has been removed.
./config/cashier.php
: https://github.com/laravel/cashier/blob/10.0/config/cashier.php#L73
💡 I would suggest reading the whole of the upgrade guide as there could be other gotchas.
usesCurrency
for this Url not geeting where and which one file nedd to change.
may this problem create cashier 10 and spark 7+
I've already asked you twice to please ask this on a support channel. Please read the upgrade guide thoroughly.
The new Payment Intents API is needed to make payments SCA compliant. This will require some significant changes in the public API of Cashier as well as making Cashier relient on webhooks to verify payments.
SCA Info: https://stripe.com/docs/strong-customer-authentication Payment Intents: https://stripe.com/docs/payments/payment-intents Billing migration guide: https://stripe.com/docs/billing/migration/strong-customer-authentication