489 lines
16 KiB
Go
489 lines
16 KiB
Go
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 ""
|
||
}
|