Stripe试用期后订阅支付失败:如何设置默认支付方式?
问题解决方案
核心问题
你当前的流程中,虽然通过confirmCardSetup将支付方式绑定到了客户,但未将该支付方式设置为客户的默认支付方式,导致试用期结束后订阅自动扣款时,Stripe找不到可用的支付方式,从而失败。
解决步骤
1. 前端修改:获取Payment Method ID并发送给后端
在stripe.confirmCardSetup成功回调中,提取绑定后的Payment Method ID,通过AJAX请求发送到后端,用于更新客户默认支付方式,同时补充错误状态下的按钮恢复逻辑:
var stripe = Stripe($('#public-key').val()); var elements = stripe.elements(); var style = { base: { color: "#32325d", } }; var card = elements.create("card", { style: style }); card.mount("#card-element"); //Capture modal payment button click $(document).on('click', '#submit-payment-btn', function (e) { e.preventDefault(); //Send an ajax call to backend to create a subscription $(this).prop('disabled', true); let spinnerHtml = '<div class="spinner-border text-light"></div>'; let origButtonHtml = $(this).html(); $(this).html(spinnerHtml); $.ajax({ url: $(this).data('create-sub-url'), type: 'POST', data: { csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val(), 'price_id': $(this).data('price-id'), 'coupon_id': $("#coupon_id").val() }, success: function (result) { if(!result.clientSecret) { console.log("result not okay") window.location.href = '/' } else{ const customerId = result.customerId; // 接收后端返回的客户ID $('#client-secret').val(result.clientSecret); console.log('set up confirmed'); stripe.confirmCardSetup(result.clientSecret, { payment_method: { card: card, billing_details: { name: $('#cardholder-name').val(), }, } }).then((setupResult) => { if (setupResult.error) { alert('Payment failed: ' + setupResult.error.message); // 恢复按钮状态 $('#submit-payment-btn').html(origButtonHtml); $('#submit-payment-btn').prop('disabled', false); } else { // 获取绑定成功的Payment Method ID const paymentMethodId = setupResult.setupIntent.payment_method; // 发送请求更新默认支付方式 $.ajax({ url: '/update-customer-default-payment/', type: 'POST', data: { csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val(), 'payment_method_id': paymentMethodId, 'customer_id': customerId }, success: function() { window.location.href = '/' }, error: function() { alert('Failed to set default payment method'); $('#submit-payment-btn').html(origButtonHtml); $('#submit-payment-btn').prop('disabled', false); } }); } }); } }, error: function (result){ $('#submit-payment-btn').html(origButtonHtml); $('#submit-payment-btn').prop('disabled', false); if(result.responseJSON.invalid_coupon) { if($(".coupon-error").length == 0) $('.coupon-div').append('<p class="coupon-error text-center" style="color:red">Invalid coupon code</p>') } else{ $('#payment-modal').modal('hide'); alert('Something Went Wrong!') } } }); })
2. 后端修改:返回客户ID并添加更新默认支付方式的视图
第一步:在SubscriptionCreateView中返回客户ID并完善SetupIntent配置
在post方法的返回context里添加customerId,同时给SetupIntent添加setup_future_usage参数(告知Stripe该支付方式用于离线自动扣款,是订阅到期扣款的必要配置):
class SubscriptionCreateView(View, SubscriptionCancellationMixin, CustomerMixin, StripeReferenceMixin, FetchCouponMixin): """View to Create subscription and sync with stripe""" def post(self, request, *args, **kwargs): context = {} subscription_payload = {} price_id = request.POST.get('price_id') coupon_id = request.POST.get('coupon_id') customer = self.fetch_customer(request.user) #Retrieve stripe coupon id or None if coupon_id != '': stripe_coupon_id, is_valid = self.is_coupon_valid(coupon_id) #Set payload to create a subscription subscription_payload['coupon'] = stripe_coupon_id #Send invalid coupon response if not is_valid: context['invalid_coupon'] = True response = JsonResponse(context) response.status_code = 400 return response if not customer: customer = self.stripe.Customer.create( email=request.user.email ) try: now = int(time.time()) # Cancel the previous subscription. self.cancel_subscription(customer) #Create a new subscription subscription_payload.update({ 'customer':customer.id, 'items': [{ 'price': price_id, }, ], 'trial_end': now +30, }, ) #create a setup intent setup_intent = self.stripe.SetupIntent.create( payment_method_types=["card"], customer=customer.id, setup_future_usage='off_session' # 添加此行 ) subscription = self.stripe.Subscription.create(**subscription_payload) # Sync the Stripe API return data to the database, # this way we don't need to wait for a webhook-triggered sync Subscription.sync_from_stripe_data(subscription) request.session['created_subscription_id'] = subscription.get('id') # Note we're sending the Subscription's # latest invoice and client secret # to the front end to confirm the payment context['subscriptionId'] = subscription.id context['clientSecret'] = setup_intent['client_secret'] context['customerId'] = customer.id # 添加此行 except Exception as e: response = JsonResponse({}) response.status_code = 400 return response return JsonResponse(context)
第二步:创建更新客户默认支付方式的视图
class UpdateCustomerDefaultPaymentView(View, CustomerMixin): """View to update customer's default payment method""" def post(self, request, *args, **kwargs): payment_method_id = request.POST.get('payment_method_id') customer_id = request.POST.get('customer_id') if not payment_method_id or not customer_id: return JsonResponse({'error': 'Missing required parameters'}, status=400) try: # 更新客户的默认支付方式(用于发票和订阅扣款) self.stripe.Customer.modify( customer_id, invoice_settings={ 'default_payment_method': payment_method_id } ) # 同时更新订阅的默认支付方式(双重保障) subscription_id = request.session.get('created_subscription_id') if subscription_id: self.stripe.Subscription.modify( subscription_id, default_payment_method=payment_method_id ) return JsonResponse({'success': True}) except Exception as e: return JsonResponse({'error': str(e)}, status=400)
第三步:添加路由
在项目的urls.py中添加对应路由:
from django.urls import path from .views import SubscriptionCreateView, UpdateCustomerDefaultPaymentView urlpatterns = [ # ... 其他路由 path('create-subscription/', SubscriptionCreateView.as_view(), name='create-subscription'), path('update-customer-default-payment/', UpdateCustomerDefaultPaymentView.as_view(), name='update-customer-default-payment'), ]
关键说明
setup_future_usage='off_session':必须设置此参数,否则Stripe不会允许该支付方式用于离线自动扣款(试用期结束后的扣款属于离线场景)。- 同时更新客户和订阅的默认支付方式:确保订阅在扣款时优先使用该支付方式,避免因配置不一致导致的失败。
内容的提问来源于stack exchange,提问作者LBJ33
相关产品推荐
相关产品推荐

