由於程式碼太多,所以分兩章來寫。
go-zero之支付服務一
API介面
編寫api檔案
syntax = "v1"
info(
title: "訂單支付"
desc: "訂單支付"
author: "charlie"
email: "cenhuqing@163.com"
version: "1.0"
)
type (
createOrderReq {
Amount float64 `form:"amount"`
PayType int64 `form:"pay_type"`
AutoRenewal int64 `form:"auto_renewal"`
Bit int64 `form:"bit"`
}
orderResp {
Code int `json:"code"`
Msg string `json:"msg"`
Data map[string]string `json:"data"`
}
generateQrcodeReq {
OrderId string `form:"order_id"` // 訂單id
Description string `form:"description"` // 商品描述
}
wxNotifyResp {
Code string `json:"code"`
Message string `json:"message"`
}
wxNotifyRequest {}
findOrderPayRequest {
OrderId string `form:"order_id"`
}
aliPayNotifyRequest {}
aliPayNotifyResponse {
Message string `json:"message"`
}
)
@server(
group: order
jwt: Auth
)
service api {
// 建立訂單
@handler CreateOrderHandle
post /order/create(createOrderReq) returns(orderResp)
// 生成二維碼
@handler GenerateQrcodeHandle
post /order/qrcode(generateQrcodeReq) returns(orderResp)
// 查詢支付訂單資訊
@handler FindOrderPayHandler
post /order/find(findOrderPayRequest) returns(orderResp)
}
@server (
group: order
)
service api {
// 微信支付回撥
@handler WxNotifyHandle
post /order/notify(wxNotifyRequest) returns(wxNotifyResp)
// 支付寶回撥
@handler AliPayNotifyHandle
post /order/alipay_notify() returns(orderResp)
}
生成檔案
> goctl.exe api go -api api.api -dir .
Done.
修改配置檔案
Name: api
Host: 0.0.0.0
Port: 8888
User:
Etcd:
Hosts:
- $IP:23790
Key: user.rpc
Auth:
AccessSecret: 1231313
AccessExpire: 72000
SmsRpc:
Etcd:
Hosts:
- $IP:23790
Key: sms.rpc
Order:
Etcd:
Hosts:
- $IP:23790
Key: order.rpc
# 微信支付
WxPay:
# 商戶號
MchId: "1111111111111111111"
# 應用ID
AppId: "wx22222222222222222"
# 商戶證照序列號
MCSNum: "FAKFJDAKFJKJAKDFEKJKDJAKFKDAJFKDAJKFJALF"
# api金鑰
ApiSecret: "kdjfkjafj23kjkdk23r23423423kjd"
# apiv3金鑰
ApiV3Secret: "12312kdjf4jkkjfkj32jljlfjel2jkj23jkjkj"
# 證照路徑
CPath: "/certificate/wx_apiclient_key.pem"
# 回撥url
NotifyUrl: "https://test.example.com/order/notify"
# 支付寶
AliPay:
# 應用ID
AppId: "11111333333333111"
# 私鑰
ApiPrivateKey: "DFJKDJAKFJKEdjkdkajkdjf112312421jkjkdkaf"
# 應用公鑰證照
AppPublicKeyCertPath: "/certificate/appCertPublicKey_2021002172619121.crt"
# 支付寶根證照
AliPayRootCertPath: "/certificate/alipayRootCert.crt"
# 支付寶公鑰證照
AliPayCertPublicKeyPath: "/certificate/alipayCertPublicKey_RSA2.crt"
# 回撥url,支付成功後,支付寶回撥此url,修改訂單狀態
NotifyUrl: "https://test.example.com/order/alipay_notify"
# 跳轉url,支付成功後跳轉到此url
RedirectUrl: "https://test.example.com"
宣告配置型別
package config
import (
"github.com/tal-tech/go-zero/rest"
"github.com/tal-tech/go-zero/zrpc"
)
type Config struct {
rest.RestConf
User zrpc.RpcClientConf
Auth struct {
AccessSecret string
AccessExpire int64
}
SmsRpc zrpc.RpcClientConf
Order zrpc.RpcClientConf
WxPay WxPayConf
AliPay AliPayConf
}
type WxPayConf struct{
MchId string
AppId string
MCSNum string
ApiSecret string
ApiV3Secret string
CPath string
NotifyUrl string
}
type AliPayConf struct {
AppId string
ApiPrivateKey string
AppPublicKeyCertPath string
AliPayRootCertPath string
AliPayCertPublicKeyPath string
NotifyUrl string
RedirectUrl string
}
const (
NoPay int64 = 0 // 未支付
Paying int64 = 1 // 正在支付
Paid int64 = 2 // 已支付
PaidFailed int64 = 3 // 支付失敗
WxPay int64 = 1 // 微信支付
AliPay int64 = 2 // 支付寶支付
)
填充依賴
package svc
import (
"app/api/internal/config"
"app/rpc/order/orderclient"
"app/rpc/sms/smsclient"
"app/rpc/user/userclient"
"github.com/tal-tech/go-zero/zrpc"
)
type ServiceContext struct {
Config config.Config
Userclient userclient.User
SmsClient smsclient.Sms
OrderClient orderclient.Order
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
Userclient: userclient.NewUser(zrpc.MustNewClient(c.User)),
SmsClient: smsclient.NewSms(zrpc.MustNewClient(c.SmsRpc)),
OrderClient: orderclient.NewOrder(zrpc.MustNewClient(c.Order)),
}
}
填充建立訂單邏輯
package order
import (
"app/api/internal/svc"
"app/api/internal/types"
"app/rpc/order/order"
"context"
"encoding/json"
"github.com/tal-tech/go-zero/core/logx"
)
type CreateOrderHandleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateOrderHandleLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateOrderHandleLogic {
return CreateOrderHandleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 建立訂單
func (l *CreateOrderHandleLogic) CreateOrderHandle(req types.CreateOrderReq) (*types.Resp, error) {
userId, _ := l.ctx.Value("userId").(json.Number).Int64()
// 金額float64轉int64
amount := int64(req.Amount * 100)
result, err := l.svcCtx.OrderClient.CreateOrder(l.ctx, &order.OrderRequest{
OrderId: "",
Amount: amount,
Status: 0,
PayType: req.PayType,
Bit: req.Bit,
BitValue: 0,
Data: "",
UserId: userId,
})
if err != nil {
return &types.Resp{
Code: 201,
Msg: result.Msg,
Data: result.Data,
}, nil
}
if result.Code != 200 {
return &types.Resp{
Code: 201,
Msg: result.Msg,
Data: result.Data,
}, nil
}
return &types.Resp{
Code: 200,
Msg: result.Msg,
Data: result.Data,
}, nil
}
填充生成url
邏輯
package order
import (
"app/api/internal/svc"
"app/api/internal/types"
"app/rpc/order/order"
"context"
"encoding/json"
"github.com/tal-tech/go-zero/core/logx"
)
type CreateOrderHandleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateOrderHandleLogic(ctx context.Context, svcCtx *svc.ServiceContext) CreateOrderHandleLogic {
return CreateOrderHandleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 建立訂單
func (l *CreateOrderHandleLogic) CreateOrderHandle(req types.CreateOrderReq) (*types.Resp, error) {
userId, _ := l.ctx.Value("userId").(json.Number).Int64()
// 金額float64轉int64
amount := int64(req.Amount * 100)
result, err := l.svcCtx.OrderClient.CreateOrder(l.ctx, &order.OrderRequest{
OrderId: "",
Amount: amount,
Status: 0,
PayType: req.PayType,
Bit: req.Bit,
BitValue: 0,
Data: "",
UserId: userId,
})
if err != nil {
return &types.Resp{
Code: 201,
Msg: result.Msg,
Data: result.Data,
}, nil
}
if result.Code != 200 {
return &types.Resp{
Code: 201,
Msg: result.Msg,
Data: result.Data,
}, nil
}
return &types.Resp{
Code: 200,
Msg: result.Msg,
Data: result.Data,
}, nil
}
微信回撥介面
package order
import (
"app/api/internal/config"
"app/api/internal/svc"
"app/api/internal/types"
"app/library/tool"
"app/rpc/order/orderclient"
"context"
"fmt"
"github.com/tal-tech/go-zero/core/logx"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
"github.com/wechatpay-apiv3/wechatpay-go/utils"
"net/http"
"time"
)
type WxNotifyHandleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewWxNotifyHandleLogic(ctx context.Context, svcCtx *svc.ServiceContext) WxNotifyHandleLogic {
return WxNotifyHandleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *WxNotifyHandleLogic) WxNotifyHandle(req *http.Request) (*types.WxNotifyResp, error) {
logx.Info("微信支付回撥")
mchAPIv3Key := l.svcCtx.Config.WxPay.ApiV3Secret
mcsNum := l.svcCtx.Config.WxPay.MCSNum
mchId := l.svcCtx.Config.WxPay.MchId
cPath := l.svcCtx.Config.WxPay.CPath
privateKey := tool.GetFilePath(cPath)
// 通過路徑載入私鑰
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(privateKey)
if err != nil {
logx.Error("載入商戶私鑰錯誤")
return &types.WxNotifyResp{
Code: "FAILED",
Message: err.Error(),
},nil
}
ctx := context.Background()
// 使用商戶私鑰等初始化 client,並使它具有自動定時獲取微信支付平臺證照的能力
opts := []core.ClientOption{
option.WithWechatPayAutoAuthCipher(mchId, mcsNum, mchPrivateKey, mchAPIv3Key),
}
_, err = core.NewClient(ctx, opts...)
if err != nil {
logx.Error("例項化微信支付客戶端錯誤: %s", err)
return &types.WxNotifyResp{
Code: "FAILED",
Message: err.Error(),
},nil
}
//獲取平臺證照訪問器
certVisitor := downloader.MgrInstance().GetCertificateVisitor(mchId)
handler := notify.NewNotifyHandler(mchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certVisitor))
logx.Info("驗證簽名")
// 解密
transaction := new(payments.Transaction)
notifyReq, err := handler.ParseNotifyRequest(context.Background(), req, transaction)
if err != nil {
logx.Error("簽名或解密失敗", err)
return &types.WxNotifyResp{
Code: "FAILED",
Message: err.Error(),
},nil
}
if notifyReq.Summary != "支付成功" {
logx.Error("支付失敗", err)
return &types.WxNotifyResp{
Code: "FAILED",
Message: err.Error(),
},nil
}
logx.Info("返回通知資料: ", transaction)
fmt.Println("返回通知資料: ", transaction)
// 修改訂單
go l.updateOrder(transaction)
return &types.WxNotifyResp{
Code: "SUCCESS",
Message: "成功",
}, nil
}
// 更新訂單
func (l *WxNotifyHandleLogic) updateOrder(transaction *payments.Transaction) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
_, err := l.svcCtx.OrderClient.UpdateOrder(ctx, &orderclient.UpdateOrderRequest{
OrderId: *transaction.OutTradeNo,
Status: config.Paid,
})
if err != nil {
logx.Error("支付寶回撥更新訂單失敗", err)
}
}
支付寶回撥介面
package order
import (
"app/api/internal/config"
"app/library/tool"
"app/rpc/order/orderclient"
"context"
"github.com/smartwalle/alipay/v3"
"net/http"
"time"
"app/api/internal/svc"
"app/api/internal/types"
"github.com/tal-tech/go-zero/core/logx"
)
type AliPayNotifyHandleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAliPayNotifyHandleLogic(ctx context.Context, svcCtx *svc.ServiceContext) AliPayNotifyHandleLogic {
return AliPayNotifyHandleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 支付寶回撥
func (l *AliPayNotifyHandleLogic) AliPayNotifyHandle(req *http.Request, resp http.ResponseWriter) (*types.Resp, error) {
var data = make(map[string]string, 0)
client, err := l.newAliPayClient()
if err != nil {
logx.Error("支付寶例項化錯誤:", err)
return &types.Resp{
Code: 201,
Msg: "支付寶例項化錯誤",
Data: data,
}, nil
}
notify, err := client.GetTradeNotification(req)
if err != nil {
logx.Error("支付寶回撥驗證錯誤:", err)
return &types.Resp{
Code: 201,
Msg: "支付寶回撥驗證錯誤",
Data: data,
}, nil
}
if notify.TradeStatus != "TRADE_SUCCESS" {
logx.Error("支付寶交易失敗:", err)
return &types.Resp{
Code: 201,
Msg: "支付寶交易失敗",
Data: data,
}, nil
}
logx.Info("交易狀態為:", notify.TradeStatus)
logx.Info("返回通知資料: ", notify)
// 非同步更新訂單狀態
go l.updateOrder(notify)
// 確認收到通知訊息
alipay.AckNotification(resp)
return &types.Resp{
Code: 200,
Msg: "success",
Data: data,
}, nil
}
// 更新訂單
func (l *AliPayNotifyHandleLogic) updateOrder(notify *alipay.TradeNotification) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
_, err := l.svcCtx.OrderClient.UpdateOrder(ctx, &orderclient.UpdateOrderRequest{
OrderId: notify.OutTradeNo,
Status: config.Paid,
})
if err != nil {
logx.Error("支付寶回撥更新訂單失敗", err)
}
}
// 例項化支付寶客戶端
func (l *AliPayNotifyHandleLogic) newAliPayClient() (*alipay.Client, error){
appId := l.svcCtx.Config.AliPay.AppId
apiPrivateKey := l.svcCtx.Config.AliPay.ApiPrivateKey
appPublicKeyCertPath := tool.GetFilePath(l.svcCtx.Config.AliPay.AppPublicKeyCertPath)
aliPayRootCertPath := tool.GetFilePath(l.svcCtx.Config.AliPay.AliPayRootCertPath)
aliPayCertPublicKeyPath := tool.GetFilePath(l.svcCtx.Config.AliPay.AliPayCertPublicKeyPath)
// 初始化
var client, err = alipay.New(appId, apiPrivateKey, true)
if err != nil {
logx.Error("支付寶初始化錯誤:", err)
return nil, err
}
_ = client.LoadAppPublicCertFromFile(appPublicKeyCertPath) // 載入應用公鑰證照
_ = client.LoadAliPayRootCertFromFile(aliPayRootCertPath) // 載入支付寶根證照
_ = client.LoadAliPayPublicCertFromFile(aliPayCertPublicKeyPath) // 載入支付寶公鑰證照
return client, err
}
啟動服務
❯ go run .\api.go
Starting api server at 0.0.0.0:8888...
測試
# 生成訂單
curl --location --request POST 'http://localhost:8888/order/create' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzUzODQ3NjUsImlhdCI6MTYzNTMxMjc2NSwidXNlcklkIjo5fQ.sgw9XFon0w0FU3kcLafafkejfkeaf' \
--form 'amount=0.01' \
--form 'bit=1' \
--form 'pay_type=2' \
--form 'auto_renewal=0'
# 結果
{
"code": 200,
"msg": "success",
"data": {
"amount": "1", # 金額,單位是分
"order_id": "P20211027101632027405" # 訂單號
}
}
# 獲取微信二維碼
curl --location --request POST 'https://localhost:8888/order/qrcode' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzUzODQ3NjUsImlhdCI6MTYzNTMxMjc2NSwidXNlcklkIjo5fQ.sgw9XFon0w0FU3kcLafafkejfkeaf' \
--form 'order_id=P20211027103535964140' \
--form 'description=卡飯包月'
# 結果
{
"code": 200,
"msg": "success",
"data": {
"url": "weixin://wxpay/bizpayurl?pr=e11vomAbc"
}
}
通過工具來轉成二維碼,手機微信掃碼支付
# 支付寶獲取連結
curl --location --request POST 'https://cleaner.kfsafe.cn/order/qrcode' \
--header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzUzODQ3NjUsImlhdCI6MTYzNTMxMjc2NSwidXNlcklkIjo5fQ.sgw9XFon0w0FU3kcLafafkejfkeaf' \
--form 'order_id=P20211027103958985656' \
--form 'description=卡飯包月'
# 結果
{
"code": 200,
"msg": "success",
"data": {
"url": "https://openapi.alipay.com/gateway.do?alipay_root_cert_sn=..."
}
}
複製url地址到瀏覽器
支付完成後,可以檢視訂單狀態,是否修改成功,如果不成功則檢視日誌,除錯
本作品採用《CC 協議》,轉載必須註明作者和本文連結