已实现订阅功能,如何调整Stripe Setup Intent支持印度Recurring Payments?
问题描述
我的项目基于Next.js + Python开发,订阅功能已完成且运行正常,但不支持印度地区的定期支付(Recurring Payments),给印度用户使用带来了问题。我尝试修改后端创建Setup Intent的代码,但仍无法正常工作,求排查问题。
原后端Setup Intent创建代码
def create(self, request, *args, **kwargs): company = get_object_or_404( Company, random_company_id=self.kwargs.pop("random_company_id") ) self.check_object_permissions(self.request, company) if company.stripe_customer_id: intent = stripe.SetupIntent.create(customer=company.stripe_customer_id) else: try: company_owner = get_company_owner(company) customer = stripe.Customer.create( name=company.company_name, email=company_owner.user.email, ) customer_id = customer.get("id", None) company.stripe_customer_id = customer_id company.save() intent = stripe.SetupIntent.create(customer=customer_id) except Exception as e: # customer wasn't created correctly, return error return Response(status=HTTP_400_BAD_REQUEST) return Response({"client_secret": intent.client_secret}, status=HTTP_200_OK)
前端确认支付方式代码
export const saveCardWithIntent = ( card, { companyId, companyName, address1, address2, country, city, state, postalCode }, setAsDefault, stripe, pricePlan ) => async (dispatch) => { dispatch({ type: TYPES.SAVE_INTENT_INITIALIZING }); const intentUrl = `${NEXT_PUBLIC_API_URL}payments/stripe/customers/${companyId}/setup-intent/`; const createCustomerUrl = `${NEXT_PUBLIC_API_URL}payments/stripe/customers/${companyId}/create/`; const priceParams = { interval: pricePlan.interval, interval_count: pricePlan.intervalCount, amount: Math.round(pricePlan.amount / 100), }; const { data: { clientSecret }, } = await axios.post(intentUrl, priceParams); const paymentMethodData = { payment_method: { type: 'card', card: card, billing_details: { name: companyName, address: { city, country, line1: address1, line2: address2, state, postal_code: postalCode, }, }, }, }; const result = await stripe.confirmCardSetup(clientSecret, paymentMethodData); // // Throw if error key exists! if (result.error) throw result.error; const body = { paymentMethodId: result.setupIntent.payment_method, setAsDefault, }; // TODO: Create separate dispatches for customer creation for tracking and more granular debug/error reporting: const postResponse = await axios.post(createCustomerUrl, body); dispatch({ type: TYPES.SAVE_INTENT_SUCCESS, payload: { postResponse, body } }); // return body.paymentMethodId;
修改后的后端代码(仍无法正常工作)
def create(self, request, *args, **kwargs): company = get_object_or_404( Company, random_company_id=self.kwargs.pop("random_company_id") ) self.check_object_permissions(self.request, company) interval = request.data.get("interval", "month") interval_count = request.data.get("interval_count", 1) amount = request.data.get("amount", 100) start_date = int(time.time()) payment_method_options_data = { 'card' : { 'mandate_options' : { 'reference': '101011', 'amount': amount, 'currency': 'usd', 'amount_type': 'fixed', 'start_date': start_date, 'interval': interval, 'interval_count': interval_count, 'supported_types': ['india'] } } } if company.stripe_customer_id: intent = stripe.SetupIntent.create(customer=company.stripe_customer_id, payment_method_options=payment_method_options_data) else: try: company_owner = get_company_owner(company) customer = stripe.Customer.create( name=company.company_name, email=company_owner.user.email, ) customer_id = customer.get("id", None) company.stripe_customer_id = customer_id company.save() intent = stripe.SetupIntent.create(customer=customer_id, payment_method_options=payment_method_options_data) except Exception as e: # customer wasn't created correctly, return error return Response(status=HTTP_400_BAD_REQUEST) return Response({"client_secret": intent.client_secret}, status=HTTP_200_OK)
问题分析与修复方案
你的修改存在几个关键错误,导致印度地区定期支付无法正常工作,具体如下:
1. 货币类型错误
印度地区的Stripe定期支付必须使用印度卢比(INR),不能用USD。Stripe对印度的支付有本地合规要求,强制使用本地货币进行定期扣款。
修复:根据用户所在国家(前端传入的country参数)判断,若为IN则设置currency: 'inr',否则保留原货币。
2. Mandate参数配置错误
supported_types是无效参数:Stripe的mandate_options中没有这个字段,属于错误配置,需删除。start_date不能是当前时间:印度的eMandate要求扣款起始日期必须是未来的日期(至少晚于创建日期1天),你用int(time.time())设置为当前时间,会导致验证失败。reference硬编码:reference需要是唯一标识(比如公司ID+订阅周期),不能固定为101011,否则会导致重复授权问题。
3. 金额单位错误
前端传递的amount是Math.round(pricePlan.amount / 100),但Stripe的金额参数是最小货币单位:
- 比如USD的1美元是100美分,传100;INR的1卢比是100派萨,传100。
- 你当前的逻辑是把金额除以100,相当于把实际金额缩小了100倍,导致金额不符合要求。
修复:前端直接传递pricePlan.amount(确保它已经是最小单位),或者后端乘以100转换为最小单位。
4. 未针对印度用户做差异化配置
你现在对所有用户都应用了印度的mandate配置,这会导致非印度用户的支付流程出错。应该根据用户的国家(前端传入的country)来决定是否添加印度专属的mandate配置。
5. 错误处理过于宽泛
原代码中捕获Exception后直接返回400,但没有打印错误信息,无法排查具体问题。建议在except块中打印e的内容,方便调试。
修复后的示例代码
后端代码(关键修改部分)
def create(self, request, *args, **kwargs): company = get_object_or_404( Company, random_company_id=self.kwargs.pop("random_company_id") ) self.check_object_permissions(self.request, company) interval = request.data.get("interval", "month") interval_count = request.data.get("interval_count", 1) # 注意:这里假设前端传递的amount已经是最小单位(比如INR的派萨、USD的美分) amount = request.data.get("amount", 100) # 获取前端传入的用户国家 country = request.data.get("country", "") payment_method_options_data = {} # 仅对印度用户配置专属mandate if country.upper() == "IN": # 起始日期设置为明天(当前时间+86400秒) start_date = int(time.time()) + 86400 payment_method_options_data = { 'card': { 'mandate_options': { 'reference': f"{company.random_company_id}-{interval}-{interval_count}", 'amount': amount, 'currency': 'inr', 'amount_type': 'fixed', 'start_date': start_date, 'interval': interval, 'interval_count': interval_count, } } } try: if company.stripe_customer_id: intent = stripe.SetupIntent.create( customer=company.stripe_customer_id, payment_method_options=payment_method_options_data if payment_method_options_data else None ) else: company_owner = get_company_owner(company) customer = stripe.Customer.create( name=company.company_name, email=company_owner.user.email, # 可选:设置客户国家,帮助Stripe做合规判断 address={"country": country} if country else None ) company.stripe_customer_id = customer.id company.save() intent = stripe.SetupIntent.create( customer=customer.id, payment_method_options=payment_method_options_data if payment_method_options_data else None ) return Response({"client_secret": intent.client_secret}, status=HTTP_200_OK) except Exception as e: # 打印错误信息,方便调试 print(f"Setup Intent creation error: {str(e)}") return Response({"error": str(e)}, status=HTTP_400_BAD_REQUEST)
前端代码(关键修改部分)
// 在priceParams中添加country参数 const priceParams = { interval: pricePlan.interval, interval_count: pricePlan.intervalCount, amount: pricePlan.amount, // 直接传递最小单位的金额,不再除以100 country: country, // 把用户的国家传递给后端 };
内容的提问来源于stack exchange,提问作者FE-Smile
相关产品推荐
相关产品推荐

