我的应用程序支付模式想法非常简单:拥有一个带有成员(member)区和一些特殊功能的 (laravel) 网站,而成员(member)帐户每年收费 19.90。我想整合 Stripe到我的注册流程以允许付款。付款成功后,我会创建一个订阅,然后每年自动续订此付款。
到目前为止一切顺利 - 我设法使用 Guide on how to set up a subscription 使其正常工作通过 Stripe 。但是,需要 3D Secure 身份验证的卡还不能使用,这是必须具备的。
所以我进一步阅读并使用了 PaymentIntent (API Docs)。但是,当前行为如下:
- 我创建一个 PaymentIntent 并将公钥传递给前端
- 客户输入凭据并提交
- 3D Secure Authentication 正确进行,返回 payment_method_id
- 在服务器端,我再次检索 PaymentIntent。它的状态为
成功
,我的 Stripe Dashboard 已收到付款。 - 然后我创建客户对象(使用我从 PaymentIntent 获得的付款方式),并与该客户一起创建订阅
- 订阅状态为
未完成
,似乎订阅尝试再次向客户收费,但由于第二次需要 3D 安全验证而失败。
所以我的实际问题是:我如何创建一个订阅,以某种方式通知客户已经使用我的 PaymentIntent 和我传递给它的 PaymentMethod 付款?
一些代码
创建 PaymentIntent 并将其传递给前端
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
$intent = \Stripe\PaymentIntent::create([
'amount' => '1990',
'currency' => 'chf',
]);
$request->session()->put('stripePaymentIntentId',$intent->id);
return view('payment.checkout')->with('intentClientSecret',$intent->client_secret);
点击“购买”时的前端结账
// I have stripe elements (the card input field) ready and working
// using the variable "card". The Stripe instance is saved in "stripe".
// Using "confirmCardPayment", the 3DS authentication is performed successfully.
stripe.confirmCardPayment(intentClientSecret,{
payment_method: {card: mycard},
setup_future_usage: 'off_session'
}).then(function(result) {
$('#card-errors').text(result.error ? result.error.message : '');
if (!result.error) {
submitMyFormToBackend(result.paymentIntent.payment_method);
}
else {
unlockPaymentForm();
}
});
提交后后台处理
// Get the PaymentMethod id from the frontend that was submitted
$payment_method_id = $request->get('stripePaymentMethodId');
// Get the PaymentIntent id which we created in the beginning
$payment_intent_id = $request->session()->get('stripePaymentIntentId');
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
// Get the Laravel User
$user = auth()->user();
// Firstly load Payment Intent to have this failing first if anything is not right
$intent = \Stripe\PaymentIntent::retrieve($payment_intent_id);
if ($intent instanceof \Stripe\PaymentIntent) {
// PaymentIntent loaded successfully.
if ($intent->status == 'succeeded') {
// The intent succeeded and at this point I believe the money
// has already been transferred to my account, so it's paid.
// Setting up the user with the paymentMethod given from the frontend (from
// the 3DS confirmation).
$customer = \Stripe\Customer::create([
'payment_method' => $payment_method_id,
'email' => $user->email,
'invoice_settings' => [
'default_payment_method' => $payment_method_id,
],
]);
$stripeSub = \Stripe\Subscription::create([
'customer' => $customer->id,
'items' => [
[
'plan' => env('STRIPE_PLAN_ID'),
]
],
'collection_method' => 'charge_automatically',
'off_session' => false,
]);
// If the state of the subscription would be "active" or "trialing", we would be fine
// (depends on the trial settings on the plan), but both would be ok.
if (in_array($stripeSub->status,['active','trialing'])) {
return "SUCCESS";
}
// HOWEVER the state that I get here is "incomplete", thus it's an error.
else {
return "ERROR";
}
}
}
最佳答案
我终于得到了一个为我的网站运行的有效解决方案。它是这样的:
1 - 后端:创建一个 SetupIntent
我创建了一个 SetupIntent ( SetupIntent API Docs ) 以完全覆盖结帐流程。与 PaymentIntent 的区别( PaymentIntent API Docs ) 是 PaymentIntent 从收集卡详细信息、准备付款并有效地将金额转移到帐户,而 SetupIntent 仅准备收卡,但尚未执行付款。你会得到一个 PaymentMethod ( PaymentMethod API Docs ),您可以稍后使用。
$intent = SetupIntent::create([
'payment_method_types' => ['card'],
]);
然后我将 $intent->client_secret
key 传递给我的客户端 JavaScript。
2 - 前端:使用 Elements 收集卡片详细信息
在前端,我放置了 Stripe 卡片元素来收集卡片详细信息。
var stripe = Stripe(your_stripe_public_key);
var elements = stripe.elements();
var style = { /* my custom style definitions */ };
var card = elements.create('card',{style:style});
card.mount('.my-cards-element-container');
// Add live error message listener
card.addEventListener('change',function(event) {
$('.my-card-errors-container').text(event.error ? event.error.message : '');
}
// Add payment button listener
$('.my-payment-submit-button').on('click',function() {
// Ensure to lock the Payment Form while performing async actions
lockMyPaymentForm();
// Confirm the setup without charging it yet thanks to the SetupIntent.
// With 3D Secure 2 cards, this will trigger the confirmation window.
// With 3D Secure cards, this will not trigger a confirmation.
stripe.confirmCardSetup(setup_intent_client_secret, {
payment_method: {card: card} // <- the latter is the card object variable
}).then(function(result) {
$('.my-card-errors-container').text(event.error ? event.error.message : '');
if (!result.error) {
submitPaymentMethodIdToBackend(result.setupIntent.payment_method);
}
else {
// There was an error so unlock the payment form again.
unlockMyPaymentForm();
}
});
}
function lockMyPaymentForm() {
$('.my-payment-submit-button').addClass('disabled'); // From Bootstrap
// Get the card element here and disable it
// This variable is not global so this is just sample code that does not work.
card.update({disabled: true});
}
function unlockMyPaymentForm() {
$('.my-payment-submit-button').removeClass('disabled'); // From Bootstrap
// Get the card element here and enable it again
// This variable is not global so this is just sample code that does not work.
card.update({disabled: false});
}
3 - 后端:创建客户和订阅
在后端,我收到了我从前端提交的$payment_method_id
。
首先,我们现在需要创建一个 Customer ( Customer API Docs ) 如果它还不存在。对于客户,我们将从 SetupIntent 中附加付款方式。然后,我们创建 Subscription ( Subscription API Docs ) 将从 SetupIntent 开始收费。
$customer = \Stripe\Customer::create([
'email' => $user->email, // A field from my previously registered laravel user
]);
$paymentMethod = \Stripe\PaymentMethod::retrieve($payment_method_id);
$paymentMethod->attach([
'customer' => $customer->id,
]);
$customer = \Stripe\Customer::update($customer->id,[
'invoice_settings' => [
'default_payment_method' => $paymentMethod->id,
],
]);
$subscription = \Stripe\Subscription::create([
'customer' => $customer->id,
'items' => [
[
'plan' => 'MY_STRIPE_PLAN_ID',
],
],
'off_session' => TRUE, //for use when the subscription renews
]);
现在我们有一个订阅对象。对于普通卡,状态应该是active
或 trialing
,具体取决于您在订阅中设置的试用期。然而,在处理 3D Secure 测试卡时,我得到的订阅仍处于 incomplete
状态。根据我的 Stripe 支持联系人的说法,这也可能是一个问题,因为 3D 安全测试卡尚未完全正常工作。但是我认为这也可能发生在使用某种卡的生产环境中,因此我们必须处理它。
对于状态为 incomplete
的订阅,您可以像这样从 $subscription->latest_invoice
检索最新的发票:
$invoice = \Stripe\Invoice::retrieve($subscription->latest_invoice);
在您的发票对象上,您会找到一个status
和一个hosted_invoice_url
。当 status
仍然是 open
时,我现在向用户显示他必须首先完成的托管发票的 URL。我让他在新窗口中打开链接,其中显示了一张由 stripe 托管的漂亮发票。在那里,他可以自由地再次确认他的信用卡详细信息,包括 3D Secure 工作流程。万一他在那里成功了,在您从 Stripe 重新检索订阅后,$subscription->status
会更改为 active
或 trialing
。
这是一种万无一失的策略,如果您的实现出现任何问题,只需将它们发送给 Stripe 即可完成。一定要提示用户,如果他必须两次确认他的卡,它不会扣两次,只会扣一次!
我无法创建@snieguu 解决方案的工作版本,因为我想使用 Elements 而不是单独收集信用卡详细信息,然后自己创建一个 PaymentMethod。
关于laravel - 支持3D Secure卡Stripe支付开始订阅,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59021814/