Files
kami_gateway/internal/service/supplier/third_party/mf178_v2.go
2025-02-24 21:45:06 +08:00

483 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"
"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 ""
}