Go调用Stripe支付需用stripe-go SDK创建PaymentIntent,设idempotency_key防重扣;Webhook验签必用Signing Secret且确保body未被中间件读取;退款/订阅/发票复用统一参数模式,DB更新须与Stripe调用事务一致。

Go 语言调用 Stripe API 实现支付,核心在于正确构造请求、签名 Webhook、处理异步事件——不是简单发个 POST 就完事,漏掉 Stripe-Signature 验证或忽略 idempotency_key,线上就可能重复扣款或验签失败。
用 stripe-go 官方 SDK 发起支付(PaymentIntent)
别手写 HTTP 请求。Stripe 官方维护的 stripe-go SDK 已封装好认证、重试、序列化和错误映射,直接用它创建 PaymentIntent 最稳妥。
- 安装:
go get github.com/stripe/stripe-go/v76(注意 v76 是当前稳定版,v77+ 要求 Go 1.21+) - 初始化客户端时必须传入 secret key,且不能硬编码:用环境变量读取
os.Getenv("STRIPE_SECRET_KEY") - 创建
PaymentIntent时,Amount单位是「最小货币单位」(如 USD 是分),Currency必须小写("usd",不是"USD") - 务必设置
IdempotencyKey(例如用 UUID v4),否则网络重试可能导致用户被多次扣款
pi, err := paymentintent.New(&stripe.PaymentIntentParams{
Amount: stripe.Int64(2000), // $20.00
Currency: stripe.String("usd"),
Description: stripe.String("Premium plan"),
IdempotencyKey: stripe.String(uuid.NewString()),
})
前端传 client_secret 后,后端如何确认支付完成
client_secret 只用于前端调用 Stripe.js 的 confirmCardPayment,它本身不证明支付成功;后端必须监听 Webhook 或主动查 PaymentIntent 状态,不能信前端传来的任何 status 字段。
- 最可靠方式:在 Webhook endpoint(如
/webhook/stripe)中监听payment_intent.succeeded事件 - 收到事件后,先用
stripe.Webhook.ConstructEvent验证签名,密钥用 Dashboard 里生成的Webhook Signing Secret,不是 secret key - 验证通过后再从
event.Data.Object解析出PaymentIntent,检查其Status是否为"succeeded",并核对Amount和业务订单是否匹配 - 若跳过验签(比如只比对
id),攻击者可伪造请求触发发货或开通服务
Webhook 验证失败常见原因
本地调试时 stripe-cli 转发 Webhook 到 localhost 很方便,但验签失败往往卡在三个地方:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- HTTP body 被中间件(如 Gin 的
gin.Recovery()或自定义日志中间件)提前读取过,导致stripe.Webhook.ConstructEvent拿到空 body —— 必须确保原始io.ReadCloser未被消费 - Webhook endpoint 返回非 2xx 状态码(比如 500 或 400),Stripe 会重试 3 次,但你的日志可能只看到第一次失败
- Dashboard 中配置的 Webhook URL 域名没加
https://,或用了http://(Stripe 强制要求 HTTPS) - 用
stripe-cli本地测试时,没运行stripe listen --forward-to localhost:8080/webhook/stripe,或者没把打印出的SigningSecret正确配进代码
退款、订阅、发票等进阶操作怎么复用同一套结构
所有 Stripe 资源(Refund、Subscription、Invoice)都遵循统一模式:用对应资源的 New / Get / Update 函数,参数结构体命名规则一致(如 RefundParams、SubscriptionParams)。
- 退款要传
PaymentIntent的ID(不是客户端拿到的client_secret),并指定Amount(可部分退款) - 创建订阅前,必须先有
Price(对应 Dashboard 里的 pricing table),用price_...ID 创建Subscription,而不是直接传金额 - 发票(
Invoice)默认是自动结算的,如果想手动开票,得关掉CollectionMethod: "send_invoice"并调用Invoice.Pay - 所有操作都支持
Expand字段(如Expand: []string{"latest_invoice"}),避免多次 API 调用
真正容易被忽略的是幂等性与事务边界:比如用户订阅成功后要同步更新本地数据库,这个 DB 写入必须和 Stripe 调用放在同一个事务里(或至少用唯一索引+重试机制兜底),否则可能出现「Stripe 订单成功但本地没记录」的情况。

















