支付宝沙箱回调必须使用公网可访问地址,本地开发需通过ngrok等内网穿透工具获取https地址填入应用网关;alipay.new参数顺序为appid、privatekey、alipublickey、isproduction,沙箱时isproduction须为false;notify handler必须原样返回“success”字符串,且验签前需剔除sign和sign_type参数。

支付宝沙箱回调地址必须能被公网访问
本地开发时,notify_url 不能填 http://localhost:8080/notify —— 支付宝服务器根本连不上。这是最常卡住的一步。
真实调试必须走内网穿透,比如 ngrok 或 natapp。免费隧道生成的地址形如 https://xxx.ngrok-free.app/notify,这个 URL 才能填进支付宝沙箱后台的「应用网关」配置里。
- 填错会导致支付宝发来的
POST请求全部 404,日志里看不到任何回调记录 - 哪怕你本地
curl -X POST https://xxx.ngrok-free.app/notify能通,也不代表支付宝能通——它走的是自己的出口 IP,某些穿透服务会拦截非浏览器 UA - 建议在 Gin 路由里加个简单
GET /health端点,用支付宝沙箱后台的「测试网关连通性」按钮验证(不是手动 curl)
alipay.New 的参数顺序和布尔值含义容易反
使用 github.com/smartwalle/alipay/v3 时,alipay.New 的签名是:func New(appID, privateKey, aliPublicKey string, isProduction bool) *Client。注意第三参数是支付宝公钥,不是你自己的公钥;第四个参数是是否生产环境,false 才是沙箱。
- 把
isProduction写成true会导致请求发往正式网关https://openapi.alipay.com/gateway.do,而沙箱密钥对不上,直接验签失败 - 漏传
aliPublicKey或传空字符串,client.VerifySign()会 panic:「public key is empty」 - v2 和 v3 版本参数顺序不同,v2 是
New(appID, privateKey, isProduction),混用会编译通过但运行时报错
notify handler 必须原样返回 success 字符串
支付宝收到你的 notify_url 响应后,只认一个东西:success(全小写、无空格、无换行、无 HTML 标签)。哪怕你返回 {"code":0,"msg":"ok"},它也认为没收到确认,会反复重试(最长 24 小时)。
- Gin 中正确写法是:
c.String(200, "success"),不要用c.JSON或c.Status - 如果 handler 里有数据库操作或耗时逻辑,务必先
c.String(200, "success")立即返回,再异步处理业务(比如用 goroutine 或消息队列),否则超时重发 - 支付宝不校验 HTTP 状态码,只看响应体内容。返回
500但 body 是success,它照样认为成功
验签前必须先 parse form 并排除 sign/sign_type 参数
支付宝回调请求是 application/x-www-form-urlencoded,但它的 sign 字段不能参与验签计算。标准做法是把所有参数读出来,删掉 sign 和 sign_type,再按字典序拼接键值对。
- 直接用
c.Request.FormValue("sign")取签名,然后调client.VerifySign(c.Request.PostForm)是错的——PostForm包含了sign,会导致验签失败 - 正确做法:先
c.Request.ParseForm(),再构造一个不含sign和sign_type的 map,最后传给VerifySign - 注意
out_trade_no和trade_status必须核对:沙箱里常见trade_status=TRADE_SUCCESS,但正式环境还可能有TRADE_FINISHED,不能只判等号
/notify 能稳定打印出一条日志再说。











