iOS App集成Stripe调用requestPayment报source/customer缺失错误排查
Stripe支付报错「Must provide source or customer」问题定位与修复
核心问题根因
- 报错
Error: Must provide source or customer:前后端参数名不匹配,iOS端调用云函数时传的用户Stripe ID参数key是customerID,云函数makeCharge接口里取值用的key是customer_id,命名规则(驼峰/下划线)不一致,导致取到的customerID为undefined。 - 硬编码customerID后报错「Cannot charge a customer that has no active card」:后端调用扣款接口时,没有传入用户在前端选中的支付方式ID,Stripe不知道要从该客户名下哪张卡扣款;如果硬编码的测试客户本身没绑定过支付方式,也会触发这个报错。
- 真卡/测试卡提示「Your card was declined」:你当前使用的是旧版
stripe.charges.create直接扣款逻辑,没有适配3DS安全验证,约旦地区发行的银行卡大多强制要求3DS验证,旧接口无法触发验证流程就会直接拒付,和区域准入规则无关,JOD是Stripe官方支持的结算货币。 - 隐性笔误风险:你在用户创建触发器里写入Firestore的Stripe客户ID字段名是
stripID(Stripe少拼了字母e),虽然当前前后端都用这个错名字段暂时能匹配,但后续维护很容易踩坑。 - 逻辑漏洞:iOS端的
didCreatePaymentResult回调里,没有把Stripe返回的支付方式ID传给后端,后端完全拿不到用户选的卡信息,自然无法完成扣款。
分步修复方案
1. 统一前后端参数命名
打开Firebase云函数index.js,把makeCharge和createEphemeralKey两个接口里取客户ID的代码,从:
const customerID = data.customer_id;
修改为和iOS端传参一致的key:
const customerID = data.customerID;
建议全链路统一参数命名规则,要么全用驼峰格式,要么全用下划线格式,避免跨端传参出现取值为空的问题。
2. 补全支付源参数传递
- 首先修改iOS端
didCreatePaymentResult回调的传参逻辑,把用户选中的支付方式ID一并传给云函数:
let data : [String : Any] = [ "total" : StripeCart.total , "customerID" : UserService.user.stripID , "sourceId": paymentResult.sourceId, // 新增:用户选中的支付方式唯一标识 "idempotency" : idempotency ]
- 然后修改云函数
makeCharge的扣款逻辑,传入支付源参数:
return stripe.charges.create({ amount: total, currency: 'JOD', customer: customerID, source: data.sourceId // 新增:指定本次扣款使用的支付方式 }, { idempotency_key: idempotency })
长期建议迁移到新版PaymentIntent API替代旧版Charges接口,可自动适配3DS验证流程,大幅降低约旦地区银行卡的拒付率。
3. 修正字段拼写(强烈建议)
把全链路的stripID字段统一修正为正确拼写stripeID:
- 云函数
createStripeCustomer里的Firestore更新代码改为:
return admin.firestore().collection('users').doc(data.id).update({ stripeID: customer.id })
- iOS端用户模型取值、传参key同步修改为
stripeID,云函数对应取值key同步调整,避免后续维护出现字段不匹配问题。
4. 支付测试注意事项
- 流程调试阶段先用Stripe官方基础测试卡
4242 4242 4242 4242,过期日期填未来任意年月,CVC填任意3位数字,先跑通完整支付流程再切换真实卡测试。 - 真卡支付如果仍被拒,去Stripe后台查看具体拒付错误码,基本都是3DS验证未触发导致,迁移到PaymentIntent接口后可自动处理验证流程,解决绝大多数拒付问题。
内容的提问来源于stack exchange,提问作者Abdullah Shaqrah
相关产品推荐
相关产品推荐

