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 协议》,转载必须注明作者和本文链接
本帖由系统于 2年前 自动加精
githup 代码呢?
@剑尘 业务代码
代表go-zero组织慰问你 :kissing_heart: