iOS集成Stripe支付requestPayment报无活跃卡片错误排查
问题根因
- 绑卡逻辑缺失核心步骤:
attatchPaymentMethod云函数直接更新客户默认支付方式配置,但从未调用Stripe的PaymentMethod附加接口将支付方式绑定到客户名下。未绑定的PaymentMethod不属于任何客户,Stripe控制台无法识别展示,自然无法用于扣款。 - 接口调用时序错误:在用户选卡的
paymentContextDidChange回调中并行异步触发绑卡、创建SetupIntent请求,既没有保证执行顺序,也没有等待请求完成就允许用户发起扣款,经常出现扣款时绑卡流程还未执行完成的情况。另外创建的SetupIntent从未做客户端确认,完全是无效逻辑。 - 扣款接口版本不匹配:使用已废弃的老版
stripe.charges.create接口且未传入支付方式参数,该接口默认只会读取客户名下老版Card/Sources体系的支付源(也就是手动在后台添加的卡类型),无法识别新版PaymentMethod体系下App端添加的卡,这是手动加卡可支付、App端加卡报错的核心原因。 - 全局变量风险:服务端定义了全局
customer变量存储客户ID,多用户并发请求时会出现客户数据串号的严重问题。
修复方案
服务端(index.js)修改
- 删除开头的全局
var customer变量,避免并发串数据 - 重写绑卡方法,先执行支付方式附加操作,再设置为默认支付方式,补充权限校验
- 废弃老版Charges扣款接口,改用PaymentIntent接口完成扣款,显式传入支付方式ID
- 可删除无实际作用的
setupIntent云函数,单次扣款场景不需要单独创建SetupIntent
对应修改后的核心代码:
// 重写绑卡方法 exports.attatchPaymentMethod = functions.https.onCall(async (data , context) => { if (!context.auth) { throw new functions.https.HttpsError('permission-denied' , '未授权访问'); } const paymentMethodId = data.paymentMethod; const customerId = data.customer; // 补全之前缺失的支付方式绑定步骤 await stripe.paymentMethods.attach(paymentMethodId, { customer: customerId, }); // 绑定成功后再设置为默认支付方式 const updatedCustomer = await stripe.customers.update( customerId, { invoice_settings: { default_payment_method: paymentMethodId } } ); return updatedCustomer; }) // 重写扣款方法 exports.makeCharge = functions.https.onCall(async (data , context) => { const customerId = data.customerID; const total = data.total; const paymentMethodId = data.paymentMethodId; const idempotency = data.idempotency; if (!context.auth) { throw new functions.https.HttpsError('permission-denied' , '非法访问') } // 用新版PaymentIntent接口完成扣款,显式指定支付方式 const paymentIntent = await stripe.paymentIntents.create({ amount: total, currency: 'JOD', customer: customerId, payment_method: paymentMethodId, off_session: true, confirm: true, }, { idempotency_key: idempotency }); return paymentIntent; })
iOS客户端修改
- 删除
paymentContextDidChange回调中所有调用云函数的逻辑,仅保留支付方式选中状态的UI更新,STPPaymentContext会通过临时密钥自动拉取可用支付方式,不需要选卡时提前触发服务端操作 - 在
didCreatePaymentResult回调中按顺序执行:先绑卡设为默认,再发起扣款,确保步骤串行执行,同时将当前选中的支付方式ID传给服务端扣款接口
对应修改后的核心代理代码:
// 精简选卡回调逻辑,只做UI更新 func paymentContextDidChange(_ paymentContext: STPPaymentContext) { if let paymentMethod = paymentContext.selectedPaymentOption { selectPaymentMethodBtn.setTitle(paymentMethod.label, for: .normal) } else { selectPaymentMethodBtn.setTitle("Select Method", for: .normal) } } // 重写支付结果回调,串行执行绑卡、扣款逻辑 func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) { let idempotency = UUID().uuidString.replacingOccurrences(of: "-", with: "") guard let paymentMethodId = paymentResult.paymentMethod?.stripeId else { completion(STPPaymentStatus.error, NSError(domain: "StripeError", code: -1, userInfo: [NSLocalizedDescriptionKey: "未获取到有效支付方式"])) return } let customerId = UserService.user.stripID // 第一步:绑定支付方式到客户 Functions.functions().httpsCallable("attatchPaymentMethod").call([ "paymentMethod": paymentMethodId, "customer": customerId ]) { _, error in if let error = error { completion(STPPaymentStatus.error, error) return } // 第二步:绑定成功后发起扣款 let chargeData : [String : Any] = [ "total" : StripeCart.total , "customerID" : customerId , "paymentMethodId": paymentMethodId, "idempotency" : idempotency ] Functions.functions().httpsCallable("makeCharge").call(chargeData) { result, error in if let error = error { completion(STPPaymentStatus.error , error) return } StripeCart.clearCart() self.tableView.reloadData() self.setupPaymentInfo() completion(STPPaymentStatus.success, nil) } } }
验证要点
- App端添加新卡后,Stripe控制台对应客户详情页的支付方式列表可看到该卡片,且标记为默认支付方式
- 发起支付不再报「无活跃卡」错误,扣款成功后控制台可看到对应的PaymentIntent支付记录
- 不需要手动在后台添加卡片,App端添加的卡片可直接完成支付流程
内容的提问来源于stack exchange,提问作者Abdullah Shaqrah
相关产品推荐
相关产品推荐

