Files
kami_gateway/internal/service/supplier/third_party/walmart.go
danial e88ff05a14 refactor(trace): 重命名 otel 包为 otelTrace并更新相关引用
- 将内部使用的 otel 包重命名为 otelTrace
- 更新了所有引用该包的文件中的导入路径
- 修改了部分函数和变量名称以适应新的包名
2025-02-23 21:56:29 +08:00

489 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package third_party
import (
"context"
"encoding/json"
"fmt"
"gateway/internal/config"
"gateway/internal/otelTrace"
"gateway/internal/service/supplier"
"gateway/internal/models/merchant"
"gateway/internal/models/order"
"gateway/internal/models/payfor"
"gateway/internal/models/road"
"gateway/internal/models/supply_model"
"gateway/internal/service"
"gateway/internal/utils"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/httplib"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/beego/beego/v2/server/web"
"github.com/bytedance/sonic"
"github.com/widuu/gojson"
)
type WalMartImpl struct {
web.Controller
}
// WalMartOrderStatus 充值编码
type WalMartOrderStatus int
const (
WalMartOrderSuccess WalMartOrderStatus = 200 // 绑定成功
WalMartOrderRepeat WalMartOrderStatus = 201 // 绑定成功,金鼎不符
WalMartOrderFail WalMartOrderStatus = 511 // 已经绑定过的卡密
WalMartOrderFailWithUnknown WalMartOrderStatus = 500 // 失败状态(其他原因)
)
// HasDependencyHTML 是否有单独的支付页面
func (c *WalMartImpl) HasDependencyHTML() bool {
return false
}
func (w *WalMartOrderStatus) String() string {
switch *w {
case WalMartOrderSuccess:
return "充值成功"
case WalMartOrderRepeat:
return "绑定成功,金鼎不符"
case WalMartOrderFail:
return "已经绑定过的卡密"
case WalMartOrderFailWithUnknown:
return "失败状态(其他原因)"
}
return "未知状态"
}
func (c *WalMartImpl) verifyCardNo(cardNo string) bool {
// 验证卡号是否正常
// 限制出现 A、B、E、I、O、S、U、1、0的卡密
forbiddenList := []string{"A", "B", "E", "I", "O", "S", "U", "1", "0"}
for _, s := range forbiddenList {
if strings.Contains(cardNo, s) {
return false
}
}
return true
}
func (c *WalMartImpl) sendEncrypt(ci SendCardInfo, secret string) string {
// 构造待加密的字符串
params := []string{
fmt.Sprintf("actionType=%d", ci.ActionType),
fmt.Sprintf("bizNotifyUrl=%s", ci.BizNotifyUrl),
fmt.Sprintf("bizOrderNo=%s", ci.BizOrderNo),
fmt.Sprintf("cardKey=%s", ci.CardKey),
fmt.Sprintf("cardPwd=%s", ci.CardPwd),
fmt.Sprintf("expectAmount=%d", ci.ExpectAmount),
fmt.Sprintf("expectCardType=%d", ci.ExpectCardType),
fmt.Sprintf("merchantId=%s", ci.MerchantId),
fmt.Sprintf("notify=%d", ci.Notify),
fmt.Sprintf("secret=%s", secret),
}
data := strings.Join(params, "&")
// md5加密
return utils.EncodeMd5([]byte(data))
}
func (c *WalMartImpl) notifyEncrypt(ci ResponseStruct, secret string) string {
// 构造待加密的字符串
params := []string{
fmt.Sprintf("bindState=%d", ci.Data.BindState),
fmt.Sprintf("bizOrderNo=%s", url.QueryEscape(ci.Data.BizOrderNo)),
fmt.Sprintf("cardAmount=%d", ci.Data.CardAmount),
fmt.Sprintf("cardType=%d", ci.Data.CardType),
fmt.Sprintf("cardTypeName=%s", url.QueryEscape(ci.Data.CardTypeName)),
fmt.Sprintf("secret=%s", url.QueryEscape(secret)),
}
// 按照指定顺序拼接参数
paramString := strings.Join(params, "&")
return utils.EncodeMd5([]byte(paramString))
}
// SendCardInfo 结构体用于表示卡信息
type SendCardInfo struct {
MerchantId string `json:"merchantId"` // 核销租户id核销平台登录后可查看
ActionType int `json:"actionType"` // 绑卡固定传2
CardPwd string `json:"cardPwd"` // 卡密如果是京东E卡格式为AAAA-BBBB-CCCC-DDDD沃尔玛礼品卡是一串数字例如23263942357982372978天猫卡是一串数字例如3102460223476581231
CardKey string `json:"cardKey"` // 卡的充值密码京东E卡不传沃尔玛卡、天猫卡必须传密码
Notify int `json:"notify"` // 是否需要通知回调1或0
BizOrderNo string `json:"bizOrderNo"` // 租户方业务单号
BizNotifyUrl string `json:"bizNotifyUrl"` // 通知地址
ExpectAmount int `json:"expectAmount"` // 订单金额必填(单位分)
ExpectCardType int `json:"expectCardType"` // 卡密类型京东E卡传3沃尔玛卡传23天猫卡传33
}
func (c *WalMartImpl) SendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string) (bool, string) {
cfg := config.GetConfig()
expectAmount, err := strconv.ParseInt(cardInfo.FaceType, 10, 64)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("金额转换失败", zap.Error(err))
expectAmount = 0
}
params := SendCardInfo{
MerchantId: "1865764189252980736",
ActionType: 2,
CardPwd: cardInfo.CardNo,
CardKey: cardInfo.Data,
Notify: 1,
BizOrderNo: attach,
BizNotifyUrl: cfg.GetWalMartNotifyUrl(),
ExpectAmount: int(expectAmount),
ExpectCardType: 23,
}
requestData := map[string]interface{}{
"sign": c.sendEncrypt(params, "0468b97dfd8747f5ad57e06a17e03c365ae3269d70c14db39ddea25e70d55bbf"),
"data": params,
}
req := httplib.NewBeegoRequestWithCtx(ctx, "http://47.76.153.52/merchant/api/order", "POST")
req.SetTransport(otelhttp.NewTransport(http.DefaultTransport))
marshal, err := json.Marshal(requestData)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Map转化为byte数组失败,异常。", zap.Error(err))
return false, "内部错误请稍后再试试01"
}
otelTrace.Logger.WithContext(ctx).Info("请求参数:" + string(marshal))
req.Header("Content-Type", "application/json")
req.Body(marshal)
req.SetTimeout(time.Second*30, time.Second*30)
req.Header("Accept-Charset", "utf-8")
response, err := req.String()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Apple GetToken 请求失败:", zap.Error(err))
return false, ""
}
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
type AutoGenerated struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
OrderNo string `json:"orderNo"`
} `json:"data"`
}
var resData AutoGenerated
err = json.Unmarshal([]byte(response), &resData)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err), zap.String("response", response))
return false, "内部数据处理失败"
}
if resData.Code != 200 {
otelTrace.Logger.WithContext(ctx).Error("充值错误", zap.String("Message", resData.Message))
return false, resData.Message
}
return true, response
}
func (c *WalMartImpl) Scan(ctx context.Context, orderInfo order.OrderInfo, roadInfo road.RoadInfo, merchantInfo merchant.MerchantInfo) supplier.ScanData {
ctx, cancel := otelTrace.Span(ctx, "WalMartImpl", "Scan", trace.WithAttributes(
attribute.String("BankOrderId", orderInfo.BankOrderId),
attribute.String("MerchantUid", orderInfo.MerchantUid),
attribute.String("ExValue", orderInfo.ExValue),
))
defer cancel()
cdata := supplier.RedeemCardInfo{}
err := json.Unmarshal([]byte(orderInfo.ExValue), &cdata)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("格式化数据失败", zap.String("ExValue", orderInfo.ExValue))
return supplier.ScanData{Status: "-1", Msg: "订单有有误,请稍后再试"}
}
ok, str := c.SendCard(ctx, roadInfo.Params, cdata, orderInfo.BankOrderId)
var scanData supplier.ScanData
if !ok {
scanData = supplier.ScanData{
Status: "-1",
Msg: "订单有有误,请稍后再试:" + str,
BankNo: orderInfo.MerchantOrderId,
OrderNo: orderInfo.BankOrderId,
ReturnData: str,
}
return scanData
}
scanData.Status = "00"
scanData.OrderNo = orderInfo.BankOrderId
scanData.BankNo = orderInfo.MerchantOrderId
scanData.OrderPrice = strconv.FormatFloat(orderInfo.OrderAmount, 'f', 2, 64)
scanData.ReturnData = str
return scanData
}
// ResponseStruct 结构体用于表示卡信息
type ResponseStruct struct {
Sign string `json:"sign"`
Data struct {
BizOrderNo string `json:"bizOrderNo"` // 租户方提交的订单号
CardAmount int `json:"cardAmount"` // 卡密金额(单位分)
CardTypeName string `json:"cardTypeName"` // 卡密类型中文
CardType int `json:"cardType"` // 卡片类型
BindState int `json:"bindState"` // 绑定结果,请查看字典表章节
} `json:"data"`
}
func (c *WalMartImpl) PayNotify() {
ctx := c.Ctx.Request.Context()
otelTrace.Logger.WithContext(ctx).Info("【沃尔玛】快付回调开始")
response := ResponseStruct{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &response)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err), zap.String("RequestBody", string(c.Ctx.Input.RequestBody)))
c.Ctx.WriteString("fail")
return
}
if c.notifyEncrypt(response, "0468b97dfd8747f5ad57e06a17e03c365ae3269d70c14db39ddea25e70d55bbf") != response.Sign {
otelTrace.Logger.WithContext(ctx).Sugar().Errorf("【沃尔玛】快付回调失败,签名错误,参数=%s", string(c.Ctx.Input.RequestBody))
c.Ctx.WriteString("fail")
return
}
orderInfo := order.GetOrderByBankOrderId(ctx, response.Data.BizOrderNo) // OrderId
merchantUid := orderInfo.MerchantUid
merchantInfo := merchant.GetMerchantByUid(ctx, merchantUid)
if merchantInfo.MerchantUid == "" || len(merchantInfo.MerchantUid) == 0 {
otelTrace.Logger.WithContext(ctx).Sugar().Errorf("【沃尔玛】快付回调失败该商户不存在或者已经删除商户uid=%s\n", merchantUid)
c.Ctx.WriteString("fail")
return
}
orderInfo.BankTransId = response.Data.BizOrderNo
if WalMartOrderStatus(response.Data.BindState) == WalMartOrderSuccess {
isOk := service.SolvePaySuccess(ctx, orderInfo.BankOrderId, orderInfo.FactAmount, response.Data.BizOrderNo, "支付成功")
if isOk {
c.Ctx.WriteString("success")
} else {
c.Ctx.WriteString("fail")
}
}
status := WalMartOrderStatus(response.Data.BindState)
order.InsertCardReturnData(orderInfo.MerchantOrderId, status.String())
return
}
func (c *WalMartImpl) PayQuery(orderInfo order.OrderInfo, roadInfo road.RoadInfo) bool {
params := map[string]any{}
ctx := c.Ctx.Request.Context()
cardData, err := sonic.GetFromString(orderInfo.CardReturnData)
if err != nil {
return false
}
orderId, err := cardData.Get("order_id").String()
if err != nil {
return false
}
params["order_id"] = orderId
params["app_key"] = gojson.Json(roadInfo.Params).Get("appKey").Tostring()
params["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
params["sign"] = utils.GetMD5SignMF(params, gojson.Json(roadInfo.Params).Get("appSecret").Tostring())
cfg := config.Config{}
url1, err := cfg.GetMFCardQueryUrl()
req := httplib.Post(url1)
marshal, err := json.Marshal(params)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Map转化为byte数组失败,异常。", zap.Error(err))
// fmt.Printf("Map转化为byte数组失败,异常:%s\n",zap.Error(err))
return false
}
otelTrace.Logger.WithContext(ctx).Info("请求参数:" + string(marshal))
req.Header("Content-Type", "application/json")
req.Body(marshal)
req.Header("Accept-Charset", "utf-8")
response, err := req.String()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("MF GetToken 请求失败:", zap.Error(err))
return false
}
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
resData, err := sonic.GetFromString(response)
if err != nil {
return false
}
resStatus, err := resData.Get("data").Get("status").Int64()
if err != nil {
return false
}
resCode, err := resData.Get("code").Int64()
if err != nil {
return false
}
if resCode == 0 && resStatus == 9 {
return true
}
return false
}
func (c *WalMartImpl) PayQueryV2(orderInfo order.OrderInfo, roadInfo road.RoadInfo) supply_model.MsgModel {
params := map[string]any{}
ctx := c.Ctx.Request.Context()
cardData, err := sonic.GetFromString(orderInfo.CardReturnData)
if err != nil {
return supply_model.CardMsgErr
}
orderId, err := cardData.Get("order_id").String()
if err != nil {
return supply_model.CardMsgErr
}
params["order_id"] = orderId
params["app_key"] = gojson.Json(roadInfo.Params).Get("appKey").Tostring()
params["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
params["sign"] = utils.GetMD5SignMF(params, gojson.Json(roadInfo.Params).Get("appSecret").Tostring())
cfg := config.Config{}
url1, err := cfg.GetMFCardQueryUrl()
req := httplib.Post(url1)
marshal, err := json.Marshal(params)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Map转化为byte数组失败,异常。", zap.Error(err))
// fmt.Printf("Map转化为byte数组失败,异常:%s\n",zap.Error(err))
return supply_model.DataErr
}
otelTrace.Logger.WithContext(ctx).Info("请求参数:" + string(marshal))
req.Header("Content-Type", "application/json")
req.Body(marshal)
req.Header("Accept-Charset", "utf-8")
response, err := req.String()
if err != nil {
return supply_model.RemoteDataErr
}
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
resData, err := sonic.GetFromString(response)
if err != nil {
return supply_model.RemoteDataErr
}
resStatus, err := resData.Get("data").Get("status").Int64()
if err != nil {
return supply_model.RemoteDataErr
}
resCode, err := resData.Get("code").Int64()
if err != nil {
return supply_model.RemoteDataErr
}
if resCode == 0 {
switch resStatus {
case 9:
return supply_model.RemoteSuccess
case 2, 3, 4:
return supply_model.RemoteDataDealing
case 7:
return supply_model.RemoteDataHandErr
case 8:
return supply_model.RemoteDataHealingErr
}
}
return supply_model.RemoteDataErr
}
func (c *WalMartImpl) PayFor(info payfor.PayforInfo) string {
return ""
}
func (c *WalMartImpl) PayForQuery(payFor payfor.PayforInfo) (string, string) {
cfg := config.Config{}
url1, err := cfg.GetMFCardQueryUrl()
if err != nil {
return config.PAYFOR_FAIL, ""
}
ctx := c.Ctx.Request.Context()
params := map[string]string{}
params["order_id"] = payFor.BankOrderId
params["app_key"] = gojson.Json("").Get("appKey").Tostring()
req := httplib.Post(url1)
marshal, err := json.Marshal(params)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Map转化为byte数组失败,异常。", zap.Error(err))
// fmt.Printf("Map转化为byte数组失败,异常:%s\n",zap.Error(err))
return config.PAYFOR_FAIL, "内部错误请稍后再试试01"
}
otelTrace.Logger.WithContext(ctx).Info("请求参数:" + string(marshal))
req.Header("Content-Type", "application/json")
req.Body(marshal)
req.Header("Accept-Charset", "utf-8")
response, err := req.String()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("MF GetToken 请求失败:", zap.Error(err))
return config.PAYFOR_FAIL, ""
}
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
if gojson.Json(response).Get("code").Tostring() == "" {
otelTrace.Logger.WithContext(ctx).Error("远程调用失败")
return config.PAYFOR_BANKING, ""
}
if gojson.Json(response).Get("code").Tostring() == "0" {
type data struct {
OrderID int64 `json:"order_id"`
CardNo string `json:"card_no"`
CardPwd string `json:"card_pwd"`
Status int `json:"status"`
RspInfo string `json:"rsp_info"`
FaceVal int `json:"face_val"`
Amount int `json:"amount"`
Discount string `json:"discount"`
}
var d data
err2 := json.Unmarshal([]byte(gojson.Json(response).Get("data").Tostring()), &d)
if err2 != nil {
return config.PAYFOR_FAIL, ""
}
if d.Status == 9 {
return config.PAYFOR_SUCCESS, ""
}
if d.Status == 4 {
return config.PAYFOR_BANKING, ""
}
if d.Status == 7 || d.Status == 8 {
return config.PAYFOR_FAIL, ""
}
}
otelTrace.Logger.WithContext(ctx).Error("远程调用失败")
return config.PAYFOR_BANKING, ""
}
func (c *WalMartImpl) BalanceQuery(roadInfo road.RoadInfo) float64 {
return 0.00
}
func (c *WalMartImpl) PayForNotify() string {
return ""
}