feat(kami_gateway): 新增卡密订单提交与回调功能
- 新增 kami_gateway 包,实现订单提交接口 - 实现 SubmitOrder 方法,支持签名生成与表单提交 - 新增 model 定义 SubmitOrderReq 和 SubmitOrderResponse 结构体 - 新增工具函数 GetMD5SignMF 用于生成 MD5 签名 - 修改订单回调逻辑,使用新的网关接口替代原有 HTTP 请求 - 更新京东订单状态变更类型,增加 Callback 类型 - 调整过期订单清理时间从 24 小时改为30 分钟- 移除冗余的订单创建前释放过期订单逻辑 - 删除无用的 ValueIsNil 错误处理函数 - 更新 boot_enums.go 中的枚举定义,增加 jd_order_change_type 的 callback 类型 - 优化 cron任务中的京东支付状态监控逻辑,提前执行过期订单清理 - 修复 jd_cookie 包中查询 jd_order时缺少 limit 条件的问题
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -92,28 +92,30 @@ const (
|
||||
type JdOrderChangeType string
|
||||
|
||||
const (
|
||||
JdOrderChangeTypeCreate JdOrderChangeType = "create" // 创建
|
||||
JdOrderChangeTypeBind JdOrderChangeType = "bind" // 绑定
|
||||
JdOrderChangeTypeUnbind JdOrderChangeType = "unbind" // 解绑
|
||||
JdOrderChangeTypePendPay JdOrderChangeType = "pendPay" // 待支付
|
||||
JdOrderChangeTypePay JdOrderChangeType = "pay" // 支付
|
||||
JdOrderChangeTypeExpire JdOrderChangeType = "expire" // 过期
|
||||
JdOrderChangeTypeInvalid JdOrderChangeType = "invalid" // 失效(新增)
|
||||
JdOrderChangeTypeSend JdOrderChangeType = "send" // 发货
|
||||
JdOrderChangeTypeReplace JdOrderChangeType = "replace" // 换绑
|
||||
JdOrderChangeTypeCreate JdOrderChangeType = "create" // 创建
|
||||
JdOrderChangeTypeBind JdOrderChangeType = "bind" // 绑定
|
||||
JdOrderChangeTypeUnbind JdOrderChangeType = "unbind" // 解绑
|
||||
JdOrderChangeTypePendPay JdOrderChangeType = "pendPay" // 待支付
|
||||
JdOrderChangeTypePay JdOrderChangeType = "pay" // 支付
|
||||
JdOrderChangeTypeExpire JdOrderChangeType = "expire" // 过期
|
||||
JdOrderChangeTypeInvalid JdOrderChangeType = "invalid" // 失效(新增)
|
||||
JdOrderChangeTypeSend JdOrderChangeType = "send" // 发货
|
||||
JdOrderChangeTypeReplace JdOrderChangeType = "replace" // 换绑
|
||||
JdOrderChangeTypeCallback JdOrderChangeType = "callback" // 回调
|
||||
)
|
||||
|
||||
// JdOrderChangeTypeText 京东订单变更类型文本映射
|
||||
var JdOrderChangeTypeText = map[JdOrderChangeType]string{
|
||||
JdOrderChangeTypeCreate: "创建",
|
||||
JdOrderChangeTypeBind: "绑定",
|
||||
JdOrderChangeTypeUnbind: "解绑",
|
||||
JdOrderChangeTypePay: "支付",
|
||||
JdOrderChangeTypeExpire: "过期",
|
||||
JdOrderChangeTypeInvalid: "失效",
|
||||
JdOrderChangeTypeSend: "发货",
|
||||
JdOrderChangeTypeReplace: "换绑",
|
||||
JdOrderChangeTypePendPay: "待支付",
|
||||
JdOrderChangeTypeCreate: "创建",
|
||||
JdOrderChangeTypeBind: "绑定",
|
||||
JdOrderChangeTypeUnbind: "解绑",
|
||||
JdOrderChangeTypePay: "支付",
|
||||
JdOrderChangeTypeExpire: "过期",
|
||||
JdOrderChangeTypeInvalid: "失效",
|
||||
JdOrderChangeTypeSend: "发货",
|
||||
JdOrderChangeTypeReplace: "换绑",
|
||||
JdOrderChangeTypePendPay: "待支付",
|
||||
JdOrderChangeTypeCallback: "回调",
|
||||
}
|
||||
|
||||
// OrderChangeType 订单变更类型
|
||||
|
||||
@@ -20,8 +20,6 @@ import (
|
||||
|
||||
// CreateOrder 创建订单
|
||||
func (s *sJdCookie) CreateOrder(ctx context.Context, req *model.CreateOrderReq) (result *model.CreateOrderResult, err error) {
|
||||
_ = s.ReleaseExpiredJdOrders(ctx)
|
||||
|
||||
if req.UserOrderId == "" {
|
||||
return nil, gerror.New("用户订单号不能为空")
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import (
|
||||
"kami/internal/model/entity"
|
||||
"kami/utility/cache"
|
||||
"kami/utility/config"
|
||||
"kami/utility/integration/kami_gateway"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
@@ -521,40 +521,59 @@ func (s *sJdCookie) ExtractCardInfo(ctx context.Context, jdOrderId string) error
|
||||
|
||||
_ = s.RecordJdOrderHistory(ctx, jdOrderId, consts.JdOrderChangeTypeSend, jdOrder.OrderId, jdOrder.WxPayUrl, "")
|
||||
|
||||
//提取成功要回调
|
||||
go s.callback(ctx, jdOrder.OrderId)
|
||||
|
||||
glog.Info(ctx, "卡密提取成功", g.Map{
|
||||
"jdOrderId": jdOrderId,
|
||||
"cardNo": resp.CardNo,
|
||||
"orderId": jdOrder.OrderId,
|
||||
})
|
||||
|
||||
//提取成功要回调
|
||||
go s.callback(ctx, jdOrder, resp.CardNo, resp.CardPassword)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// callback TODO:临时的回调
|
||||
func (s *sJdCookie) callback(ctx context.Context, orderId string) {
|
||||
var order *entity.V1JdCookieOrder
|
||||
// callback
|
||||
func (s *sJdCookie) callback(ctx context.Context, jdOrder *entity.V1JdCookieJdOrder, cardNo, cardPassword string) {
|
||||
var cookieOrder *entity.V1JdCookieOrder
|
||||
if err := dao.V1JdCookieOrder.Ctx(ctx).DB(config.GetDatabaseV1()).
|
||||
Where(dao.V1JdCookieOrder.Columns().OrderId, orderId).Scan(&order); err != nil || order == nil || order.Id == 0 {
|
||||
glog.Error(ctx, "查询订单失败", g.Map{"orderId": orderId, "err": err})
|
||||
Where(dao.V1JdCookieOrder.Columns().OrderId, jdOrder.OrderId).Scan(&cookieOrder); err != nil || cookieOrder == nil || cookieOrder.Id == 0 {
|
||||
glog.Error(ctx, "查询订单失败", g.Map{"orderId": jdOrder.OrderId, "err": err})
|
||||
return
|
||||
}
|
||||
|
||||
var data *entity.V1OrderInfo
|
||||
if err := dao.V1OrderInfo.Ctx(ctx).DB(config.GetDatabaseV1()).Where(dao.V1OrderInfo.Columns().BankOrderId, order.UserOrderId).Scan(&data); err != nil || data == nil || data.Id == 0 {
|
||||
glog.Error(ctx, "查询订单失败", g.Map{"userOrderId": order.UserOrderId, "err": err})
|
||||
var orderInfo *entity.V1OrderInfo
|
||||
if err := dao.V1OrderInfo.Ctx(ctx).DB(config.GetDatabaseV1()).Where(dao.V1OrderInfo.Columns().BankOrderId, cookieOrder.UserOrderId).Scan(&orderInfo); err != nil || orderInfo == nil || orderInfo.Id == 0 {
|
||||
glog.Error(ctx, "查询订单失败", g.Map{"userOrderId": cookieOrder.UserOrderId, "err": err})
|
||||
return
|
||||
}
|
||||
response, _ := gclient.New().Get(ctx, "http://kami_gateway:12309/appleCard/notify", g.Map{
|
||||
"attach": data.BankOrderId,
|
||||
"merchantId": orderId,
|
||||
"amount": order.Amount,
|
||||
"status": "1",
|
||||
"sign": "123456",
|
||||
var merchantInfo *entity.V1MerchantInfo
|
||||
if err := dao.V1MerchantInfo.Ctx(ctx).DB(config.GetDatabaseV1()).Where(dao.V1MerchantInfo.Columns().MerchantUid, orderInfo.MerchantUid).Scan(&merchantInfo); err != nil || merchantInfo == nil || merchantInfo.Id == 0 {
|
||||
glog.Error(ctx, "查询商户信息失败", g.Map{"merchantId": orderInfo.MerchantUid, "err": err})
|
||||
return
|
||||
}
|
||||
_, err := kami_gateway.SubmitOrder(ctx, &kami_gateway.SubmitOrderReq{
|
||||
OrderPeriod: 24,
|
||||
NotifyUrl: "https://baidu.com",
|
||||
OrderPrice: orderInfo.OrderAmount.String(),
|
||||
OrderNo: orderInfo.MerchantOrderId,
|
||||
ProductCode: orderInfo.PayProductCode,
|
||||
ExValue: (&kami_gateway.RedeemCardInfo{
|
||||
FaceType: orderInfo.OrderAmount.String(),
|
||||
RecoveryType: "8",
|
||||
Data: cardNo,
|
||||
CardNo: cardPassword,
|
||||
}).ToJson(),
|
||||
Ip: "127.0.0.1",
|
||||
PayKey: merchantInfo.MerchantKey,
|
||||
PaySecret: merchantInfo.MerchantSecret,
|
||||
Url: "http://kami_gateway",
|
||||
})
|
||||
glog.Info(ctx, "回调成功", g.Map{"response": response.ReadAllString()})
|
||||
if err != nil {
|
||||
_ = s.RecordJdOrderHistory(ctx, jdOrder.JdOrderId, consts.JdOrderChangeTypeCallback, "", "", "回调失败")
|
||||
} else {
|
||||
_ = s.RecordJdOrderHistory(ctx, jdOrder.JdOrderId, consts.JdOrderChangeTypeCallback, "", "", "回调成功")
|
||||
}
|
||||
}
|
||||
|
||||
// shouldExtractCard 判断是否需要提取卡密
|
||||
@@ -596,8 +615,8 @@ func (s *sJdCookie) CleanupExpiredOrders(ctx context.Context) error {
|
||||
_, err = jdOrderModel.
|
||||
Where(dao.V1JdCookieJdOrder.Columns().Status, int(consts.JdOrderStatusPending)).
|
||||
WhereLT(dao.V1JdCookieJdOrder.Columns().OrderExpireAt, gtime.Now()).
|
||||
Update(g.Map{
|
||||
dao.V1JdCookieJdOrder.Columns().Status: int(consts.JdOrderStatusExpired),
|
||||
Update(do.V1JdCookieJdOrder{
|
||||
Status: int(consts.JdOrderStatusExpired),
|
||||
})
|
||||
if err != nil {
|
||||
glog.Error(ctx, "清理过期京东订单失败", err)
|
||||
@@ -626,7 +645,7 @@ func (s *sJdCookie) CleanupExpiredOrders(ctx context.Context) error {
|
||||
var expiredOrders []*entity.V1JdCookieOrder
|
||||
err = orderModel.
|
||||
Where(dao.V1JdCookieOrder.Columns().Status, int(consts.OrderStatusPending)).
|
||||
WhereLT(dao.V1JdCookieOrder.Columns().CreatedAt, gtime.Now().Add(-time.Hour*24)).
|
||||
WhereLT(dao.V1JdCookieOrder.Columns().CreatedAt, gtime.Now().Add(-time.Hour*30)).
|
||||
Scan(&expiredOrders)
|
||||
if err != nil {
|
||||
glog.Error(ctx, "查询过期用户订单失败", err)
|
||||
@@ -636,8 +655,8 @@ func (s *sJdCookie) CleanupExpiredOrders(ctx context.Context) error {
|
||||
// 批量更新过期状态
|
||||
_, err = orderModel.
|
||||
Where(dao.V1JdCookieOrder.Columns().Status, int(consts.OrderStatusPending)).
|
||||
WhereLT(dao.V1JdCookieOrder.Columns().CreatedAt, gtime.Now().Add(-time.Hour*24)).
|
||||
Update(do.V1JdCookieJdOrder{
|
||||
WhereLT(dao.V1JdCookieOrder.Columns().CreatedAt, gtime.Now().Add(-time.Minute*30)).
|
||||
Update(do.V1JdCookieOrder{
|
||||
Status: consts.OrderStatusExpired,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -248,7 +248,6 @@ func (s *sJdCookie) findReusableJdOrder(ctx context.Context, amount float64, cat
|
||||
WhereNull(dao.V1JdCookieJdOrder.Columns().OrderId).
|
||||
WhereGT(dao.V1JdCookieJdOrder.Columns().OrderExpireAt, gtime.Now()).
|
||||
OrderAsc(dao.V1JdCookieJdOrder.Columns().CreatedAt).
|
||||
Limit(1).
|
||||
Scan(&jdOrder)
|
||||
|
||||
return
|
||||
|
||||
@@ -55,7 +55,12 @@ func Register(ctx context.Context) {
|
||||
tracer := gtrace.NewTracer("京东支付状态监控任务")
|
||||
ctx, span := tracer.Start(ctx, "京东支付状态监控任务", trace.WithNewRoot())
|
||||
defer span.End()
|
||||
|
||||
if err := service.JdCookie().ReleaseExpiredJdOrders(ctx); err != nil {
|
||||
glog.Error(ctx, "释放过期京东订单失败", err)
|
||||
}
|
||||
if err := service.JdCookie().CleanupExpiredOrders(ctx); err != nil {
|
||||
glog.Error(ctx, "清理过期订单失败", err)
|
||||
}
|
||||
glog.Debug(ctx, "开始执行京东支付状态监控任务")
|
||||
if err := service.JdCookie().BatchCheckPaymentStatus(ctx); err != nil {
|
||||
glog.Error(ctx, "京东支付状态监控任务失败", err)
|
||||
|
||||
47
utility/integration/kami_gateway/client.go
Normal file
47
utility/integration/kami_gateway/client.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package kami_gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
"github.com/gogf/gf/v2/net/gclient"
|
||||
"github.com/gogf/gf/v2/net/gtrace"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
func SubmitOrder(ctx context.Context, input *SubmitOrderReq) (*SubmitOrderResponse, error) {
|
||||
ctx, span := gtrace.NewSpan(ctx, "submitOrder", trace.WithAttributes(
|
||||
attribute.String("input", fmt.Sprintf("%+v", input))),
|
||||
)
|
||||
|
||||
defer span.End()
|
||||
input.NotifyUrl = input.GetNotifyUrl()
|
||||
params := input.ToStrMap()
|
||||
params["sign"] = GetMD5SignMF(params, input.PaySecret)
|
||||
|
||||
paramsStr := map[string]string{}
|
||||
for k, v := range params {
|
||||
paramsStr[k] = convertor.ToString(v)
|
||||
}
|
||||
|
||||
response, err := gclient.New().PostForm(ctx, input.GetUrl(), paramsStr)
|
||||
if err != nil {
|
||||
glog.Error(ctx, "提交订单失败", "url", input.GetUrl(), "params", params, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respData := response.ReadAll()
|
||||
glog.Info(ctx, "提交订单成功", "url", input.GetUrl(), "params", params, "response", string(respData))
|
||||
|
||||
submitOrderResponse := SubmitOrderResponse{}
|
||||
err = json.Unmarshal(respData, &submitOrderResponse)
|
||||
if err != nil {
|
||||
glog.Error(ctx, "解析订单失败", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
return &submitOrderResponse, nil
|
||||
}
|
||||
65
utility/integration/kami_gateway/model.go
Normal file
65
utility/integration/kami_gateway/model.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package kami_gateway
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type SubmitOrderReq struct {
|
||||
OrderPeriod int `json:"orderPeriod"`
|
||||
NotifyUrl string `json:"notifyUrl"`
|
||||
OrderPrice string `json:"orderPrice"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
ProductCode string `json:"productCode"`
|
||||
ExValue string `json:"exValue"`
|
||||
Ip string `json:"ip"`
|
||||
PayKey string `json:"payKey"`
|
||||
PaySecret string `json:"paySecret"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func (s *SubmitOrderReq) GetUrl() string {
|
||||
return fmt.Sprintf("%s/gateway/scan", "http://kami_gateway")
|
||||
}
|
||||
|
||||
func (s *SubmitOrderReq) ToStrMap() map[string]any {
|
||||
data := map[string]any{
|
||||
"orderPeriod": strconv.Itoa(s.OrderPeriod),
|
||||
"notifyUrl": s.GetNotifyUrl(),
|
||||
"orderPrice": s.OrderPrice,
|
||||
"orderNo": s.OrderNo,
|
||||
"productCode": s.ProductCode,
|
||||
"exValue": s.ExValue,
|
||||
"ip": s.Ip,
|
||||
"payKey": s.PayKey,
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (s *SubmitOrderReq) GetNotifyUrl() string {
|
||||
notifyUrl := s.NotifyUrl
|
||||
if notifyUrl != "" {
|
||||
return notifyUrl
|
||||
}
|
||||
return fmt.Sprintf("%s/myself/notify", "http://kami_gateway")
|
||||
}
|
||||
|
||||
type SubmitOrderResponse struct {
|
||||
PayKey string `json:"payKey"`
|
||||
StatusCode string `json:"statusCode"`
|
||||
Msg string `json:"msg"`
|
||||
Code int `json:"code"`
|
||||
}
|
||||
|
||||
type RedeemCardInfo struct {
|
||||
FaceType string `json:"faceType"` // 面额
|
||||
RecoveryType string `json:"RecoveryType,omitempty"` // 类型 2 仅卡密 8 卡号卡密
|
||||
Data string `json:"data"` // 卡密
|
||||
CardNo string `json:"cardNo,omitempty"` // 卡号
|
||||
}
|
||||
|
||||
func (d *RedeemCardInfo) ToJson() string {
|
||||
jsonStr, _ := json.Marshal(d)
|
||||
return string(jsonStr)
|
||||
}
|
||||
51
utility/integration/kami_gateway/utils.go
Normal file
51
utility/integration/kami_gateway/utils.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package kami_gateway
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"sort"
|
||||
|
||||
"github.com/duke-git/lancet/v2/convertor"
|
||||
)
|
||||
|
||||
func GetMD5SignMF(params map[string]any, paySecret string) string {
|
||||
strArr := SortMap(params)
|
||||
signStr := ""
|
||||
for i := range strArr {
|
||||
k := strArr[i]
|
||||
if len(convertor.ToString(params[k])) == 0 {
|
||||
signStr += k
|
||||
} else {
|
||||
signStr += k + convertor.ToString(params[k])
|
||||
}
|
||||
}
|
||||
signStr += paySecret
|
||||
return GetMd5Lower(signStr)
|
||||
}
|
||||
|
||||
// SortMap 对map的key值进行排序
|
||||
func SortMap(m map[string]any) []string {
|
||||
var arr []string
|
||||
for k := range m {
|
||||
arr = append(arr, k)
|
||||
}
|
||||
sort.Strings(arr)
|
||||
return arr
|
||||
}
|
||||
|
||||
// SortMapByKeys 按照key的ascii值从小到大给map排序
|
||||
func SortMapByKeys(m map[string]any) map[string]any {
|
||||
keys := SortMap(m)
|
||||
tmp := make(map[string]any)
|
||||
for _, key := range keys {
|
||||
tmp[key] = convertor.ToString(m[key])
|
||||
}
|
||||
return tmp
|
||||
}
|
||||
|
||||
// GetMd5Lower 获取小写的MD5
|
||||
func GetMd5Lower(s string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(s))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
@@ -18,10 +18,3 @@ func ErrIsNil(ctx context.Context, err error, msg ...string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ValueIsNil 判断值是否是nil
|
||||
func ValueIsNil(value interface{}, msg string) {
|
||||
if g.IsNil(value) {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user