go-zero之支付服务一

代码有点多,所以分开写了
go-zero之支付服务二

微信支付

由于是微信扫码支付,所以这里使用Native下单。商户Native支付下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。

RPC服务

开通产品

微信设置


支付宝设置

写proto文件
syntax = "proto3";

package order;

message response {
  int64 code = 1;
  string msg = 2;
  map<string, string> data = 3;
}
message orderRequest {
  string orderId = 1; // 订单id
  int64 amount = 2; // 金额
  int64 status = 3; // 支付状态
  int64 payType = 4; // 支付方式,1:微信,2:支付宝
  string data = 5;  // 订单详情
  int64 userId = 6; // 用户id
  int64 bit = 7; // 单位
  int64 bitValue = 8; // 数量
}


service Order {
  // 创建订单
  rpc createOrder(orderRequest) returns(response);
}

message generateQrcodeRequest {
  string description = 1; // 商品描述
  string outTradeNo = 2;  // 商户订单号
}

message wxPayNotifyRequest{}

message updateOrderRequest {
  string order_id = 1;
  int64 status = 2;
}

message findOrderRequest {
  string order_id = 1;
}

message findPayOrderRequest {
  string order_id = 1;
  string mch_id = 2;
}
生成文件
> goctl.exe rpc proto -src .\order.proto -dir .
protoc  --proto_path=C:\web\kafan-go-zero\rpc\order order.proto --go_out=plugins=grpc:C:\web\kafan-go-zero\rpc\order\order --go_opt=Morder.proto=.
Done.
添加配置
Name: order.rpc
ListenOn: 0.0.0.0:8082
Etcd:
  Hosts:
  - $IP:23790
  Key: order.rpc
# mongo配置
Mongodb:
  Uri: "mongodb://root:root@$IP:27017/admin"
  Db: "kafan"
  MaxPoolSize: 2000
# redis配置
Redis:
  Host: "$IP:6379"
  Pass: "root"
# 微信支付
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/zrpc"

type Config struct {
    zrpc.RpcServerConf
    Mongodb MongoConf
    WxPay WxPayConf
    AliPay AliPayConf
}
type MongoConf struct {
    Uri         string
    Db          string
    MaxPoolSize uint64
}

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 (
    AutoRenewal int64  = 1

    WxPay = 1
    AliPay = 2

    ProductCode = "FAST_INSTANT_TRADE_PAY"
)
添加依赖
package svc

import (
    "app/rpc/order/internal/config"
    "context"
    "github.com/tal-tech/go-zero/core/stores/redis"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
    "time"
)

var ServiceContextObj *ServiceContext

type ServiceContext struct {
    Config config.Config
    RedisClient   *redis.Redis
    MongoClient   *mongo.Client
    MongoClientDb *mongo.Database
}

func NewServiceContext(c config.Config) *ServiceContext {
    srvCtx := &ServiceContext{
        Config: c,
    }

    //redis初始化
    if c.Redis.Host != "" {
        srvCtx.RedisClient = srvCtx.Config.Redis.NewRedis()
    }
    //mongo初始化
    if c.Mongodb.Uri != "" {
        client, db, err := srvCtx.initMongoDB(c)
        if err != nil {
            panic("Mongodb init error" + err.Error())
        }
        srvCtx.MongoClient = client
        srvCtx.MongoClientDb = db
    }
    ServiceContextObj = srvCtx
    return srvCtx
}

// 初始化MongoDB
func (s *ServiceContext) initMongoDB(c config.Config) (*mongo.Client, *mongo.Database, error) {
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    //设置mongo参数
    options := options.Client().
        ApplyURI(c.Mongodb.Uri).
        SetMaxPoolSize(c.Mongodb.MaxPoolSize)
    //常见数据库连接
    client, err := mongo.Connect(ctx, options)
    if err != nil {
        return nil, nil, err
    }
    pref := readpref.ReadPref{}
    err = client.Ping(ctx, &pref)

    db := client.Database(c.Mongodb.Db)
    if err != nil {
        return nil, nil, err
    }
    return client, db, nil
}
创建model文件

由于使用的mongo存储,所以需要手动创建

package model

import (
    "app/rpc/order/internal/svc"
    "context"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/mongo"
)

