go-zero之支付服務一

charliecen發表於2021-10-27

程式碼有點多,所以分開寫了
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/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
}

rpc 服務已經完成, 啟動服務

❯ go run .\order.go
Starting rpc server at 0.0.0.0:8082...
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章