Files
kami_gateway/internal/service/supplier/third_party/eggplant.go

662 lines
20 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"
"net/url"
"slices"
"sort"
"strconv"
"time"
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/server/web"
"github.com/bytedance/sonic"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/strutil"
"github.com/go-resty/resty/v2"
"github.com/widuu/gojson"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
)
type EggplantImpl struct {
web.Controller
}
// HasDependencyHTML 是否有单独的支付页面
func (c *EggplantImpl) HasDependencyHTML() bool {
return false
}
type eggplantProductCode struct {
ProductCode string `json:"productCode"`
ForwoardUrl string `json:"forwoardUrl"`
CardNo string `json:"cardNo"`
CardPassword string `json:"cardPassword"`
}
func (c *eggplantProductCode) SendData(ctx context.Context, orderId string, forwardUrl *url.URL) (bool, string, string) {
// 通道1
chnnalOneCodeArray := []string{"8055", "8076", "8055"}
if slices.Contains(chnnalOneCodeArray, strutil.Trim(c.ProductCode)) {
return c.channelOne(ctx, forwardUrl)
}
// 通道2
chnnalTwoCodeArray := []string{"8069"}
if slices.Contains(chnnalTwoCodeArray, strutil.Trim(c.ProductCode)) {
return c.channelTwo(ctx, forwardUrl)
}
return false, "通道不存在", ""
}
// 第一个通道
func (c *eggplantProductCode) channelOne(ctx context.Context, forwardUrl *url.URL) (bool, string, string) {
// 获取数据 通道编码 8055
req := httplib.NewBeegoRequestWithCtx(ctx, "https://api.djfkm.xyz/api/order/getOrderById", "GET").SetTransport(otelhttp.NewTransport(http.DefaultTransport)).
SetTimeout(time.Second*30, time.Second*30).Retries(3).SetProxy(func(req *http.Request) (*url.URL, error) {
proxy, err := utils.OrderBasedProxyStrategyInstance.GetProxy(ctx, forwardUrl.Query().Get("orderId"))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("获取代理失败", zap.Error(err))
return nil, err
}
return url.Parse(proxy)
})
req.Param("id", forwardUrl.Query().Get("orderId"))
response, err := req.String()
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("获取订单接口失败", zap.Error(err))
return false, "内部数据处理失败", ""
}
var orderGetOrderByIdResp struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
ID int `json:"id"`
OrderNo string `json:"orderNo"`
TraceNo string `json:"traceNo"`
ChannelCode string `json:"channelCode"`
ChannelName string `json:"channelName"`
Amount float64 `json:"amount"`
OrderTime string `json:"orderTime"`
WarnNotes string `json:"warnNotes"`
ShopPayTimeout string `json:"shopPayTimeout"`
OrderStatus string `json:"orderStatus"`
JumpUrl int `json:"jumpUrl"`
} `json:"data"`
}
err = json.Unmarshal([]byte(response), &orderGetOrderByIdResp)
otelTrace.Logger.WithContext(ctx).Info("获取订单接口", zap.Any("response", response), zap.Any("解析结果", orderGetOrderByIdResp))
if orderGetOrderByIdResp.Code != 200 {
return false, orderGetOrderByIdResp.Msg, ""
}
req = httplib.NewBeegoRequestWithCtx(ctx, "https://api.djfkm.xyz/api/order/shopSubCard", "POST").
SetTransport(otelhttp.NewTransport(http.DefaultTransport)).
SetTimeout(time.Second*30, time.Second*30).Retries(3).SetProxy(func(req *http.Request) (*url.URL, error) {
proxy, err := utils.OrderBasedProxyStrategyInstance.GetProxy(ctx, forwardUrl.Query().Get("orderId"))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("获取代理失败", zap.Error(err))
return nil, err
}
return url.Parse(proxy)
})
jsonBody := struct {
OrderId string `json:"orderId"`
TraceNo string `json:"traceNo"`
Amount int `json:"amount"`
ChannelName string `json:"channelName"`
ChannelCode string `json:"channelCode"`
CardNo string `json:"cardNo"`
CardPassword string `json:"cardPassword"`
}{
OrderId: forwardUrl.Query().Get("orderId"),
TraceNo: orderGetOrderByIdResp.Data.TraceNo,
Amount: int(orderGetOrderByIdResp.Data.Amount),
ChannelName: orderGetOrderByIdResp.Data.ChannelName,
ChannelCode: orderGetOrderByIdResp.Data.ChannelCode,
CardNo: c.CardNo,
CardPassword: c.CardPassword,
}
req, err = req.JSONBody(jsonBody)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("json序列化失败", zap.Error(err))
return false, "内部数据处理失败", ""
}
var respData struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
err = req.ToJSON(&respData)
otelTrace.Logger.WithContext(ctx).Info("提交卡密接口", zap.Any("解析结果", respData), zap.Any("jsonBody", jsonBody))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err))
return false, "内部数据处理失败", ""
}
if respData.Code != 200 {
return false, respData.Msg, ""
}
return true, respData.Msg, ""
}
// 第二个通道
func (c *eggplantProductCode) channelTwo(ctx context.Context, forwardUrl *url.URL) (bool, string, string) {
proxy, _ := utils.OrderBasedProxyStrategyInstance.GetProxy(ctx, forwardUrl.Query().Get("order_no"))
client := resty.New().EnableTrace().SetRetryCount(3).SetRetryWaitTime(time.Second * 30)
client.SetProxy(proxy)
otelTrace.Logger.WithContext(ctx).Info("发送请求信息", zap.Any("forwardUrl", forwardUrl), zap.Any("cardNo", c.CardNo), zap.Any("cardPassword", c.CardPassword), zap.Any("orderNo", forwardUrl.Query().Get("order_no")))
resp, err := client.R().SetFormData(map[string]string{
"order_no": forwardUrl.Query().Get("order_no"),
"card_no": c.CardNo,
"pass": c.CardPassword,
}).Post("http://13.212.74.251:3767/pay")
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("请求失败", zap.Error(err))
return false, "内部数据处理失败", ""
}
var respData struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
err = json.Unmarshal([]byte(resp.String()), &respData)
otelTrace.Logger.WithContext(ctx).Info("返回数据", zap.Any("resp", resp.String()), zap.Any("respData", respData))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err))
return false, "内部数据处理失败", ""
}
// {"code":500,"msg":"订单不可用"}
// TODO: 排除额外情况
if respData.Code == 500 && respData.Msg == "订单不可用" {
return true, fmt.Sprintf("%s按照订单正常处理", respData.Msg), ""
}
if respData.Code != 0 {
return false, respData.Msg, ""
}
return true, respData.Msg, ""
}
// 签名算法
func (c *EggplantImpl) generateSign(ctx context.Context, params map[string]any, key string) string {
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
signStr := ""
for _, k := range keys {
signStr += fmt.Sprintf("%s=%s&", k, convertor.ToString(params[k]))
}
signStr += fmt.Sprintf("Key=%s", key)
return utils.GetMd5Lower(signStr)
}
func (c *EggplantImpl) SendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string) (bool, string, string) {
cfg := new(config.Config)
params := map[string]any{
"mid": gojson.Json(jsonStr).Get("mid").Tostring(),
"order_no": attach,
"client_ip": "127.0.0.1",
"amount": cardInfo.FaceType,
"pay_type": gojson.Json(jsonStr).Get("pay_type").Tostring(),
"notify_url": fmt.Sprintf("%s%s", cfg.GatewayAddr(), "/eggplant/notify"),
}
params["sign"] = c.generateSign(ctx, params, gojson.Json(jsonStr).Get("key").Tostring())
req := httplib.NewBeegoRequestWithCtx(ctx, "https://admin.djfkm.xyz/api/order/create", "POST").
SetTransport(otelhttp.NewTransport(http.DefaultTransport)).
SetTimeout(time.Second*30, time.Second*30).Retries(3)
for k, v := range params {
req.Param(k, convertor.ToString(v))
}
otelTrace.Logger.WithContext(ctx).Info("请求参数:", zap.Any("formdata", params))
response, err := req.String()
type AutoGenerated struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
Mid string `json:"mid"`
OrderNo string `json:"order_no"`
TraceNo string `json:"trace_no"`
Amount string `json:"amount"`
PayType string `json:"pay_type"`
NotifyUrl string `json:"notify_url"`
ForwardUrl string `json:"forward_url"`
} `json:"data,omitempty"`
}
var resData AutoGenerated
err = json.Unmarshal([]byte(response), &resData)
otelTrace.Logger.WithContext(ctx).Info("创建订单页面:", zap.Any("response", response))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err), zap.String("response", response))
return false, "内部数据处理失败", ""
}
if resData.Code != 0 {
return false, resData.Msg, ""
}
// 解析
forwardUrl, err := url.Parse(resData.Data.ForwardUrl)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("解析失败", zap.Error(err))
return false, "内部数据处理失败", ""
}
eggplantProductCodeInstance := &eggplantProductCode{
CardNo: cardInfo.CardNo,
CardPassword: cardInfo.Data,
ProductCode: resData.Data.PayType,
}
ok, str, _ := eggplantProductCodeInstance.SendData(ctx, attach, forwardUrl)
if !ok {
return false, str, ""
}
return true, str, ""
}
func (c *EggplantImpl) Scan(ctx context.Context, orderInfo order.OrderInfo, roadInfo road.RoadInfo, merchantInfo merchant.MerchantInfo) supplier.ScanData {
ctx, cancel := otelTrace.Span(ctx, "茄子卡", "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, forwardUrl := 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
scanData.PayUrl = forwardUrl
return scanData
}
// KMEncrypt 加密卡密
func (c *EggplantImpl) kMEncrypt(kf, appSecret string) (string, error) {
ctx := context.Background()
secret := utils.GetMd5Lower(appSecret)[:16] // 加密秘钥
block, err := aes.NewCipher([]byte(secret))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("茄子: 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 *EggplantImpl) PayNotify() {
ctx := c.Ctx.Request.Context()
var params struct {
OrderId string `json:"order_no" form:"order_no"`
Amount string `json:"amount" form:"amount"`
PayAmount string `json:"pay_amount" form:"pay_amount"`
Sign string `json:"sign" form:"sign"`
Status int `json:"status" form:"status"`
}
err := c.BindForm(&params)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("解析参数失败", zap.Error(err))
c.Ctx.WriteString("FAIL")
return
}
otelTrace.Logger.WithContext(ctx).Info("【茄子】回调数据", zap.Any("params", params))
ctx, cancel := otelTrace.Span(
ctx, "茄子卡", "PayNotify", trace.WithAttributes(
attribute.String("orderId", params.OrderId),
attribute.String("amount", params.Amount),
attribute.String("payAmount", params.PayAmount),
attribute.String("status", strconv.Itoa(params.Status)),
),
)
defer cancel()
orderInfo := order.GetOrderByBankOrderId(ctx, params.OrderId)
if orderInfo.BankOrderId == "" || len(orderInfo.BankOrderId) == 0 {
otelTrace.Logger.WithContext(ctx).Error("【茄子】回调的订单号不存在,订单号=", zap.String("attach", params.OrderId))
c.Ctx.WriteString("FAIL")
return
}
if orderInfo.Status != "wait" {
c.Ctx.WriteString("订单已经回调")
return
}
roadInfo := road.GetRoadInfoByRoadUid(ctx, orderInfo.RoadUid)
if roadInfo.RoadUid == "" || len(roadInfo.RoadUid) == 0 {
otelTrace.Logger.WithContext(ctx).Error("【茄子】支付通道已经关系或者删除,不进行回调")
c.Ctx.WriteString("FAIL")
return
}
if params.Status == 3 {
successAmount, _ := strconv.ParseFloat(params.PayAmount, 64)
isOk := false
if successAmount == orderInfo.OrderAmount {
isOk = service.SolvePaySuccess(ctx, orderInfo.BankOrderId, successAmount, orderInfo.MerchantUid, "支付成功")
} else {
isOk = service.SolvePayFail(ctx, orderInfo.BankOrderId, orderInfo.BankOrderId, "金额不一致")
}
// TODO 订单支付成功
if isOk {
c.Ctx.WriteString("success")
} else {
c.Ctx.WriteString("fail")
}
return
}
isOk := service.SolvePayFail(ctx, orderInfo.BankOrderId, orderInfo.BankOrderId, "支付失败")
if isOk {
c.Ctx.WriteString("success")
} else {
c.Ctx.WriteString("fail")
}
return
}
// 查询订单
func (c *EggplantImpl) bindSearch(ctx context.Context, orderNo string) bool {
req := httplib.NewBeegoRequestWithCtx(ctx, "http://api.qixika.com/blindSearch.html", "POST")
req.Param("orderId", orderNo)
type bindSearchResp struct {
Code string `json:"code"`
Message string `json:"message"`
Data struct {
CustomerId int `json:"customerId"`
OrderId string `json:"orderId"`
SystemOrderId string `json:"systemOrderId"`
Message string `json:"message"`
Status string `json:"status"`
} `json:"data"`
}
var resp bindSearchResp
err := req.ToJSON(&resp)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("【茄子】查询订单失败", zap.Error(err))
return false
}
otelTrace.Logger.WithContext(ctx).Info("【茄子】查询订单返回", zap.Any("resp", resp))
return resp.Data.Status == "0"
}
func (c *EggplantImpl) PayQuery(orderInfo order.OrderInfo, roadInfo road.RoadInfo) bool {
params := map[string]any{}
ctx := context.Background()
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 *EggplantImpl) PayQueryV2(orderInfo order.OrderInfo, roadInfo road.RoadInfo) supply_model.MsgModel {
params := map[string]any{}
ctx := context.Background()
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 *EggplantImpl) PayFor(info payfor.PayforInfo) string {
return ""
}
func (c *EggplantImpl) PayForQuery(payFor payfor.PayforInfo) (string, string) {
cfg := config.Config{}
url, err := cfg.GetMFCardQueryUrl()
ctx := context.Background()
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, "内部错误请稍后再试试"
}
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 *EggplantImpl) BalanceQuery(roadInfo road.RoadInfo) float64 {
return 0.00
}
func (c *EggplantImpl) PayForNotify() string {
return ""
}