type Order struct {
    Id                 int64   `bson:"id"`
    UserId          int64      `bson:"user_id"`
    OrderId         string     `bson:"order_id"`
    Amount             int64     `bson:"amount"`
    Status             int64     `bson:"status"`
    PayType            int64     `bson:"pay_type"`
    Bit               int64     `bson:"bit"`
    BitValue          int64     `bson:"bit_value"`
    Data            string    `bson:"data"`
    CreateTime       int64      `bson:"create_time"`
    UpdateTime       int64      `bson:"update_time"`
    db           *mongo.Collection
}

const (
    NoPay = 0        // 未支付
    Paying = 1        // 正在支付
    Paid = 2        // 已支付
    PaidFailed = 3    // 支付失败
)

func NewOrder() *Order {
    userModel := new(Order)
    userModel.db = svc.ServiceContextObj.MongoClientDb.Collection("order")
    return userModel
}

func (this *Order) GetId() (id int64, err error) {
    key := "model_order_id"
    b, err := svc.ServiceContextObj.RedisClient.Exists(key)
    if err != nil {
        return 0, err
    }
    if b == false { //不存在设为1,并返回
        svc.ServiceContextObj.RedisClient.Set(key, "1")
        return 1, nil
    }
    incr, err := svc.ServiceContextObj.RedisClient.Incr(key)
    return incr, err

}

func (this *Order) Insert() (*mongo.InsertOneResult, error) {
    return this.db.InsertOne(nil, this)
}

func (this *Order) FindOneByOrderId(id string) (*Order, error) {
    // 条件
    filter := bson.D{{"order_id", id}}
    err := this.db.FindOne(context.Background(), filter).Decode(this)
    return this, err
}

func (this *Order) UpdateOrder(id string, status int64) (interface{}, error){
    filter := bson.D{{"order_id", id}}
    // 更新
    update := bson.D{
        {
            "$set", bson.D{{"status", status}},
        },
    }
    result, err := this.db.UpdateOne(context.Background(), filter, update)
    if err != nil {
        return nil, err
    }
    return result.UpsertedID, nil
}
创建订单填充逻辑
package logic

import (
    "app/library/tool"
    "app/rpc/order/internal/model"
    "context"
    "strconv"
    "time"

    "app/rpc/order/internal/svc"
    "app/rpc/order/order"

    "github.com/tal-tech/go-zero/core/logx"
)

type CreateOrderLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewCreateOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateOrderLogic {
    return &CreateOrderLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

//  创建订单
func (l *CreateOrderLogic) CreateOrder(in *order.OrderRequest) (*order.Response, error) {
    data := make(map[string]string, 0)
    userId := strconv.FormatInt(in.UserId, 10)
    // 订单id
    orderId := tool.CreatePayNo(userId)
    // 查询是否存在
    orderModel := model.NewOrder()
    orderInfo, _ := orderModel.FindOneByOrderId(orderId)
    if orderInfo.Id > 0 {
        logx.Info("订单已存在")
        return &order.Response{
            Code: 201,
            Msg: "订单已存在",
            Data: data,
        }, nil
    }
    orderModel1 := model.NewOrder()

    // 创建订单
    orderModel1.Id, _ = orderModel.GetId()
    orderModel1.UserId = in.UserId
    orderModel1.OrderId = orderId
    orderModel1.Amount = in.Amount
    orderModel1.Status = 0
    orderModel1.PayType = in.PayType
    orderModel1.Bit = in.Bit
    orderModel1.BitValue = in.BitValue
    orderModel1.Data = in.Data
    orderModel1.CreateTime = time.Now().Unix()
    orderModel1.UpdateTime = time.Now().Unix()
    _, err := orderModel1.Insert()
    if err != nil {
        logx.Info("创建订单失败")
        return &order.Response{
            Code: 201,
            Msg: "创建订单失败",
            Data: data,
        }, nil
    }

    data["order_id"] = orderId
    data["amount"] = strconv.FormatInt(in.Amount, 10)

    return &order.Response{
        Code: 200,
        Msg: "success",
        Data: data,
    }, nil
}
生成支付url逻辑

注意,微信生成的url需要转成二维码,支付宝生成的url直接打开链接
转二维码工具

package logic

import (
    "app/library/tool"
    "app/rpc/order/internal/config"
    "app/rpc/order/internal/model"
    "context"
    "errors"
    "fmt"
    "github.com/go-pay/gopay"
    "github.com/wechatpay-apiv3/wechatpay-go/core"
    "github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
    "time"

    "app/rpc/order/internal/svc"
    "app/rpc/order/order"

    "github.com/tal-tech/go-zero/core/logx"
)

type GenerateQrcodeLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
    logx.Logger
}

func NewGenerateQrcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateQrcodeLogic {
    return &GenerateQrcodeLogic{
        ctx:    ctx,
        svcCtx: svcCtx,
        Logger: logx.WithContext(ctx),
    }
}

//  生成微信二维码
func (l *GenerateQrcodeLogic) GenerateQrcode(in *order.GenerateQrcodeRequest) (*order.Response, error) {
    var data = make(map[string]string, 0)
    // 查询订单
    orderModel := model.NewOrder()
    orderInfo, err := orderModel.FindOneByOrderId(in.OutTradeNo)
    if err != nil || orderInfo.Id <= 0 {
        logx.Error("订单不存在", err)
        return &order.Response{
            Code: 201,
            Msg:  "订单不存在",
            Data: data,
        }, nil
    }
    // 订单状态
    if orderInfo.Status == config.Paid {
        logx.Error("订单已支付")
        return &order.Response{
            Code: 201,
            Msg:  "订单已支付",
            Data: data,
        }, nil
    }
    // 微信支付
    if orderInfo.PayType == config.WxPay {
        resp, err := l.wxPay(orderInfo)
        if err != nil {
            return &order.Response{
                Code: 201,
                Msg:  err.Error(),
                Data: data,
            }, nil
        }
        data["url"] = *resp.CodeUrl
    } else if orderInfo.PayType == config.AliPay { // 支付宝
        resp, err := l.aliPay(orderInfo)
        if err != nil {
            return &order.Response{
                Code: 201,
                Msg:  err.Error(),
                Data: data,
            }, nil
        }
        data["url"] = resp
    } else if orderInfo.PayType == config.YsPay {
        // 云闪付

    } else if orderInfo.PayType == config.ActiveCode {
        // 激活码
        err := l.ActiveCode(orderInfo)
        if err != nil {
            return &order.Response{
                Code: 201,
                Msg:  err.Error(),
                Data: data,
            }, nil
        }
    }

    return &order.Response{
        Code: 200,
        Msg:  "success",
        Data: data,
    }, nil
}

// 激活码
func (l *GenerateQrcodeLogic) ActiveCode(orderInfo *model.Order) error {
    updateTime := time.Now().Unix()
    // 修改激活码的状态
    activeCodeModel := model.NewActiveCode()
    activeCodeInfo, err := activeCodeModel.FindOneById(orderInfo.ActiveCodeId)
    //logx.Info("ActiveCode===>", activeCodeInfo)
    if err != nil {
        logx.Error("ActiveCode===>查询激活码失败", err)
        return errors.New("查询激活码失败")
    }
    if activeCodeInfo.Status == config.Used {
        return errors.New("激活码已被使用")
    }
    _, err = activeCodeModel.UpdateActiveCodeByCode(orderInfo.ActiveCodeId, orderInfo.UserId, config.Used, updateTime)
    if err != nil {
        logx.Error("ActiveCode===>更新激活码表失败", err)
        return errors.New("更新激活码表失败")
    }
    // 修改订单表状态
    //orderModel := model.NewOrder()
    updateOrderLogic := new(UpdateOrderLogic)
    _, err = updateOrderLogic.UpdateOrder(&order.UpdateOrderRequest{
        OrderId: orderInfo.OrderId,
        Status:  config.Paid,
    })
    //_, err = orderModel.UpdateOrder(orderInfo.OrderId, config.Paid, updateTime)
    if err != nil {
        logx.Error("ActiveCode===>更新订单表失败", err)
        return errors.New("更新订单表失败")
    }
    return nil
}

