go-zero之支付服务二
由于代码太多,所以分两章来写。
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() // 由于支付宝只需要应答"success",所以这里不需要返回值
}
所以handler文件也修改下
l := order.NewAliPayNotifyHandleLogic(r.Context(), ctx) l.AliPayNotifyHandle(r, w) /*if err != nil { httpx.Error(w, err) } else { httpx.OkJson(w, resp) }*/
生成文件
> 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"
"app/rpc/order/orderclient"
"context"
"github.com/tal-tech/go-zero/core/logx"
)
type GenerateQrcodeHandleLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGenerateQrcodeHandleLogic(ctx context.Context, svcCtx *svc.ServiceContext) GenerateQrcodeHandleLogic {
return GenerateQrcodeHandleLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// 生成二维码
func (l *GenerateQrcodeHandleLogic) GenerateQrcodeHandle(req types.GenerateQrcodeReq) (*types.OrderResp, error) {
var (
result *order.Response
err error
data = make(map[string]string)
)
// rpc 获取二维码
result, err = l.svcCtx.OrderClient.GenerateQrcode(l.ctx, &orderclient.GenerateQrcodeRequest{
OutTradeNo: req.OrderId,
//Description: req.Description,
})
if err != nil {
return &types.OrderResp{
Code: 201,
Msg: result.Msg,
Data: data,
}, nil
}
if result.Code != 200 {
return &types.OrderResp{
Code: 201,
Msg: result.Msg,
Data: data,
}, nil
}
return &types.OrderResp{
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) {
var data = make(map[string]string, 0)
client, err := l.newAliPayClient()
if err != nil {
logx.Error("支付宝实例化错误:", err)
return
}
notify, err := client.GetTradeNotification(req)
if err != nil {
logx.Error("支付宝回调验证错误:", err)
return
}
if notify.TradeStatus != "TRADE_SUCCESS" {
logx.Error("支付宝交易失败:", err)
return
}
logx.Info("交易状态为:", notify.TradeStatus)
logx.Info("返回通知数据: ", notify)
// 异步更新订单状态
go l.updateOrder(notify)
// 确认收到通知消息
alipay.AckNotification(resp)
}
// 更新订单
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://localhost/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 协议》,转载必须注明作者和本文链接
建议博主 :joy:文章更新一下,现在换官方库地址了