You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

已实现订阅功能,如何调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 13:09:30