fix(deploy/Dockerfile, internal/controllers/gateway/scan_controller.go, internal/service/supplier/third_party/eggplant.go): 优化代码结构和环境配置

- 在 Dockerfile 中添加了换行以提高可读性
- 在 scan_controller.go 中添加了空行以增强代码可读性
- 新增了 EggplantImpl 结构体及其相关方法,支持七喜卡片的支付和查询逻辑,优化了支付通知和订单查询功能
This commit is contained in:
danial
2025-04-23 11:55:15 +08:00
parent 853ab0f0a4
commit 18a209b2f3
3 changed files with 508 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ RUN go mod tidy && go build main.go
FROM alpine:latest
WORKDIR /app
ENV TZ=Asia/Shanghai
ENV serverName="默认"
ENV gatewayAddr=""

View File

@@ -67,6 +67,7 @@ func (c *ScanController) Scan() {
"ip": strings.TrimSpace(c.GetString("ip")),
"deviceId": strings.TrimSpace(c.GetString("deviceId")),
})
ctx, cancel := otelTrace.Span(ctx, "页面订单", "ScanController", trace.WithAttributes(
attribute.String("exValue", p.Params["exValue"].(string)),
attribute.String("orderNo", p.Params["orderNo"].(string)),
@@ -78,10 +79,12 @@ func (c *ScanController) Scan() {
c.SolveFailJSON(p)
return
}
c.Ctx.Request = c.Ctx.Request.WithContext(ctx)
p.ClientIP = strings.TrimSpace(c.GetString("ip"))
merchantOrderLock.Lock()
cacheId, _ := idCache.Get(strings.TrimSpace(c.GetString("orderNo")))
_ = idCache.Set(strings.TrimSpace(c.GetString("orderNo")), []byte("1"))
merchantOrderLock.Unlock()
if len(cacheId) != 0 {
@@ -96,6 +99,7 @@ func (c *ScanController) Scan() {
defer func() {
_ = idCache.Delete(strings.TrimSpace(c.GetString("orderNo")))
}()
p = service.JudgeParams(ctx, p)
p = service.OrderIsValid(ctx, p)
p = service.NotifyUrlIsValid(ctx, p)
@@ -106,12 +110,14 @@ func (c *ScanController) Scan() {
c.SolveFailJSON(p)
return
}
otelTrace.Logger.WithContext(ctx).Info("【BaseGateway】获取商户请求过来的参数", zap.Any("params", p.Params))
p = service.ChooseRoadV2(ctx, p)
if p.Code == -1 {
c.SolveFailJSON(p)
return
}
merchantInfo, err := service.GetMerchantInfoByPayKey(ctx, strings.TrimSpace(c.GetString("payKey")))
if err != nil {
c.Data["json"] = response.CommonErr(-1, err.Error())
@@ -123,12 +129,14 @@ func (c *ScanController) Scan() {
c.SolveFailJSON(p)
return
}
mt := merchant_deploy.GetMerchantDeployByUidAndRoadUid(ctx, p.MerchantInfo.MerchantUid, p.RoadInfo.RoadUid)
if mt.Id == 0 {
p.Msg = "当前用户没有开通该通道"
c.SolveFailJSON(p)
return
}
orderPrice, err := strconv.ParseFloat(convertor.ToString(p.Params["orderPrice"]), 64)
if err != nil {
p.Code = -1
@@ -136,6 +144,7 @@ func (c *ScanController) Scan() {
c.SolveFailJSON(p)
return
}
pm, err := mt.GetShowMMValue(ctx, orderPrice)
if err != nil {
p.Code = -1
@@ -143,6 +152,7 @@ func (c *ScanController) Scan() {
c.SolveFailJSON(p)
return
}
p.Params["exValue"], err = service.CompleteRedeemExValue(convertor.ToString(p.Params["exValue"]), strconv.FormatFloat(pm.ShowLabel, 'f', 0, 64))
if err != nil {
p.Code = -1
@@ -150,6 +160,7 @@ func (c *ScanController) Scan() {
c.SolveFailJSON(p)
return
}
// TODO: 生成订单记录
orderInfo, _, err := service.GenerateRecord(ctx, p)
if err != nil {
@@ -185,6 +196,7 @@ func (c *ScanController) Scan() {
_ = c.ServeJSON()
return
}
otelTrace.Logger.WithContext(ctx).Info("【BaseGateway】生成订单记录", zap.Any("orderInfo", orderInfo))
if mt.AutoSettle == config.NO {
params := map[string]any{
@@ -205,6 +217,7 @@ func (c *ScanController) Scan() {
_ = c.ServeJSON()
return
}
// 获取到对应的上游
supplierCode := p.RoadInfo.ProductUid
supplierByCode := third_party.GetPaySupplierByCode(supplierCode)

View File

@@ -0,0 +1,494 @@
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"
"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/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 true
}
// 签名算法
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("customerId").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,
fmt.Sprintf("%s/tocard.html", gojson.Json(jsonStr).Get("url").Tostring()), "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))
}
req.Body(&params)
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), zap.Any("解析结果", resData))
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("json解析失败", zap.Error(err), zap.String("response", response))
return false, "内部数据处理失败", ""
}
if resData.Code == 0 {
return true, response, resData.Data.ForwardUrl
}
return false, response, ""
}
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 := context.Background()
params := map[string]interface{}{
"customerId": c.GetString("customerId"),
"orderId": c.GetString("orderId"),
"systemOrderId": c.GetString("systemOrderId"),
"status": c.GetString("status"),
"cardNumber": c.GetString("cardNumber"),
"cardPassword": c.GetString("cardPassword"),
"amount": c.GetString("amount"),
"successAmount": c.GetString("successAmount"),
"actualAmount": c.GetString("actualAmount"),
"successTime": c.GetString("successTime"),
"extendParams": c.GetString("extendParams"),
"message": c.GetString("message"),
"realPrice": c.GetString("realPrice"),
//"sign": c.GetString("sign"),
}
ctx, cancel := otelTrace.Span(
ctx, "七喜卡", "PayNotify", trace.WithAttributes(
attribute.String("orderId", convertor.ToString(params["orderId"])),
attribute.String("amount", convertor.ToString(params["amount"])),
attribute.String("successAmount", convertor.ToString(params["successAmount"])),
attribute.String("status", convertor.ToString(params["status"])),
),
)
defer cancel()
//通form表单里提取数据
otelTrace.Logger.WithContext(ctx).Info("【七喜】回调数据", zap.Any("params", params))
orderInfo := order.GetOrderByBankOrderId(ctx, convertor.ToString(params["orderId"]))
if orderInfo.BankOrderId == "" || len(orderInfo.BankOrderId) == 0 {
otelTrace.Logger.WithContext(ctx).Error("【七喜】回调的订单号不存在,订单号=", zap.String("attach", convertor.ToString(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
}
params["customerId"] = gojson.Json(roadInfo.Params).Get("customerId").Tostring()
merchantInfo := merchant.GetMerchantByUid(ctx, orderInfo.MerchantUid)
if merchantInfo.MerchantUid == "" || len(merchantInfo.MerchantUid) == 0 {
otelTrace.Logger.WithContext(ctx).Error("【七喜】快付回调失败该商户不存在或者已经删除商户uid=", zap.String("merchantUid", orderInfo.MerchantUid))
c.Ctx.WriteString("FAIL")
return
}
//TODO: 需要二次查单
//处理成功
resp, _ := json.Marshal(params)
otelTrace.Logger.WithContext(ctx).Info("【七喜】回调数据", zap.Any("params", params))
if params["status"] == "2" {
successAmount, _ := strconv.ParseFloat(convertor.ToString(params["successAmount"]), 64)
isOk := false
if successAmount == orderInfo.OrderAmount {
isOk = service.SolvePaySuccess(ctx, orderInfo.BankOrderId, successAmount, orderInfo.MerchantUid, string(resp))
} else {
isOk = service.SolvePayFail(ctx, orderInfo.BankOrderId, convertor.ToString(params["systemOrderId"]), fmt.Sprintf("金额异议 卡面金额:%.2f 实际金额:%.2f%s", orderInfo.OrderAmount, successAmount, string(resp)))
}
// TODO 订单支付成功
if isOk {
c.Ctx.WriteString("SUCCESS")
} else {
c.Ctx.WriteString("FAIL")
}
} else {
isOk := service.SolvePayFail(ctx, orderInfo.BankOrderId, convertor.ToString(params["systemOrderId"]), string(resp))
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 ""
}