程式碼有點多,所以分開寫了
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 協議》,轉載必須註明作者和本文連結