
本文已同步发布到微信公众号「人言兑」
👈 扫描二维码关注,第一时间获取更新!
作为一名独立开发者,接入微信支付可能是你项目变现的关键一步。但面对复杂的文档、各种证书、密钥和回调机制,很多人会在第一步就卡住。
本教程基于微信支付 APIv3 和官方 Go SDK,从零开始带你完成 JSAPI 支付的完整接入。我们将使用最新的微信支付公钥模式(适用于 2023 年后新申请的商户号),相比传统的平台证书模式更加简洁。
你将学到:
前置要求:
在开始之前,请确保已完成以下准备工作:
在《微信支付系列教程》系列文章中,我们将详细介绍如何从0到1在自己的网站中接入微信支付。以下是该系列文章的全部内容:
JSAPI 支付是微信支付为微信内置浏览器提供的支付能力。用户在微信客户端内访问商户的网页,选购商品后可直接在微信内完成支付,无需跳出微信。
根据官方文档定义:
JSAPI支付,提供商户在微信客户端内部浏览器网页中使用微信支付收款的能力。
| 特性 | 说明 |
|---|---|
| 环境限制 | 必须在微信内置浏览器中使用 |
| 用户标识 | 需要获取用户的 OpenID |
| 支付流程 | 用户点击支付 → 调起微信收银台 → 输入密码 → 支付完成 |
| 回调机制 | 支持前端回调 + 后端异步通知双重确认 |
| 对比项 | JSAPI 支付 | H5 支付 |
|---|---|---|
| 使用环境 | 微信内置浏览器 | 外部浏览器(Safari/Chrome等) |
| 用户标识 | 需要 OpenID | 不需要 |
| 体验流畅度 | 优(无需跳出) | 一般(需跳转微信) |
| 接入复杂度 | 一般(需授权) | 较高 (但需要先接入Native支付后单独申请开通) |
| 费率 | 相同 | 相同 |
在微信以外的浏览器下单会出现一个你可能比较熟悉的页面提示:
PC 网站推荐用 Native 支付,但也可以用 JSAPI 支付:
| PC 网站方案 | 实现方式 | 用户体验 |
|---|---|---|
| 推荐:Native 支付 | 展示支付二维码,用户微信扫码 | 扫码后直接支付,体验好 |
| 可选:JSAPI 支付 | 展示网页二维码,用户微信扫码打开网页 | 多一步操作,但可以展示更多信息 |
根据官方文档,JSAPI 支付主要适用于以下场景:
用户通过公众号菜单或推文进入商户网页,选购商品后支付。
流程:
用户扫描商户二维码,在微信中打开网页完成支付。
流程:
用户通过好友分享的链接进入商品页,直接购买。
官方文档提示:
用户可以通过公众号、扫一扫、分享链接等方式在微信客户端内部浏览器中打开商户页面,完成支付。
JSAPI 支付必须将商户号与公众号/小程序 AppID 绑定:
相关阅读推荐: 商户号绑定APPID操作指南
只有配置了授权目录的网页才能调起支付:
https://your-domain.com/)/ 结尾,且是完整的 URL所有使用JS API方式发起支付请求的链接地址,都必须在当前页面所配置的支付授权目录之下。下单前需要调用【 网页授权获取用户信息 】接口获取到用户的Openid
在开始开发前,你需要准备以下参数:
| 参数名称 | 参数说明 | 获取方式 |
|---|---|---|
| APPID | 公众账号ID | 公众平台/开放平台查看 |
| MCHID | 商户号 | 商户平台查看 |
| 商户API证书 | 用于API请求签名 | 商户平台下载 |
| 微信支付公钥 | 用于验证微信支付回调签名 | 通过API获取或手动下载 |
| APIv3密钥 | 用于加密敏感数据和验证签名 | 商户平台设置 |
获取路径:
pub_key.pemapiclient_key.pem)、证书文件(apiclient_cert.pem)openssl x509 -in cert/apiclient_cert.pem -noout -serial | cut -d= -f2 | tr '[:upper:]' '[:lower:]'openssl rand -hex 16详细参数说明可参考: 普通商户模式开发必要参数说明 。
设置「JS接口安全域名」后,开发者可在该域名下调用微信开放的JS接口,可以设置5个,添加父级域名后子域名自动可用。
而「网页授权域名」是用户在授权给本账号时的重定向地址的域名,微信会将授权数据传给一个回调地址,该地址的域名是需要区分子域名的,最多只能配2个。
配置域名时都需要验证域名所有者身份,具体验证方法如下:
接入JSAPI支付关于域名相关的配置操作,这里再次总结一下:
wechatpay-jsapi-demo/
├── .env # 环境变量配置(不提交Git)
├── .env.example # 配置示例
├── go.mod # Go模块
├── main.go # 入口文件
├── config/
│ └── config.go # 配置加载
├── handlers/
│ ├── auth.go # 微信授权(获取OpenID)
│ └── payment.go # 支付相关接口
├── templates/
│ ├── index.html # 商品列表页
│ ├── pay.html # 支付确认页
│ └── success.html # 支付成功页
└── certs/
├── apiclient_key.pem # 商户私钥
└── wechatpay_pub_key.pem # 微信支付公钥
创建 go.mod:
module wechatpay-jsapi-demo
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/joho/godotenv v1.5.1
github.com/wechatpay-apiv3/wechatpay-go v0.2.21
)
安装依赖:
创建 .env 文件:
# 服务器配置
SERVER_PORT=8080
BASE_URL=https://your-domain.com
# 微信支付配置
MCH_ID=1900000000
MCH_API_V3_KEY=YourAPIv3KeyHere
MCH_CERT_SERIAL_NO=YourCertSerialNo
MCH_PRIVATE_KEY_PATH=./certs/apiclient_key.pem
# 微信支付公钥配置(新商户必填)
WECHAT_PAY_PUBLIC_KEY_ID=PUB_KEY_ID_xxxxxxxx
WECHAT_PAY_PUBLIC_KEY_PATH=./certs/wechatpay_pub_key.pem
# 公众号配置
APP_ID=wx0000000000000000
APP_SECRET=YourAppSecret
# 支付配置
NOTIFY_URL=https://your-domain.com/api/pay/notify
ORDER_EXPIRE_MINUTES=30
根据官方文档,JSAPI 支付的整体业务流程分为五个阶段:
商户后端调用 JSAPI 下单接口,获取预支付 ID(prepay_id)。
关键参数说明:
| 参数 | 说明 | 注意事项 |
|---|---|---|
openid | 用户标识 | 必须通过网页授权获取 |
out_trade_no | 商户订单号 | 同一商户号下唯一,6-32字符 |
description | 商品描述 | 用户账单可见,127字符以内 |
amount.total | 订单金额 | 单位为分,不能有小数 |
time_expire | 支付结束时间 | RFC3339格式,如2025-02-14T10:00:00+08:00 |
notify_url | 回调地址 | 必须外网可访问,HTTPS |
prepay_id 有效期: 2小时,过期需重新下单。
前端通过微信内置对象 WeixinJSBridge 调起支付。
调起支付参数:
| 参数 | 说明 |
|---|---|
appId | 公众号 AppID |
timeStamp | 当前时间戳(秒级) |
nonceStr | 随机字符串 |
package | prepay_id=xxx |
signType | 固定值 RSA |
paySign | 签名值 |
商户调起支付前,请确保已在商户平台配置好 JSAPI 支付授权目录(只有配置了 JSAPI 支付授权目录的网页才能调起支付)。
用户在微信收银台完成支付或取消支付。
前端回调结果:
| err_msg | 含义 | 处理方式 |
|---|---|---|
get_brand_wcpay_request:ok | 支付成功 | 调后端查单确认 |
get_brand_wcpay_request:cancel | 用户取消 | 展示取消页面 |
get_brand_wcpay_request:fail | 支付失败 | 展示失败原因 |
重要提示:
前端回调并不保证它绝对可靠,不可只依赖前端回调判断订单支付状态,订单状态需以后端查询订单和支付成功回调通知为准。
微信支付异步通知商户支付结果。
通知特点:
{"code": "SUCCESS"} 表示处理成功根据官方文档,订单状态流转如下:
生成订单 → NOTPAY(未支付)
↓
支付成功 → SUCCESS(支付成功)→ 可申请退款 → REFUND(转入退款)
↓
支付失败/超时 → CLOSED(已关闭)
终态说明:
SUCCESS:支付成功,可退款CLOSED:已关闭,不可再支付REFUND:已退款由于文章篇幅不易太长,完整本地可运行的demo项目代码我已打包上传,
需要完整项目源码的可以关注我的公众号「 人言兑 」,私信发送「jsapi demo」即可获取。
package config
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/joho/godotenv"
)
type Config struct {
ServerPort string
BaseURL string
MchID string
MchAPIv3Key string
MchCertSerialNo string
MchPrivateKeyPath string
WechatPayPublicKeyID string
WechatPayPublicKeyPath string
AppID string
AppSecret string
NotifyURL string
OrderExpireMinutes int
}
var GlobalConfig *Config
func Load() *Config {
if err := godotenv.Load(); err != nil {
log.Println("Warning: .env file not found")
}
config := &Config{
ServerPort: getEnv("SERVER_PORT", "8080"),
BaseURL: getEnv("BASE_URL", ""),
MchID: getEnv("MCH_ID", ""),
MchAPIv3Key: getEnv("MCH_API_V3_KEY", ""),
MchCertSerialNo: getEnv("MCH_CERT_SERIAL_NO", ""),
MchPrivateKeyPath: getEnv("MCH_PRIVATE_KEY_PATH", "./certs/apiclient_key.pem"),
WechatPayPublicKeyID: getEnv("WECHAT_PAY_PUBLIC_KEY_ID", ""),
WechatPayPublicKeyPath: getEnv("WECHAT_PAY_PUBLIC_KEY_PATH", "./certs/wechatpay_pub_key.pem"),
AppID: getEnv("APP_ID", ""),
AppSecret: getEnv("APP_SECRET", ""),
NotifyURL: getEnv("NOTIFY_URL", ""),
OrderExpireMinutes: getEnvAsInt("ORDER_EXPIRE_MINUTES", 30),
}
if err := config.validate(); err != nil {
log.Fatalf("Config validation failed: %v", err)
}
config.MchPrivateKeyPath = toAbsPath(config.MchPrivateKeyPath)
config.WechatPayPublicKeyPath = toAbsPath(config.WechatPayPublicKeyPath)
GlobalConfig = config
return config
}
func (c *Config) validate() error {
required := []string{"MCH_ID", "MCH_API_V3_KEY", "MCH_CERT_SERIAL_NO",
"WECHAT_PAY_PUBLIC_KEY_ID", "WECHAT_PAY_PUBLIC_KEY_PATH",
"APP_ID", "APP_SECRET", "NOTIFY_URL", "BASE_URL"}
for _, field := range required {
if getEnv(field, "") == "" {
return fmt.Errorf("%s is required", field)
}
}
return nil
}
func toAbsPath(path string) string {
if !filepath.IsAbs(path) {
if abs, err := filepath.Abs(path); err == nil {
return abs
}
}
return path
}
func getEnv(key, defaultVal string) string {
if v := os.Getenv(key); v != "" {
return v
}
return defaultVal
}
func getEnvAsInt(key string, defaultVal int) int {
if v := os.Getenv(key); v != "" {
var i int
if _, err := fmt.Sscanf(v, "%d", &i); err == nil {
return i
}
}
return defaultVal
}
package handlers
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"wechatpay-jsapi-demo/config"
"github.com/gin-gonic/gin"
)
type AuthHandler struct {
cfg *config.Config
}
func NewAuthHandler() *AuthHandler {
return &AuthHandler{cfg: config.GlobalConfig}
}
// Auth 发起微信授权
func (h *AuthHandler) Auth(c *gin.Context) {
redirectURI := url.QueryEscape(h.cfg.BaseURL + "/auth/callback")
state := "STATE123" // 生产环境应使用随机数并验证
authURL := fmt.Sprintf(
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=%s#wechat_redirect",
h.cfg.AppID, redirectURI, state,
)
/*
目前网页授权有两个 scope,分别是:snsapi_base 和 snsapi_userinfo,解释如下:
- snsapi_base:用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。(不会弹出信息确认框,仅能获取用户 openid 等信息,无法获取昵称头像)
- snsapi_userinfo:用来获取用户的基本信息的。但这种授权需要用户手动同意。(由于用户同意过,所以无须依赖用户关注服务号,就可在授权后获取该用户的基本信息)
*/
c.Redirect(http.StatusFound, authURL)
}
// Callback 授权回调
func (h *AuthHandler) Callback(c *gin.Context) {
code := c.Query("code")
if code == "" {
c.String(http.StatusBadRequest, "Authorization failed")
return
}
openID, err := h.getOpenID(code)
if err != nil {
c.String(http.StatusInternalServerError, "Failed to get openid")
return
}
// 存储到 cookie
c.SetCookie("openid", openID, 3600, "/", "", false, true)
redirect := c.DefaultQuery("redirect", "/pay")
c.Redirect(http.StatusFound, redirect)
}
func (h *AuthHandler) getOpenID(code string) (string, error) {
tokenURL := fmt.Sprintf(
"https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
h.cfg.AppID, h.cfg.AppSecret, code,
)
resp, err := http.Get(tokenURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var result struct {
OpenID string `json:"openid"`
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
if result.ErrCode != 0 {
return "", fmt.Errorf("wechat error: %s", result.ErrMsg)
}
return result.OpenID, nil
}
// GetOpenID 从 cookie 获取
func GetOpenID(c *gin.Context) (string, error) {
openid, err := c.Cookie("openid")
if err != nil {
return "", fmt.Errorf("not authorized")
}
return openid, nil
}
package handlers
import (
"context"
"crypto/rsa"
"fmt"
"log"
"net/http"
"time"
"wechatpay-jsapi-demo/config"
"github.com/gin-gonic/gin"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
)
type PaymentHandler struct {
client *core.Client
cfg *config.Config
svc jsapi.JsapiApiService
wechatPayPublicKey *rsa.PublicKey
}
func NewPaymentHandler(client *core.Client, pubKey *rsa.PublicKey) *PaymentHandler {
return &PaymentHandler{
client: client,
cfg: config.GlobalConfig,
svc: jsapi.JsapiApiService{Client: client},
wechatPayPublicKey: pubKey,
}
}
// Index 商品页
func (h *PaymentHandler) Index(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
}
// PayPage 支付页
func (h *PaymentHandler) PayPage(c *gin.Context) {
openid, err := GetOpenID(c)
if err != nil {
redirect := url.QueryEscape("/pay")
c.Redirect(http.StatusFound, "/auth?redirect="+redirect)
return
}
c.HTML(http.StatusOK, "pay.html", gin.H{"openid": openid})
}
// CreateOrder 创建订单
func (h *PaymentHandler) CreateOrder(c *gin.Context) {
var req struct {
ProductID string `json:"product_id"`
ProductName string `json:"product_name"`
Price float64 `json:"price"` // 单位:元(实际业务中最好不要使用浮点数避免精度问题,这里只是方便演示)
OpenID string `json:"openid"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 金额转换:元 → 分
amount := int64(req.Price * 100)
outTradeNo := generateOrderNo()
expireTime := time.Now().Add(time.Duration(h.cfg.OrderExpireMinutes) * time.Minute)
resp, _, err := h.svc.PrepayWithRequestPayment(context.Background(),
jsapi.PrepayRequest{
Appid: core.String(h.cfg.AppID),
Mchid: core.String(h.cfg.MchID),
Description: core.String(req.ProductName),
OutTradeNo: core.String(outTradeNo),
Attach: core.String(req.ProductID),
NotifyUrl: core.String(h.cfg.NotifyURL),
Amount: &jsapi.Amount{
Total: core.Int64(amount),
},
Payer: &jsapi.Payer{
Openid: core.String(req.OpenID),
},
TimeExpire: core.Time(expireTime),
},
)
if err != nil {
log.Printf("Create order failed: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "create order failed"})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"out_trade_no": outTradeNo,
"appId": resp.Appid,
"timeStamp": resp.TimeStamp,
"nonceStr": resp.NonceStr,
"package": resp.Package,
"signType": resp.SignType,
"paySign": resp.PaySign,
})
}
// Notify 支付回调
func (h *PaymentHandler) Notify(c *gin.Context) {
handler, err := notify.NewRSANotifyHandler(
h.cfg.MchAPIv3Key,
verifiers.NewSHA256WithRSAPubkeyVerifier(h.cfg.WechatPayPublicKeyID, *h.wechatPayPublicKey),
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": "FAIL"})
return
}
transaction := new(payments.Transaction)
if _, err := handler.ParseNotifyRequest(context.Background(), c.Request, transaction); err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"code": "FAIL"})
return
}
// 处理业务逻辑
if transaction.TradeState != nil && *transaction.TradeState == "SUCCESS" {
log.Printf("Payment success: %s, amount: %d",
*transaction.OutTradeNo, *transaction.Amount.Total)
// TODO: 更新订单状态(注意幂等性)
}
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"})
}
func generateOrderNo() string {
return fmt.Sprintf("D%s%06d",
time.Now().Format("20060102150405"),
time.Now().Nanosecond()%1000000)
}
package main
import (
"context"
"fmt"
"log"
"wechatpay-jsapi-demo/config"
"wechatpay-jsapi-demo/handlers"
"github.com/gin-gonic/gin"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
)
func main() {
cfg := config.Load()
// 加载证书
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(cfg.MchPrivateKeyPath)
if err != nil {
log.Fatalf("load private key failed: %v", err)
}
wechatPayPublicKey, err := utils.LoadPublicKeyWithPath(cfg.WechatPayPublicKeyPath)
if err != nil {
log.Fatalf("load public key failed: %v", err)
}
// 使用公钥模式初始化客户端
opts := []core.ClientOption{
option.WithWechatPayPublicKeyAuthCipher(
cfg.MchID,
cfg.MchCertSerialNo,
mchPrivateKey,
cfg.WechatPayPublicKeyID,
wechatPayPublicKey,
),
}
client, err := core.NewClient(context.Background(), opts...)
if err != nil {
log.Fatalf("init client failed: %v", err)
}
// 设置路由
r := gin.Default()
r.LoadHTMLGlob("templates/*")
paymentHandler := handlers.NewPaymentHandler(client, wechatPayPublicKey)
authHandler := handlers.NewAuthHandler()
r.GET("/", paymentHandler.Index)
r.GET("/pay", paymentHandler.PayPage)
r.GET("/auth", authHandler.Auth)
r.GET("/auth/callback", authHandler.Callback)
r.POST("/api/pay/create", paymentHandler.CreateOrder)
r.POST("/api/pay/notify", paymentHandler.Notify)
r.Run(":" + cfg.ServerPort)
}
templates/index.html(商品列表):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>商品列表</title>
<style>
body {
font-family: -apple-system, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.product {
border: 1px solid #ddd;
padding: 15px;
margin: 10px 0;
border-radius: 8px;
cursor: pointer;
}
.product:hover {
border-color: #07c160;
}
.price {
color: #ff6b6b;
font-size: 20px;
font-weight: bold;
}
</style>
</head>
<body>
<h2>选择商品</h2>
<div class="product" onclick="buy('P001', '测试商品-可乐', 0.01)">
<h3>测试商品-可乐</h3>
<p class="price">¥0.01</p>
</div>
<div class="product" onclick="buy('P002', '测试商品-雪碧', 0.01)">
<h3>测试商品-雪碧</h3>
<p class="price">¥0.01</p>
</div>
<script>
function buy(id, name, price) {
// price 单位:元
sessionStorage.setItem("product", JSON.stringify({ id, name, price }));
location.href = "/pay";
}
</script>
</body>
</html>
templates/pay.html(支付确认):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>确认支付</title>
<style>
body {
font-family: -apple-system, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
.amount {
font-size: 48px;
color: #333;
margin: 30px 0;
}
.btn {
background: #07c160;
color: white;
padding: 15px 40px;
border: none;
border-radius: 5px;
font-size: 18px;
}
</style>
</head>
<body>
<h2>确认支付</h2>
<div id="productName"></div>
<div class="amount">¥<span id="price">0.00</span></div>
<button class="btn" onclick="pay()">立即支付</button>
<script>
const product = JSON.parse(sessionStorage.getItem("product"));
document.getElementById("productName").textContent = product.name;
document.getElementById("price").textContent = product.price.toFixed(2);
async function pay() {
const openid = "{{.openid}}";
// 金额转换:元 → 分
const amountInCent = Math.round(product.price * 100);
const res = await fetch("/api/pay/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
product_id: product.id,
product_name: product.name,
price: product.price, // 实际业务中应该后端根据商品id查询价格,而不是前端指定价格,这里只是为了方便演示
openid: openid,
}),
});
const data = await res.json();
if (!data.success) {
alert("创建订单失败");
return;
}
// 调起微信支付
WeixinJSBridge.invoke(
"getBrandWCPayRequest",
{
appId: data.appId,
timeStamp: data.timeStamp,
nonceStr: data.nonceStr,
package: data.package,
signType: data.signType,
paySign: data.paySign,
},
function (res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
alert("支付成功");
location.href = "/";
} else if (res.err_msg === "get_brand_wcpay_request:cancel") {
alert("已取消");
} else {
alert("支付失败: " + res.err_msg);
}
},
);
}
</script>
</body>
</html>
将服务部署到公网,或者使用 ngrok 获取临时公网域名:
测试流程:
1. 访问商品页:在微信中打开 https://your-domain/
2. 选择商品:点击商品进入支付页
3. 授权获取 OpenID:首次访问自动静默授权
4. 确认支付:显示金额 ¥0.01,点击支付
5. 调起收银台:输入支付密码
6. 查看结果:支付成功后返回商户页面
原因:微信授权回调域名配置不正确
解决:
BASE_URL 完全一致(不含 https:// 和端口号)排查清单:
原因:使用了错误的验签方式
解决:
NewSHA256WithRSAPubkeyVerifier)通过本教程,你应该已经掌握了 JSAPI 支付的完整接入流程。从获取 OpenID、创建订单、调起支付到处理回调,每一步都有详细的代码示例和官方文档支持。
作为独立开发者,接入微信支付是项目变现的重要一步。希望这篇教程能帮助你少走弯路,快速上线支付功能。如果在接入过程中遇到问题,建议仔细阅读官方文档的错误码说明,或在微信支付开发者社区寻求帮助。
祝你开发顺利,产品大卖!
本教程基于微信支付 APIv3 和官方 Go SDK v0.2.21 编写,内容可能会随官方更新而变化,请以最新官方文档为准。