483 lines
16 KiB
Go
483 lines
16 KiB
Go
package third_party
|
||
|
||
import (
|
||
"context"
|
||
"crypto/aes"
|
||
"crypto/cipher"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"gateway/internal/config"
|
||
"gateway/internal/models/merchant"
|
||
"gateway/internal/models/order"
|
||
"gateway/internal/models/payfor"
|
||
"gateway/internal/models/road"
|
||
"gateway/internal/models/supply_model"
|
||
"gateway/internal/otelTrace"
|
||
|
||
"gateway/internal/service"
|
||
"gateway/internal/service/supplier"
|
||
"gateway/internal/utils"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/beego/beego/v2/client/httplib"
|
||
"github.com/duke-git/lancet/v2/convertor"
|
||
"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 MFCardV2Impl struct {
|
||
web.Controller
|
||
}
|
||
|
||
// HasDependencyHTML 是否有单独的支付页面
|
||
func (c *MFCardV2Impl) HasDependencyHTML() bool {
|
||
return false
|
||
}
|
||
|
||
func (c *MFCardV2Impl) SendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string) (bool, string) {
|
||
appKey := gojson.Json(jsonStr).Get("appKey").Tostring()
|
||
appSecret := gojson.Json(jsonStr).Get("appSecret").Tostring()
|
||
goodsSku := gojson.Json(jsonStr).Get("goodsSku").Tostring()
|
||
if appKey == "" || appSecret == "" {
|
||
return false, ""
|
||
}
|
||
cfg := config.Config{}
|
||
|
||
params := make(map[string]any)
|
||
params["app_key"] = appKey
|
||
params["goods_sku"] = goodsSku // 卡片类型
|
||
params["face_val"] = cardInfo.FaceType // 提交面值
|
||
params["callback_url"] = cfg.GetMfNotifyUrl() // 回调地址
|
||
params["attach"] = attach // 附带参数
|
||
if cardInfo.RecoveryType == "8" {
|
||
params["card_no"] = cardInfo.CardNo // 卡号
|
||
}
|
||
params["third_order_id"] = attach
|
||
var err error
|
||
params["card_pwd"], err = c.kMEncrypt(cardInfo.Data, appSecret) // 卡密
|
||
if err != nil {
|
||
return false, "加密失败"
|
||
}
|
||
params["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
|
||
sign := utils.GetMD5SignMF(params, appSecret)
|
||
params["sign"] = sign
|
||
|
||
otelTrace.Logger.WithContext(ctx).Info("params", zap.Any("params", params))
|
||
url, err := cfg.GetMFCardSubmitUrl()
|
||
if err != nil {
|
||
return false, ""
|
||
}
|
||
req := httplib.NewBeegoRequestWithCtx(ctx, url, "POST")
|
||
req.SetTransport(otelhttp.NewTransport(http.DefaultTransport))
|
||
req.SetTimeout(time.Second*30, time.Second*30)
|
||
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, "内部错误请稍后再试试(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 false, ""
|
||
}
|
||
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
|
||
if gojson.Json(response).Get("code").Tostring() == "" {
|
||
otelTrace.Logger.WithContext(ctx).Error("远程调用失败")
|
||
return false, ""
|
||
}
|
||
type AutoGenerated struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
Stime float64 `json:"stime"`
|
||
Etime float64 `json:"etime"`
|
||
Data struct {
|
||
Cards []interface{} `json:"cards"`
|
||
ErrorData []struct {
|
||
Msg string `json:"msg"`
|
||
Data string `json:"data"`
|
||
} `json:"error_data"`
|
||
} `json:"data"`
|
||
}
|
||
if gojson.Json(response).Get("code").Tostring() != "0" {
|
||
var resData AutoGenerated
|
||
_ = json.Unmarshal([]byte(response), &resData)
|
||
errorMsg := ""
|
||
if len(resData.Data.ErrorData) > 0 {
|
||
errorMsg = resData.Data.ErrorData[0].Msg
|
||
}
|
||
return false, fmt.Sprintf("错误原因:%s,具体原因:%s", resData.Message, errorMsg)
|
||
}
|
||
return true, response
|
||
}
|
||
|
||
func (c *MFCardV2Impl) Scan(ctx context.Context, orderInfo order.OrderInfo, roadInfo road.RoadInfo, merchantInfo merchant.MerchantInfo) supplier.ScanData {
|
||
ctx, cancel := otelTrace.Span(ctx, "蜜蜂178", "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), zap.Error(err))
|
||
return supplier.ScanData{Status: "-1", Msg: "订单有有误,请稍后再试"}
|
||
}
|
||
ok, str := c.SendCard(ctx, roadInfo.Params, cdata, orderInfo.BankOrderId)
|
||
if !ok {
|
||
return supplier.ScanData{Status: "-1", Msg: "订单有有误,请稍后再试:" + str}
|
||
}
|
||
if str == "" {
|
||
return supplier.ScanData{Status: "-1", Msg: "订单有有误,请稍后再试:" + str}
|
||
}
|
||
|
||
retStr := gojson.Json(str).Get("code").Tostring()
|
||
if retStr != "0" {
|
||
return supplier.ScanData{Status: "-1", Msg: gojson.Json(str).Get("msg").Tostring()}
|
||
}
|
||
|
||
if err != nil {
|
||
otelTrace.Logger.WithContext(ctx).Error("SolvePaySuccess失败:", zap.Error(err))
|
||
return supplier.ScanData{Status: "-1", Msg: "订单有有误,请稍后再试"}
|
||
}
|
||
var scanData supplier.ScanData
|
||
scanData.Status = "00"
|
||
scanData.OrderNo = orderInfo.BankOrderId
|
||
scanData.BankNo = orderInfo.MerchantOrderId
|
||
scanData.OrderPrice = strconv.FormatFloat(orderInfo.OrderAmount, 'f', 2, 64)
|
||
if jsStr, err2 := sonic.GetFromString(str); err2 == nil {
|
||
if s, err3 := jsStr.Get("data").Get("cards").Index(0).Raw(); err3 == nil {
|
||
scanData.ReturnData = s
|
||
}
|
||
}
|
||
return scanData
|
||
}
|
||
|
||
// KMEncrypt 加密卡密
|
||
func (c *MFCardV2Impl) kMEncrypt(kf, appSecret string) (string, error) {
|
||
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "蜜蜂178", "kMEncrypt")
|
||
defer cancel()
|
||
secret := utils.GetMD5LOWER(appSecret)[:16] // 加密秘钥
|
||
block, err := aes.NewCipher([]byte(secret))
|
||
if err != nil {
|
||
otelTrace.Logger.WithContext(ctx).Error("Joker: AesDecrypt failed to NewCipher")
|
||
return "", err
|
||
}
|
||
// 数据填充
|
||
plaintext := utils.PadType(kf)
|
||
iv := "0102030405060708"
|
||
mode := cipher.NewCBCEncrypter(block, []byte(iv))
|
||
mode.CryptBlocks(plaintext, plaintext)
|
||
return base64.StdEncoding.EncodeToString(plaintext), nil
|
||
}
|
||
|
||
func (c *MFCardV2Impl) PayNotify() {
|
||
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "蜜蜂178", "PayNotify")
|
||
defer cancel()
|
||
otelTrace.Logger.WithContext(ctx).Info("消息回调成功!!!")
|
||
params := make(map[string]any)
|
||
attach := strings.TrimSpace(c.GetString("attach"))
|
||
orderInfo := order.GetOrderByBankOrderId(ctx, attach) // OrderId
|
||
if orderInfo.BankOrderId == "" || len(orderInfo.BankOrderId) == 0 {
|
||
otelTrace.Logger.WithContext(ctx).Error("【MF178】回调的订单号不存在,订单号=", zap.String("attach", attach))
|
||
c.StopRun()
|
||
return
|
||
}
|
||
roadInfo := road.GetRoadInfoByRoadUid(ctx, orderInfo.RoadUid)
|
||
if roadInfo.RoadUid == "" || len(roadInfo.RoadUid) == 0 {
|
||
otelTrace.Logger.WithContext(ctx).Error("【MF178】支付通道已经关系或者删除,不进行回调")
|
||
c.StopRun()
|
||
return
|
||
}
|
||
merchantUid := orderInfo.MerchantUid
|
||
merchantInfo := merchant.GetMerchantByUid(ctx, merchantUid)
|
||
if merchantInfo.MerchantUid == "" || len(merchantInfo.MerchantUid) == 0 {
|
||
otelTrace.Logger.WithContext(ctx).Error("【MF178】快付回调失败,该商户不存在或者已经删除,商户uid=", zap.String("merchantUid", merchantUid))
|
||
c.StopRun()
|
||
return
|
||
}
|
||
paySecretGroup, err := sonic.GetFromString(roadInfo.Params)
|
||
if err != nil {
|
||
otelTrace.Logger.WithContext(ctx).Error("获取蜜蜂秘钥失败", zap.String("merchantUid", merchantUid))
|
||
c.StopRun()
|
||
return
|
||
}
|
||
paySecret, _ := paySecretGroup.Get("appSecret").String()
|
||
params["order_id"] = strings.TrimSpace(c.GetString("order_id"))
|
||
params["third_order_id"] = strings.TrimSpace(c.GetString("third_order_id")) // 时间戳
|
||
params["card_no"] = strings.TrimSpace(c.GetString("card_no")) // 卡号
|
||
params["card_pwd"] = strings.TrimSpace(c.GetString("card_pwd")) // 时间戳
|
||
params["face_val"] = strings.TrimSpace(c.GetString("face_val")) // 提交面额
|
||
params["amount"] = strings.TrimSpace(c.GetString("amount")) // 实际面额
|
||
params["discount"] = strings.TrimSpace(c.GetString("discount")) // 折扣
|
||
params["remark"] = strings.TrimSpace(c.GetString("remark")) // 备注
|
||
params["attach"] = attach // 附加备注
|
||
params["timestamp"] = strings.TrimSpace(c.GetString("timestamp")) // 时间戳
|
||
params["status"] = strings.TrimSpace(c.GetString("status")) // 状态
|
||
// 对参数进行验签
|
||
tmpSign := utils.GetMD5SignMF(params, paySecret)
|
||
sign := strings.TrimSpace(c.GetString("sign"))
|
||
params["sign"] = sign // 签名
|
||
if tmpSign != sign {
|
||
otelTrace.Logger.WithContext(ctx).Error("【MF178】回调签名异常,回调失败")
|
||
c.StopRun()
|
||
return
|
||
}
|
||
// 实际支付金额
|
||
factAmount, err := strconv.ParseFloat(convertor.ToString(params["amount"]), 64)
|
||
if err != nil {
|
||
orderInfo.FactAmount = 0
|
||
}
|
||
if params["status"] == "8" { // 失败
|
||
otelTrace.Logger.WithContext(ctx).Info("【MF178】回调失败,订单信息", zap.Any("params", params))
|
||
if !service.SolvePayFail(ctx, convertor.ToString(params["order_id"]), "", "") {
|
||
otelTrace.Logger.WithContext(ctx).Error("solve order fail fail")
|
||
}
|
||
} else if params["status"] == "9" && factAmount == orderInfo.FactAmount {
|
||
// TODO 订单支付成功
|
||
service.SolvePaySuccess(ctx, convertor.ToString(params["order_id"]), factAmount, convertor.ToString(params["order_id"]), "支付成功")
|
||
}
|
||
c.Ctx.WriteString("SUCCESS")
|
||
}
|
||
|
||
func (c *MFCardV2Impl) PayQuery(orderInfo order.OrderInfo, roadInfo road.RoadInfo) bool {
|
||
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "蜜蜂178", "PayQuery")
|
||
defer cancel()
|
||
params := map[string]any{}
|
||
|
||
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{}
|
||
url, err := cfg.GetMFCardQueryUrl()
|
||
|
||
req := httplib.Post(url)
|
||
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 *MFCardV2Impl) PayQueryV2(orderInfo order.OrderInfo, roadInfo road.RoadInfo) supply_model.MsgModel {
|
||
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "蜜蜂178", "PayQueryV2")
|
||
defer cancel()
|
||
params := map[string]any{}
|
||
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{}
|
||
url, err := cfg.GetMFCardQueryUrl()
|
||
|
||
req := httplib.Post(url)
|
||
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 *MFCardV2Impl) PayFor(info payfor.PayforInfo) string {
|
||
return ""
|
||
}
|
||
|
||
func (c *MFCardV2Impl) PayForQuery(payFor payfor.PayforInfo) (string, string) {
|
||
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "蜜蜂178", "PayForQuery")
|
||
defer cancel()
|
||
cfg := config.Config{}
|
||
url, err := cfg.GetMFCardQueryUrl()
|
||
if err != nil {
|
||
return config.PAYFOR_FAIL, ""
|
||
}
|
||
|
||
params := map[string]string{}
|
||
params["order_id"] = payFor.BankOrderId
|
||
params["app_key"] = gojson.Json("").Get("appKey").Tostring()
|
||
|
||
req := httplib.Post(url)
|
||
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 *MFCardV2Impl) BalanceQuery(roadInfo road.RoadInfo) float64 {
|
||
return 0.00
|
||
}
|
||
|
||
func (c *MFCardV2Impl) PayForNotify() string {
|
||
return ""
|
||
}
|