测试模式下Stripe Payment模式请求payload中customer字段为null的解决方案
当Stripe处于测试模式时,使用payment模式(而非subscription模式)的请求payload中customer字段为null。即使在提交测试信用卡付款前提前在Stripe中创建客户,也无法改变payment模式下customer字段的状态。
请问在测试模式下使用payment模式时,有没有办法让Stripe在请求payload中包含customer ID?Stripe默认认为仅进行一次性付款而非订阅服务的客户无需创建账户,但我的应用场景并非如此。
使用Subscription模式(customer: cus_PIpLq6wFgjP2W3)
{"id": "evt_1OUDgfDcghvJaUoz0kK03Z13","object": "event","api_version": "2022-11-15","created": 1704223901,"data": {"object": {"id": "cs_test_a1PGOU8a6sLHxvrBnuFeFArDdcXN8E1rSqeAAfjB6kqVK2sBFOy6dWq4q4","object": "checkout.session","after_expiration": null,"allow_promotion_codes": null,"amount_subtotal": 999,"amount_total": 999,"automatic_tax": {"enabled": false,"status": null},"billing_address_collection": null,"cancel_url": "http://localhost:3000/cancel","client_reference_id": null,"client_secret": null,"consent": null,"consent_collection": null,"created": 1704223859,"currency": "usd","currency_conversion": null,"custom_fields": [],"custom_text": {"after_submit": null,"shipping_address": null,"submit": null,"terms_of_service_acceptance": null},"customer": "cus_PIpLq2wFgpP2W3","customer_creation": null,"customer_details": {"address": {"city": null,"country": "US","line1": null,"line2": null,"postal_code": "28036","state": null},"email": "testemail@gmail.com","name": "test1 test2","phone": null,"tax_exempt": "none","tax_ids": []},"customer_email": null,"expires_at": 1704310259,"invoice": "in_1OUDgbDcghvJaUozYJCmJyo7","invoice_creation": null,"livemode": false,"locale": null,"metadata": {},"mode": "subscription","payment_intent": null,"payment_link": null,"payment_method_collection": null,"payment_method_configuration_details": null,"payment_method_options": null,"payment_method_types": ["card"],"payment_status": "paid","phone_number_collection": {"enabled": false},"recovered_from": null,"setup_intent": null,"shipping_address_collection": null,"shipping_cost": null,"shipping_details": null,"shipping_options": [],"status": "complete","submit_type": null,"subscription": "sub_1OUDgbDcghvJaUozli1YZwLQ","success_url": "http://localhost:3000/login","total_details": {"amount_discount": 0,"amount_shipping": 0,"amount_tax": 0},"ui_mode": "hosted","url": null}},"livemode": false,"pending_webhooks": 1,"request": {"id": null,"idempotency_key": null},"type": "checkout.session.completed"}
使用Payment模式(customer: null)
{"id": "evt_1OTwhHDcghvJaUozf9VptPDi","object": "event","api_version": "2022-11-15","created": 1704158591,"data": {"object": {"id": "cs_test_a1ryh8MDmMh1q8mEgX7kExp4m2Ovx5h2WxbKUNFVK6IQhjMhIXDMLsP3BQ","object": "checkout.session","after_expiration": null,"allow_promotion_codes": null,"amount_subtotal": 2399,"amount_total": 2399,"automatic_tax": {"enabled": false,"status": null},"billing_address_collection": null,"cancel_url": "http://localhost:3000/cancel","client_reference_id": null,"client_secret": null,"consent": null,"consent_collection": null,"created": 1704158556,"currency": "usd","currency_conversion": null,"custom_fields": [],"custom_text": {"after_submit": null,"shipping_address": null,"submit": null,"terms_of_service_acceptance": null},"customer": null,"customer_details": {"address": {"city": null,"country": "US","line1": null,"line2": null,"postal_code": "28036","state": null},"email": "testemail@gmail.com","name": "Testfirst Testlast","phone": null,"tax_exempt": "none","tax_ids": []},"customer_email": null,"expires_at": 1704244956,"invoice": null,"invoice_creation": null,"livemode": false,"locale": null,"metadata": {},"mode": "payment","payment_intent": "pi_3OEmgiDcghvJaUoz1Q1TFagq","payment_link": null,"payment_method_collection": null,"payment_method_configuration_details": null,"payment_method_options": {},"payment_method_types": ["card"],"payment_status": "paid","phone_number_collection": {"enabled": false},"recovered_from": null,"setup_intent": null,"shipping_address_collection": null,"shipping_cost": null,"shipping_details": null,"shipping_options": [],"status": "complete","submit_type": null,"subscription": null,"success_url": "http://localhost:3000/login","total_details": {"amount_discount": 0,"amount_shipping": 0,"amount_tax": 0},"ui_mode": "hosted","url": null}},"livemode": false,"pending_webhooks": 1,"request": {"id": null,"idempotency_key": null},"type": "checkout.session.completed"}
loadStripe代码
async function handleClick() { const stripe = await loadStripe( 'pk_test_2fsdf979sdeUozQvee95j3F88MFXPMFrPGPpjlklmm4Y4oh2Uob7uiRdJCjlkozZIyAayjth3vv3fds898sfe2g' ) const { error } = await stripe.redirectToCheckout({ lineItems: [ { price: 'price_10FEPIMIDfsdfozETytrkq2', quantity: 1, } ], mode: 'subscription', successUrl: 'http://localhost:3000/login', cancelUrl: 'http://localhost:3000/cancel' }) }
我使用的是Stripe托管页面,因此无法灵活地将customer ID硬编码到交易中。
方法1:后端创建Checkout Session时指定customer参数
前端直接调用redirectToCheckout无法指定customer参数,这是Stripe的安全限制。你需要通过后端API创建Checkout Session,传入提前创建好的Customer ID,同时设置customer_creation参数控制客户创建逻辑。示例Node.js后端代码:
const stripe = require('stripe')('sk_test_your_secret_key'); const createCheckoutSession = async (req, res) => { const customerId = 'cus_your_existing_customer_id'; // 提前在Stripe创建的客户ID const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { price: 'price_10FEPIMIDfsdfozETytrkq2', quantity: 1, }, ], mode: 'payment', customer: customerId, customer_creation: 'if_required', // 若客户不存在则创建,存在则复用 success_url: 'http://localhost:3000/login', cancel_url: 'http://localhost:3000/cancel', }); res.json({ url: session.url }); };
前端修改为调用后端接口获取Session URL后跳转:
async function handleClick() { const response = await fetch('/create-checkout-session', { method: 'POST' }); const { url } = await response.json(); window.location.href = url; }
这种方式创建的Payment模式Session会关联指定的Customer ID,Webhook事件中customer字段会显示对应的ID。
方法2:通过customer_email自动关联现有客户
如果你不想提前创建客户,可以在后端创建Session时传入customer_email参数,Stripe会自动匹配系统中已存在的同邮箱客户并关联到Session中。示例:
const session = await stripe.checkout.sessions.create({ // 其他参数不变 customer_email: 'testemail@gmail.com', mode: 'payment', });
若Stripe系统中已有该邮箱对应的客户,Session会自动关联,Webhook事件的customer字段会填充对应ID;如果没有,会自动创建新客户并关联。
注意事项
- 测试模式和生产模式的逻辑一致,上述方法在两种模式下都适用。
- 必须通过后端创建Checkout Session才能实现客户关联,前端直接调用无法满足需求。
内容的提问来源于stack exchange,提问作者henry sneed

