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 协议》,转载必须注明作者和本文链接
讨论数量: 1

建议博主 :joy:文章更新一下,现在换官方库地址了

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
未填写
文章
39
粉丝
9
喜欢
71
收藏
102
排名:461
访问:1.9 万
私信
所有博文
社区赞助商