Flo*_*ler 4 laravel stripe-payments
我的应用程序的支付模型想法非常简单:拥有一个带有会员区域和一些特殊功能的 (laravel) 网站,而会员帐户的费用为 19.90/年。我想将Stripe集成到我的注册流程中以允许付款。付款成功后,我会创建一个订阅,然后每年都会自动续订此付款。
到目前为止一切顺利 - 我设法使用有关如何设置Stripe 订阅的指南使其正常工作。然而,需要3D安全认证的卡还不能使用,而这是必须具备的。
所以我进一步阅读并使用了PaymentIntent(API 文档)。但是,当前的行为如下:
succeeded,并且付款已在我的 Stripe 仪表板上收到。incomplete,并且订阅似乎尝试再次向客户收费,但由于第二次需要进行 3D 安全验证而失败。所以我的实际问题是:如何创建一个订阅,以某种方式注意到客户已经使用我的 PaymentIntent 和我传递给它的 PaymentMethod 进行了付款?
\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);
Run Code Online (Sandbox Code Playgroud)
// 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();
}
});
Run Code Online (Sandbox Code Playgroud)
// 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";
}
}
}
Run Code Online (Sandbox Code Playgroud)
我终于得到了一个适用于我的网站的可行解决方案。事情是这样的:
我创建了一个SetupIntent(SetupIntent API 文档)来完全涵盖结账流程。与PaymentIntent(PaymentIntent API 文档)的区别在于,PaymentIntent 从收集卡详细信息、准备付款并有效地将金额转入帐户,而 SetupIntent 仅准备收卡,但尚未执行付款。您将从中获得一个PaymentMethod ( PaymentMethod API Docs ),您可以稍后使用。
$intent = SetupIntent::create([
'payment_method_types' => ['card'],
]);
Run Code Online (Sandbox Code Playgroud)
然后我将$intent->client_secret密钥传递给客户端 JavaScript。
在前端,我放置了 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});
}
Run Code Online (Sandbox Code Playgroud)
在后端,我收到了$payment_method_id我从前端提交的数据。首先,如果客户尚不存在,我们现在需要创建一个客户(客户 API 文档)。对于客户,我们将附加来自 SetupIntent 的付款方式。然后,我们创建订阅(订阅 API 文档),它将从 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
]);
Run Code Online (Sandbox Code Playgroud)
现在我们有一个 Subscription 对象。对于普通卡,状态应为active或trialing,具体取决于您在订阅上设置的试用天数。然而,在处理 3D Secure 测试卡时,我的订阅仍然处于状态incomplete。根据我的 Stripe 支持联系人的说法,这也可能是一个问题,因为 3D Secure 测试卡尚未完全正常工作。不过,我认为这种情况也可能发生在使用某种卡的生产环境中,因此我们必须处理它。
在具有状态的订阅中,incomplete您可以从中检索最新的发票,$subscription->latest_invoice如下所示:
$invoice = \Stripe\Invoice::retrieve($subscription->latest_invoice);
Run Code Online (Sandbox Code Playgroud)
在您的发票对象上,您将找到 astatus和 a hosted_invoice_url。当status仍然 时open,我现在向用户提供他必须首先完成的托管发票的 URL。我让他在新窗口中打开链接,其中显示了由 stripe 托管的漂亮发票。在那里,他可以再次确认他的信用卡详细信息,包括 3D Secure 工作流程。如果他在那里成功,则在您从 Stripe 重新检索订阅后或之后$subscription->status进行更改。activetrialing
这是某种万无一失的策略,如果您的实施出现任何问题,只需将其发送到 Stripe 即可完成。请务必提示用户,如果他必须确认他的卡两次,则不会收取两次费用,而只会收取一次费用!
我无法创建 @snieguu 解决方案的工作版本,因为我想使用 Elements,而不是单独收集信用卡详细信息,然后自己创建 PaymentMethod。
| 归档时间: |
|
| 查看次数: |
5033 次 |
| 最近记录: |