// 微信支付
func (l *GenerateQrcodeLogic) wxPay(orderInfo *model.Order) (*native.PrepayResponse, error) {
    // 获取二维码
    mcsNum := l.svcCtx.Config.WxPay.MCSNum
    appId := l.svcCtx.Config.WxPay.AppId
    mchId := l.svcCtx.Config.WxPay.MchId
    mchAPIv3Key := l.svcCtx.Config.WxPay.ApiV3Secret
    cPath := l.svcCtx.Config.WxPay.CPath
    notifyUrl := l.svcCtx.Config.WxPay.NotifyUrl
    privateKey := tool.GetFilePath(cPath)
    client, ctx, err := tool.NewWxPayClient(mchId, mcsNum, mchAPIv3Key, privateKey)
    // 通过路径加载私钥
    /*mchPrivateKey, err := utils.LoadPrivateKeyWithPath(privateKey)
    if err != nil {
        logx.Error("加载商户私钥错误")
        return nil, errors.New("加载商户私钥错误")
    }
    ctx := context.Background()
    // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力
    opts := []core.ClientOption{
        option.WithWechatPayAutoAuthCipher(mchId, mcsNum, mchPrivateKey, mchAPIv3Key),
    }
    client, err := core.NewClient(ctx, opts...)*/
    if err != nil {
        logx.Error("实例化微信支付客户端错误: %s", err)
        return nil, errors.New("实例化微信支付客户端错误")
    }
    wxSvc := native.NativeApiService{Client: client}
    resp, result, err := wxSvc.Prepay(ctx,
        native.PrepayRequest{
            Appid:       core.String(appId),
            Mchid:       core.String(mchId),
            Description: core.String(orderInfo.Desc),
            OutTradeNo:  core.String(orderInfo.OrderId),
            TimeExpire:  core.Time(time.Now()),
            Attach:      core.String("自定义"),
            NotifyUrl:   core.String(notifyUrl),
            GoodsTag:    core.String("KF"),
            //LimitPay:      []string{"LimitPay_example"},
            SupportFapiao: core.Bool(false),
            Amount: &native.Amount{
                Total:    core.Int64(orderInfo.Amount),
                Currency: core.String("CNY"),
            },
        })
    if err != nil {
        logx.Error("微信请求预支付错误: %s", err)
        AddLogs(orderInfo.UserId, fmt.Sprintf("微信请求预支付错误: %s", err))
        return nil, errors.New("微信请求预支付错误")
    }
    AddLogs(orderInfo.UserId, fmt.Sprintf("微信请求预支付成功: %s", resp))
    logx.Infof("status=%d resp=%s", result.Response.StatusCode, resp)
    return resp, nil
}

// 支付宝
func (l *GenerateQrcodeLogic) aliPay(orderInfo *model.Order) (string, error) {
    notifyUrl := l.svcCtx.Config.AliPay.NotifyUrl
    redirectUrl := l.svcCtx.Config.AliPay.RedirectUrl
    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)
    expireTime := l.svcCtx.Config.AliPay.ExpireTime
    var returnUrl string
    if orderInfo.Pid == config.Cleaner {
        returnUrl = redirectUrl.Cleaner
    } else if orderInfo.Pid == config.PDF {
        returnUrl = redirectUrl.PDF
    }
    // 初始化
    client, err := tool.NewAliPayClient(appId, apiPrivateKey, returnUrl, notifyUrl, appPublicKeyCertPath, aliPayRootCertPath, aliPayCertPublicKeyPath)
    if err != nil {
        return "", err
    }

    // 支付宝需要元为单位,所以转成浮点型数值
    money := float64(orderInfo.Amount) / float64(100)
    amount := fmt.Sprintf("%.2f", money)

    bm := make(gopay.BodyMap)
    bm.Set("subject", orderInfo.Desc)
    bm.Set("out_trade_no", orderInfo.OrderId)
    bm.Set("total_amount", amount)
    bm.Set("timeout_express", expireTime)
    bm.Set("product_code", config.ProductCode)
    logx.Info("支付宝支付金额:", amount)

    aliPayUrl, err := client.TradePagePay(l.ctx, bm)
    if err != nil {
        logx.Error("生成链接失败:", err)
        AddLogs(orderInfo.UserId, fmt.Sprintf("支付宝生成链接失败: %s", err))
        return "", err
    }
    AddLogs(orderInfo.UserId, fmt.Sprintf("支付宝生成链接成功: %s", aliPayUrl))
    logx.Info("url:", aliPayUrl)
    return aliPayUrl, nil
}

rpc 服务已经完成, 启动服务

❯ go run .\order.go
Starting rpc server at 0.0.0.0:8082...
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 1年前 自动加精
讨论数量: 3

代表go-zero组织慰问你 :kissing_heart:

1年前 评论

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