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

467 lines
15 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"
"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"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/httplib"
"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 AppleCardImpl struct {
web.Controller
}
// AppleRechargeOrderStatus 充值编码
type AppleRechargeOrderStatus int
const (
AppleRechargeOrderFail AppleRechargeOrderStatus = 0 // 充值失败
AppleRechargeOrderSuccess AppleRechargeOrderStatus = 1 // 充值成功
AppleRechargeOrderProcessing AppleRechargeOrderStatus = 2 // 正在充值
AppleRechargeOrderWaiting AppleRechargeOrderStatus = 3 // 待充值
AppleRechargeOrderLimited AppleRechargeOrderStatus = 4 // 充值受限
AppleRechargeOrderExpired AppleRechargeOrderStatus = 5 // 充值过期
AppleRechargeOrderCanceled AppleRechargeOrderStatus = 6 // 充值取消
AppleRechargeOrderRefunded AppleRechargeOrderStatus = 7 // 充值退款
AppleRechargeOrderRefunding AppleRechargeOrderStatus = 8 // 充值退款中
AppleRechargeOrderRefundFailed AppleRechargeOrderStatus = 9 // 充值退款失败
AppleRechargeOrderRefundSuccess AppleRechargeOrderStatus = 10 // 充值退款成功
AppleRechargeOrderRefundWaiting AppleRechargeOrderStatus = 11 // 充值退款待处理
AppleRechargeOrderRefundProcessing AppleRechargeOrderStatus = 12 // 充值退款处理中
AppleRechargeOrderAccountOverLimited AppleRechargeOrderStatus = 13 // 账户枯竭
AppleRechargeOrderCardNoOrCardPassDuplicated AppleRechargeOrderStatus = 14 // 卡号或密码重复
AppleRechargeOrderAmountDifferent AppleRechargeOrderStatus = 15 // 充值金额与标定金额不一致
)
func (c *AppleCardImpl) 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
}
// HasDependencyHTML 是否有单独的支付页面
func (c *AppleCardImpl) HasDependencyHTML() bool {
return false
}
func (c *AppleCardImpl) SendCard(ctx context.Context, jsonStr string, cardInfo supplier.RedeemCardInfo, attach string, merchantId string) (bool, string) {
cfg := config.Config{}
params := make(map[string]string)
params["callbackUrl"], _ = cfg.GetAppleNotifyUrl() // 回调地址
params["attach"] = attach // 附带参数
params["faceValue"] = cardInfo.FaceType // 面额
params["cardNo"] = cardInfo.CardNo // 卡号
params["cardPass"] = cardInfo.Data
params["merchantId"] = merchantId
params["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
params["sign"] = utils.TmpEncrypt(params["attach"] + params["cardPass"] + params["timestamp"])
url, err := cfg.GetAppleCardSubmitUrl()
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)
req.Retries(3)
marshal, err := json.Marshal(params)
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.Header("Accept-Charset", "utf-8")
req.Header("tokenFrom", "iframe")
response, err := req.String()
otelTrace.Logger.WithContext(ctx).Info("远端请求返回数据:" + response)
if err != nil {
otelTrace.Logger.WithContext(ctx).Error("Apple GetToken 请求失败:", zap.Error(err))
return false, ""
}
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"`
Data struct {
Status AppleRechargeOrderStatus `json:"status"`
Message string `json:"message"`
} `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 != 0 {
return false, resData.Message
}
switch resData.Data.Status {
case AppleRechargeOrderFail:
return false, "充值失败"
case AppleRechargeOrderSuccess:
return true, "等待兑换"
case AppleRechargeOrderProcessing:
return true, "等待兑换"
case AppleRechargeOrderWaiting:
return true, "等待兑换"
case AppleRechargeOrderCardNoOrCardPassDuplicated:
return false, "卡号或密码已兑换或等待兑换"
}
return false, response
}
func (c *AppleCardImpl) Scan(ctx context.Context, orderInfo order.OrderInfo, roadInfo road.RoadInfo, merchantInfo merchant.MerchantInfo) supplier.ScanData {
ctx, cancel := otelTrace.Span(ctx, "AppleCardImpl", "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, orderInfo.MerchantOrderId)
var scanData supplier.ScanData
if !ok {
scanData = supplier.ScanData{
Status: "-1",
BankNo: orderInfo.MerchantOrderId,
OrderNo: orderInfo.BankOrderId,
ReturnData: str,
Msg: 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
}
// KMEncrypt 加密卡密
func (c *AppleCardImpl) kMEncrypt(kf, appSecret string) (string, error) {
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "苹果", "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 *AppleCardImpl) PayNotify() {
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "苹果", "PayNotify")
defer cancel()
attach := strings.TrimSpace(c.GetString("attach"))
orderInfo := order.GetOrderByBankOrderId(ctx, attach) // OrderId
if orderInfo.BankOrderId == "" || len(orderInfo.BankOrderId) == 0 {
otelTrace.Logger.WithContext(ctx).Error("【APPLE】回调的订单号不存在订单号=", zap.String("attach", attach))
c.Ctx.WriteString("FAIL")
return
}
params := map[string]string{
"merchantId": strings.TrimSpace(c.GetString("merchantId")),
"amount": strings.TrimSpace(c.GetString("amount")), // 时间戳
"status": strings.TrimSpace(c.GetString("status")),
"sign": strings.TrimSpace(c.GetString("sign")),
"remark": strings.TrimSpace(c.GetString("remark")),
}
orderInfo.BankTransId = params["merchantId"]
if params["status"] == "1" {
// TODO 订单支付成功
isOk := service.SolvePaySuccess(ctx, orderInfo.BankOrderId, orderInfo.FactAmount, params["merchantId"], "支付成功")
if isOk {
c.Ctx.WriteString("SUCCESS")
} else {
c.Ctx.WriteString("FAIL")
}
return
}
isOk := service.SolvePayFail(ctx, orderInfo.BankOrderId, "", params["remark"])
if isOk {
c.Ctx.WriteString("SUCCESS")
} else {
c.Ctx.WriteString("FAIL")
}
return
}
func (c *AppleCardImpl) PayQuery(orderInfo order.OrderInfo, roadInfo road.RoadInfo) bool {
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "苹果", "PayQuery")
defer cancel()
cardData, err := sonic.GetFromString(orderInfo.CardReturnData)
if err != nil {
return false
}
orderId, err := cardData.Get("order_id").String()
if err != nil {
return false
}
params := map[string]any{
"order_id": orderId,
"app_key": gojson.Json(roadInfo.Params).Get("appKey").Tostring(),
"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 *AppleCardImpl) PayQueryV2(orderInfo order.OrderInfo, roadInfo road.RoadInfo) supply_model.MsgModel {
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "苹果", "PayQueryV2")
defer cancel()
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 := map[string]any{
"order_id": orderId,
"app_key": gojson.Json(roadInfo.Params).Get("appKey").Tostring(),
"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 *AppleCardImpl) PayFor(info payfor.PayforInfo) string {
return ""
}
func (c *AppleCardImpl) PayForQuery(payFor payfor.PayforInfo) (string, string) {
cfg := config.Config{}
url, err := cfg.GetMFCardQueryUrl()
if err != nil {
return config.PAYFOR_FAIL, ""
}
ctx, cancel := otelTrace.Span(c.Ctx.Request.Context(), "苹果", "PayForQuery")
defer cancel()
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 *AppleCardImpl) BalanceQuery(roadInfo road.RoadInfo) float64 {
return 0.00
}
func (c *AppleCardImpl) PayForNotify() string {
return ""
}