本文面向有基础编程能力的独立开发者,手把手教你从零开始在PC网站接入微信支付Native支付。全文基于 微信支付官方APIv3文档 ,提供可直接运行的Go语言Demo代码,预计30分钟即可完成支付流程跑通。
在《微信支付系列教程》系列文章中,我们将详细介绍如何从0到1在自己的网站中接入微信支付。以下是该系列文章的全部内容:
阅读本文后,你将能够独立完成:
在开始之前,请确保已完成以下准备工作:
Native支付是微信支付提供的一种支付方式,主要面向PC端网页浏览器场景,允许商户在自己的网站中生成支付二维码,用户使用微信"扫一扫"功能扫描二维码完成支付。
根据 微信支付官方文档 ,Native支付的核心特点是:
code_urlcode_url 转换为二维码图片展示给用户Native支付特别适用于以下业务场景:
不适用场景:
根据 官方Native支付文档 ,Native支付的完整流程如下:
第一阶段:商户下单生成二维码
code_url(二维码链接)code_url 转换为二维码图片展示给用户第二阶段:用户扫码支付
第三阶段:支付结果处理
根据 Native支付快速开始文档 和 权限申请文档 ,接入Native支付需要满足以下条件:
1. 公众账号要求(三选一)
2. 商户号要求
3. 绑定关系
如果你已经有商户号但未开通Native支付权限,按以下步骤操作:
详细操作可参考 申请Native支付权限指引 。
如果你还没有商户号,可以在申请商户号的同时申请Native支付权限:
注意事项:
在开始开发前,你需要准备以下参数:
| 参数名称 | 参数说明 | 获取方式 |
|---|---|---|
| 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详细参数说明可参考: 普通商户模式开发必要参数说明 。
另外,还需要在产品中心-开发配置中配置Native支付的回调链接:
本教程使用以下技术栈演示:
./wechatpay-native-demo
├── README.md
├── cert
│ ├── apiclient_cert.p12
│ ├── apiclient_cert.pem
│ ├── apiclient_key.pem
│ └── pub_key.pem
├── config
│ └── config.go
├── go.mod
├── go.sum
├── handler
│ ├── notify.go
│ └── payment.go
├── main.go
├── service
│ └── wechatpay.go
└── templates
├── index.html
├── pay.html
└── success.html
根据 Native支付开发指引文档 ,Native支付的完整开发流程分为四个阶段:
Native支付下单API:
/v3/pay/transactions/nativehttps://api.mch.weixin.qq.com(主域名)或 https://api2.mch.weixin.qq.com(备域名)详细接口文档可参考 Native下单API文档 。
| 参数 | 必填 | 类型 | 描述 |
|---|---|---|---|
appid | 是 | string(32) | 公众账号ID(服务号/小程序/移动应用) |
mchid | 是 | string(32) | 商户号 |
description | 是 | string(127) | 商品描述,会显示在微信账单中 |
out_trade_no | 是 | string(32) | 商户系统内部订单号(6-32字符,同一商户号下唯一) |
time_expire | 否 | string(64) | 支付结束时间(rfc3339格式,如2025-01-29T12:00:00+08:00) |
attach | 否 | string(128) | 商户数据包,原样返回 |
notify_url | 是 | string(255) | 支付结果回调地址,必须为外网可访问URL |
goods_tag | 否 | string(32) | 订单优惠标记,用于代金券 |
support_fapiao | 否 | boolean | 是否开通电子发票入口 |
amount | 是 | object | 订单金额信息 |
detail | 否 | object | 商品详情 |
amount对象:
| 参数 | 必填 | 类型 | 描述 |
|---|---|---|---|
| total | 是 | int | 订单总金额,单位为分 |
| currency | 否 | string(16) | 货币类型,默认CNY(人民币) |
关键参数说明:
yyyy-MM-DDTHH:mm:ss+TIMEZONE,如 2025-01-29T13:29:35+08:00code_url 有效期为2小时code_url创建 config/config.go:
package config
import (
"os"
)
type WechatPayConfig struct {
AppID string // 公众账号ID
MchID string // 商户号
APIv3Key string // APIv3密钥
CertSerialNo string // 商户证书序列号
PrivateKeyPath string // 商户私钥文件路径
PublicKeyID string // 微信支付公钥ID
PublicKeyPath string // 微信支付公钥文件路径
NotifyURL string // 支付结果回调地址
}
func LoadConfig() *WechatPayConfig {
return &WechatPayConfig{
AppID: os.Getenv("WXPAY_APPID"),
MchID: os.Getenv("WXPAY_MCHID"),
APIv3Key: os.Getenv("WXPAY_APIV3_KEY"),
CertSerialNo: os.Getenv("WXPAY_CERT_SERIAL_NO"),
PrivateKeyPath: os.Getenv("WXPAY_PRIVATE_KEY_PATH"),
PublicKeyID: os.Getenv("WXPAY_PUBLIC_KEY_ID"),
PublicKeyPath: os.Getenv("WXPAY_PUBLIC_KEY_PATH"),
NotifyURL: os.Getenv("WXPAY_NOTIFY_URL"),
}
}
创建 service/wechatpay.go:
package service
import (
"context"
"fmt"
"wechatpay-native-demo/config"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
)
type WechatPayService struct {
client *core.Client
config *config.WechatPayConfig
}
// NewWechatPayService 创建微信支付服务(使用微信支付公钥模式)
func NewWechatPayService(cfg *config.WechatPayConfig) (*WechatPayService, error) {
// 加载商户私钥
privateKey, err := utils.LoadPrivateKeyWithPath(cfg.PrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("加载商户私钥失败: %v", err)
}
// 加载微信支付公钥(用于验签)
wechatPayPublicKey, err := utils.LoadPublicKeyWithPath(cfg.PublicKeyPath)
if err != nil {
return nil, fmt.Errorf("加载微信支付公钥失败: %v", err)
}
// 创建商户配置(使用公钥模式)
opts := []core.ClientOption{
// 使用商户私钥进行请求签名,使用微信支付公钥验证响应
option.WithWechatPayPublicKeyAuthCipher(
cfg.MchID, // 商户号
cfg.CertSerialNo, // 商户证书序列号
privateKey, // 商户私钥
cfg.PublicKeyID, // 微信支付公钥ID
wechatPayPublicKey, // 微信支付公钥
),
}
// 创建客户端
client, err := core.NewClient(context.Background(), opts...)
if err != nil {
return nil, fmt.Errorf("创建微信支付客户端失败: %v", err)
}
return &WechatPayService{
client: client,
config: cfg,
}, nil
}
// CreateNativeOrder 创建Native支付订单
func (s *WechatPayService) CreateNativeOrder(outTradeNo, description string, totalFee int64) (string, error) {
svc := native.NativeApiService{Client: s.client}
resp, result, err := svc.Prepay(context.Background(), native.PrepayRequest{
Appid: core.String(s.config.AppID),
Mchid: core.String(s.config.MchID),
Description: core.String(description),
OutTradeNo: core.String(outTradeNo),
NotifyUrl: core.String(s.config.NotifyURL),
Amount: &native.Amount{
Total: core.Int64(totalFee),
},
})
if err != nil {
return "", fmt.Errorf("创建订单失败: %v, result: %+v", err, result)
}
return *resp.CodeUrl, nil
}
// QueryOrder 查询订单
func (s *WechatPayService) QueryOrder(outTradeNo string) (*payments.Transaction, error) {
svc := native.NativeApiService{Client: s.client}
resp, result, err := svc.QueryOrderByOutTradeNo(context.Background(), native.QueryOrderByOutTradeNoRequest{
OutTradeNo: core.String(outTradeNo),
Mchid: core.String(s.config.MchID),
})
if err != nil {
return nil, fmt.Errorf("查询订单失败: %v, result: %+v", err, result)
}
return resp, nil
}
创建 handler/payment.go:
package handler
import (
"fmt"
"net/http"
"time"
"wechatpay-native-demo/service"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type PaymentHandler struct {
wechatPay *service.WechatPayService
}
func NewPaymentHandler(wechatPay *service.WechatPayService) *PaymentHandler {
return &PaymentHandler{wechatPay: wechatPay}
}
// CreateOrderRequest 创建订单请求
type CreateOrderRequest struct {
ProductName string `json:"product_name" binding:"required"`
Amount int64 `json:"amount" binding:"required,min=1"` // 单位:分
}
// CreateOrderResponse 创建订单响应
type CreateOrderResponse struct {
OrderID string `json:"order_id"`
CodeURL string `json:"code_url"`
Amount int64 `json:"amount"`
ProductName string `json:"product_name"`
}
// CreateOrder 创建支付订单
func (h *PaymentHandler) CreateOrder(c *gin.Context) {
var req CreateOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 生成订单号(实际项目中应使用更严谨的订单号生成策略)
outTradeNo := fmt.Sprintf("NATIVE%s%s", time.Now().Format("20060102150405"), uuid.New().String()[:8])
// 调用微信支付接口创建订单
codeURL, err := h.wechatPay.CreateNativeOrder(outTradeNo, req.ProductName, req.Amount)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// 保存订单信息到数据库(实际项目中应持久化存储)
// TODO: 保存订单到数据库
c.JSON(http.StatusOK, CreateOrderResponse{
OrderID: outTradeNo,
CodeURL: codeURL,
Amount: req.Amount,
ProductName: req.ProductName,
})
}
// QueryOrder 查询订单状态
func (h *PaymentHandler) QueryOrder(c *gin.Context) {
orderID := c.Param("order_id")
// TODO: 从数据库查询订单状态
// 调用微信支付查询接口
resp, err := h.wechatPay.QueryOrder(orderID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
// 构建响应
result := gin.H{
"order_id": orderID,
"status": "NOTPAY", // 默认未支付
}
if resp.TradeState != nil {
result["status"] = *resp.TradeState
// 如果支付成功,返回更多信息
if *resp.TradeState == "SUCCESS" {
if resp.TransactionId != nil {
result["transaction_id"] = *resp.TransactionId
}
if resp.SuccessTime != nil {
result["success_time"] = *resp.SuccessTime
}
if resp.Amount != nil && resp.Amount.Total != nil {
result["total_amount"] = *resp.Amount.Total
}
if resp.Payer != nil && resp.Payer.Openid != nil {
result["payer_openid"] = *resp.Payer.Openid
}
}
}
// 添加交易状态描述
if resp.TradeStateDesc != nil {
result["trade_state_desc"] = *resp.TradeStateDesc
}
c.JSON(http.StatusOK, result)
}
创建 templates/pay.html:
<body>
<div class="container">
<div class="header">
<h1>微信扫码支付</h1>
<p id="product-name">商品名称加载中...</p>
</div>
<div class="amount"><span>¥</span><span id="amount">0.00</span></div>
<div class="qrcode-container">
<div id="qrcode"></div>
</div>
<div class="tips">
<p>请使用微信扫一扫扫描二维码完成支付</p>
<p class="highlight">注意:不支持长按识别或从相册识别</p>
</div>
<div class="countdown" id="countdown">
二维码有效期:<span id="timer">30:00</span>
</div>
<button class="refresh-btn" onclick="refreshOrder()">刷新二维码</button>
<div id="status" class="status"></div>
</div>
<script>
let orderId = "";
let codeUrl = "";
let checkInterval = null;
let countdownInterval = null;
let remainingTime = 30 * 60; // 30分钟(秒)
// 页面加载时执行
window.onload = function () {
createOrder();
};
// 创建订单
function createOrder() {
const productName =
new URLSearchParams(window.location.search).get("product") ||
"测试商品";
const amount =
new URLSearchParams(window.location.search).get("amount") || 1;
fetch("/api/order", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
product_name: productName,
amount: parseInt(amount),
}),
})
.then((response) => response.json())
.then((data) => {
if (data.error) {
showStatus("error", "创建订单失败:" + data.error);
return;
}
orderId = data.order_id;
codeUrl = data.code_url;
// 更新页面信息
document.getElementById("product-name").textContent =
data.product_name;
document.getElementById("amount").textContent = (
data.amount / 100
).toFixed(2);
// 生成二维码
generateQRCode(codeUrl);
// 开始倒计时
startCountdown();
// 开始轮询查询订单状态
startPolling();
})
.catch((error) => {
showStatus("error", "网络错误:" + error.message);
});
}
// 生成二维码
function generateQRCode(url) {
// 清空之前的二维码
const container = document.getElementById("qrcode");
container.innerHTML = "";
// 生成新二维码
new QRCode(container, {
text: url,
width: 256,
height: 256,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.M,
});
}
// 开始倒计时
function startCountdown() {
remainingTime = 30 * 60; // 重置为30分钟
if (countdownInterval) {
clearInterval(countdownInterval);
}
countdownInterval = setInterval(function () {
remainingTime--;
if (remainingTime <= 0) {
clearInterval(countdownInterval);
document.getElementById("timer").textContent = "已过期";
document.getElementById("countdown").style.background = "#f8d7da";
document.getElementById("countdown").style.color = "#721c24";
return;
}
const minutes = Math.floor(remainingTime / 60);
const seconds = remainingTime % 60;
document.getElementById("timer").textContent =
minutes.toString().padStart(2, "0") +
":" +
seconds.toString().padStart(2, "0");
}, 1000);
}
// 开始轮询查询订单状态
function startPolling() {
if (checkInterval) {
clearInterval(checkInterval);
}
// 每3秒查询一次
checkInterval = setInterval(function () {
checkOrderStatus();
}, 3000);
}
// 查询订单状态
function checkOrderStatus() {
if (!orderId) return;
fetch("/api/order/" + orderId)
.then((response) => response.json())
.then((data) => {
if (data.status === "SUCCESS") {
// 支付成功
clearInterval(checkInterval);
clearInterval(countdownInterval);
showStatus("success", "支付成功!即将跳转...");
// 2秒后跳转到成功页面
setTimeout(function () {
window.location.href = "/success?order_id=" + orderId;
}, 2000);
} else if (data.status === "CLOSED") {
// 订单已关闭
clearInterval(checkInterval);
showStatus("error", "订单已关闭,请重新下单");
}
})
.catch((error) => {
console.error("查询订单状态失败:", error);
});
}
// 刷新订单
function refreshOrder() {
clearInterval(checkInterval);
clearInterval(countdownInterval);
createOrder();
}
// 显示状态信息
function showStatus(type, message) {
const statusDiv = document.getElementById("status");
statusDiv.className = "status " + type;
statusDiv.textContent = message;
}
// 页面关闭时清理定时器
window.onbeforeunload = function () {
if (checkInterval) clearInterval(checkInterval);
if (countdownInterval) clearInterval(countdownInterval);
};
</script>
</body>
根据
支付成功回调通知文档
,当用户支付成功后,微信支付会向商户指定的 notify_url 发送回调通知。
步骤1:接收回调通知
步骤2:验证签名
Wechatpay-Serial:验签的微信支付平台证书序列号Wechatpay-Signature:签名值Wechatpay-Timestamp:时间戳Wechatpay-Nonce:随机字符串步骤3:解密通知内容
resource.ciphertextAEAD_AES_256_GCMnonce:加密使用的随机串associated_data:附加数据(通常为"transaction")步骤4:处理业务逻辑
步骤5:响应微信支付
重要提醒:
创建 handler/notify.go:
package handler
import (
"context"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
)
// NotifyHandler 支付回调处理器
type NotifyHandler struct {
handler *notify.Handler
apiV3Key string
}
// NewNotifyHandler 创建支付回调处理器
func NewNotifyHandler(apiV3Key, wechatPayPublicKeyID, publicKeyPath string) (*NotifyHandler, error) {
// 加载微信支付公钥
wechatPayPublicKey, err := utils.LoadPublicKeyWithPath(publicKeyPath)
if err != nil {
return nil, fmt.Errorf("加载微信支付公钥失败: %v", err)
}
// 初始化 notify.Handler,使用 SDK 提供的验签方法
handler := notify.NewNotifyHandler(
apiV3Key,
verifiers.NewSHA256WithRSAPubkeyVerifier(wechatPayPublicKeyID, *wechatPayPublicKey),
)
return &NotifyHandler{
handler: handler,
apiV3Key: apiV3Key,
}, nil
}
// PaymentNotify 支付结果回调
func (h *NotifyHandler) PaymentNotify(c *gin.Context) {
// 使用 SDK 的 Handler 解析通知
transaction := new(Transaction)
notifyReq, err := h.handler.ParseNotifyRequest(context.Background(), c.Request, transaction)
if err != nil {
fmt.Printf("[Notify] 解析通知失败: %v\n", err)
c.JSON(http.StatusUnauthorized, gin.H{"code": "FAIL", "message": "验签未通过,或者解密失败"})
return
}
// 记录日志
fmt.Printf("[Notify] 收到支付通知: EventType=%s, OrderID=%s\n",
notifyReq.EventType, transaction.OutTradeNo)
// 检查事件类型
if notifyReq.EventType != "TRANSACTION.SUCCESS" {
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS", "message": "非支付成功通知"})
return
}
// 异步处理业务逻辑
go func() {
processPaymentSuccess(*transaction)
}()
// 返回成功响应
c.Status(http.StatusNoContent)
}
// Transaction 支付通知中的订单信息
type Transaction struct {
AppID string `json:"appid"`
MchID string `json:"mchid"`
OutTradeNo string `json:"out_trade_no"`
TransactionID string `json:"transaction_id"`
TradeType string `json:"trade_type"`
TradeState string `json:"trade_state"`
TradeStateDesc string `json:"trade_state_desc"`
BankType string `json:"bank_type"`
Attach string `json:"attach"`
SuccessTime string `json:"success_time"`
Payer struct {
OpenID string `json:"openid"`
} `json:"payer"`
Amount struct {
Total int `json:"total"`
PayerTotal int `json:"payer_total"`
Currency string `json:"currency"`
PayerCurrency string `json:"payer_currency"`
} `json:"amount"`
}
// processPaymentSuccess 处理支付成功业务逻辑
func processPaymentSuccess(result Transaction) {
fmt.Printf("[Process] 处理支付成功订单: %s, 微信支付订单号: %s, 支付金额: %d分\n",
result.OutTradeNo, result.TransactionID, result.Amount.Total)
// TODO: 实现订单状态更新、库存扣减、发货通知等业务逻辑
// 注意:需要处理重复通知的情况(幂等性)
}
根据 微信支付订单号查询订单 和 商户订单号查询订单 文档,商户可以主动查询订单状态。
通过微信支付订单号查询:
/v3/pay/transactions/id/{transaction_id}?mchid={mchid}通过商户订单号查询:
/v3/pay/transactions/out-trade-no/{out_trade_no}?mchid={mchid}订单状态说明:
| 状态值 | 说明 |
|---|---|
| SUCCESS | 支付成功 |
| REFUND | 转入退款 |
| NOTPAY | 未支付 |
| CLOSED | 已关闭 |
| REVOKED | 已撤销(仅付款码支付) |
| USERPAYING | 用户支付中(仅付款码支付) |
| PAYERROR | 支付失败(仅付款码支付) |
根据 关闭订单文档 ,对于未支付的订单,商户可以调用关单接口:
/v3/pay/transactions/out-trade-no/{out_trade_no}/close{"mchid": "1230000109"}关单场景:
在 service/wechat_pay.go 中添加关单方法:
// CloseOrder 关闭订单
func (s *WechatPayService) CloseOrder(outTradeNo string) error {
svc := native.NativeApiService{Client: s.client}
result, err := svc.CloseOrder(context.Background(), native.CloseOrderRequest{
OutTradeNo: core.String(outTradeNo),
Mchid: core.String(s.config.MchID),
})
if err != nil {
return fmt.Errorf("关闭订单失败: %v, result: %+v", err, result)
}
return nil
}
根据 退款申请文档 ,交易完成后一年内可申请退款。
/v3/refund/domestic/refunds重要限制:
请求参数:
| 参数 | 必填 | 说明 |
|---|---|---|
transaction_id | 二选一 | 微信支付订单号 |
out_trade_no | 二选一 | 商户订单号 |
out_refund_no | 是 | 商户退款单号(64字符内,唯一) |
reason | 否 | 退款原因(80字符内) |
notify_url | 否 | 退款结果回调地址 |
amount | 是 | 退款金额信息 |
查询退款:
/v3/refund/domestic/refunds/{out_refund_no}发起异常退款(当退款到银行卡失败时):
/v3/refund/domestic/refunds/{refund_id}/apply-abnormal-refund// ApplyRefund 申请退款
func (s *WechatPayService) ApplyRefund(outTradeNo, outRefundNo string, refundFee int64, reason string) (*refund.Refund, error) {
svc := refund.RefundsApiService{Client: s.client}
resp, result, err := svc.Create(context.Background(), refund.CreateRequest{
OutTradeNo: core.String(outTradeNo),
OutRefundNo: core.String(outRefundNo),
Reason: core.String(reason),
Amount: &refund.AmountReq{
Refund: core.Int64(refundFee),
Total: core.Int64(refundFee), // 原订单金额,这里简化处理
Currency: core.String("CNY"),
},
})
if err != nil {
return nil, fmt.Errorf("申请退款失败: %v, result: %+v", err, result)
}
return resp, nil
}
由于文章篇幅不易太长,完整本地可运行的demo项目代码我已打包上传, 需要完整项目源码的可以关注我的公众号「 人言兑 」,私信发送「
native demo」即可获取。
main.gopackage main
import (
"log"
"os"
"wechatpay-native-demo/config"
"wechatpay-native-demo/handler"
"wechatpay-native-demo/service"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
)
func main() {
// 加载环境变量
if err := godotenv.Load(); err != nil {
log.Println("未找到.env文件,使用系统环境变量")
}
// 加载配置
cfg := config.LoadConfig()
// 验证必要配置
if cfg.AppID == "" || cfg.MchID == "" || cfg.APIv3Key == "" || cfg.PublicKeyID == "" {
log.Fatal("缺少必要的微信支付配置,请检查环境变量")
}
// 创建微信支付服务
wechatPay, err := service.NewWechatPayService(cfg)
if err != nil {
log.Fatalf("初始化微信支付服务失败: %v", err)
}
// 创建处理器
paymentHandler := handler.NewPaymentHandler(wechatPay)
notifyHandler, err := handler.NewNotifyHandler(cfg.APIv3Key, cfg.PublicKeyID, cfg.PublicKeyPath)
if err != nil {
log.Fatalf("初始化通知处理器失败: %v", err)
}
// 创建Gin路由
r := gin.Default()
// 加载HTML模板
r.LoadHTMLGlob("templates/*")
// 静态文件服务
r.Static("/static", "./static")
// 页面路由
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.html", nil)
})
r.GET("/pay", func(c *gin.Context) {
c.HTML(200, "pay.html", nil)
})
r.GET("/success", func(c *gin.Context) {
c.HTML(200, "success.html", gin.H{
"order_id": c.Query("order_id"),
})
})
// API路由
api := r.Group("/api")
{
api.POST("/order", paymentHandler.CreateOrder)
api.GET("/order/:order_id", paymentHandler.QueryOrder)
api.POST("/notify", notifyHandler.PaymentNotify)
}
// 启动服务器
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("服务器启动在 http://localhost:%s", port)
if err := r.Run(":" + port); err != nil {
log.Fatalf("启动服务器失败: %v", err)
}
}
templates/index.html<body>
<div class="container">
<h1>微信支付 Native支付 Demo</h1>
<div class="products">
<div class="product-card">
<div class="product-image">📱</div>
<div class="product-info">
<div class="product-name">高级会员 1个月</div>
<div class="product-desc">
解锁全部高级功能,享受VIP专属服务,无限制使用所有特性。
</div>
<div class="product-footer">
<div class="price"><span>¥</span>29.90</div>
<button class="buy-btn" onclick="buy('高级会员 1个月', 2990)">
立即购买
</button>
</div>
</div>
</div>
<div class="product-card">
<div class="product-image">💎</div>
<div class="product-info">
<div class="product-name">高级会员 1年</div>
<div class="product-desc">
年度会员更优惠,相当于每月仅需19.9元,节省33%。
</div>
<div class="product-footer">
<div class="price"><span>¥</span>199.00</div>
<button class="buy-btn" onclick="buy('高级会员 1年', 19900)">
立即购买
</button>
</div>
</div>
</div>
<div class="product-card">
<div class="product-image">🎁</div>
<div class="product-info">
<div class="product-name">测试商品 0.01元</div>
<div class="product-desc">
用于测试支付流程,实际支付0.01元,支付后可申请退款。
</div>
<div class="product-footer">
<div class="price"><span>¥</span>0.01</div>
<button class="buy-btn" onclick="buy('测试商品', 1)">
立即购买
</button>
</div>
</div>
</div>
</div>
</div>
<script>
function buy(productName, amount) {
// 跳转到支付页面
window.location.href =
"/pay?product=" + encodeURIComponent(productName) + "&amount=" + amount;
}
</script>
</body>
.env# 微信支付配置
# 公众账号ID(服务号/小程序/移动应用的AppID)
WXPAY_APPID=wx1234567890abcdef
# 商户号
WXPAY_MCHID=1234567890
# APIv3密钥(32位字符,在商户平台-API安全设置)
WXPAY_APIV3_KEY=YourAPIv3KeyHere
# 商户证书序列号
# 获取命令:openssl x509 -in apiclient_cert.pem -noout -serial | cut -d= -f2 | tr '[:upper:]' '[:lower:]'
WXPAY_CERT_SERIAL_NO=1234567890ABCDEF1234567890ABCDEF
# 商户私钥路径
WXPAY_PRIVATE_KEY_PATH=./cert/apiclient_key.pem
# 微信支付公钥模式(2024年后新商户默认使用)
# 公钥ID在商户平台获取,格式如:PUB_KEY_ID_xxxxxxxxxxxxxxxx
# 获取路径:商户平台 → API安全 → 微信支付公钥 → 查看公钥ID
WXPAY_PUBLIC_KEY_ID=PUB_KEY_ID_xxxxxxxxxxxxxxxx
WXPAY_PUBLIC_KEY_PATH=./cert/pub_key.pem
# 在商户号上配置的Native支付回调地址(必须外网可访问)
WXPAY_NOTIFY_URL=https://yourdomain.com/api/notify
# 服务器配置
PORT=8080
根据 官方Native支付常见问题文档 ,整理以下常见问题:
Q1:调用Native支付统一下单成功,但扫描二维码支付时返回"系统繁忙,请稍后再试"
解决方案:
Q2:长按识别Native支付二维码返回:“该商户暂不支持通过长按识别二维码完成支付”
解决方案:
Q3:用户支付时报错:“商家订单信息有误,请重新下单支付”
解决方案:
Q4:Native支付二维码能否实现1分钟刷新?
解决方案:
code_url 会改变,同时旧的 code_url 将会过期失效Q1:收不到支付成功回调通知
排查步骤:
notify_url 是否为外网可访问的URL,不能是localhost或内网地址Q2:回调通知重复发送
解决方案:
SIGN_ERROR 签名错误
StatusCode: 401 Code: "SIGN_ERROR"
Message: 签名错误
常见原因及修复:未设置证书序列号或证书序列号错误。
使用以下命令获取微信支付商户平台证书序列号:
openssl x509 -in ./cert/apiclient_cert.pem -noout -serial | cut -d= -f2 | tr '[:upper:]' '[:lower:]'
APPID_MCHID_NOT_MATCH appid和mch_id不匹配
StatusCode: 400
Code: "APPID_MCHID_NOT_MATCH"
Message: appid和mch_id不匹配,请检查后再试
检查appid和mch_id是否配置正确,或者未在商户平台-产品中心进行关联appid
NO_AUTH 此商家的收款功能已被限制,暂无法支付
StatusCode: 403
Code: "NO_AUTH"
Message: 此商家的收款功能已被限制,暂无法支付。商家可登录微信商户平台/微信支付商家助手小程序/经营账户页面查看原因和解决方案。
登录商户平台查看「消息中心」看看有没有「业务限制通知」,按照里面的说明操作接触相应限制即可。
RESOURCE_NOT_EXISTS 无可用的平台证书
StatusCode: 404 Code: "RESOURCE_NOT_EXISTS"
Message: 无可用的平台证书,请在商户平台-API安全申请使用微信支付公钥。可查看指引https://pay.weixin.qq.com/doc/v3/merchant/4012153196
这个错误说明微信支付现在要求使用微信支付公钥而不是传统的平台证书。根据错误信息中的指引 https://pay.weixin.qq.com/doc/v3/merchant/4012153196 ,最新商户号需要使用公钥方式。
Q1:Native支付和其他基础支付的区别
说明:
Q2:未支付的订单在微信里能看到吗?
说明:
本文详细介绍了微信支付Native支付的完整接入流程,包括:
通过本文的指引,你应该能够独立完成Native支付的接入开发。如果在接入过程中遇到问题,建议参考官方文档或联系微信支付技术支持。
本教程基于微信支付官方文档整理,接口参数和行为可能随官方更新而变化,请以 微信支付官方文档 为准。