feat(supplier): 新增星寂支付渠道

- 添加星寂支付相关的配置和接口实现
- 新增星寂支付的路由和回调处理
- 实现星寂支付的查询和通知功能
- 添加星寂支付的单元测试
This commit is contained in:
danial
2025-02-06 00:10:17 +08:00
parent a025be3cdc
commit 003927868d
7 changed files with 461 additions and 2 deletions

4
go.mod
View File

@@ -8,7 +8,7 @@ require github.com/beego/beego/v2 v2.3.4
require (
github.com/bytedance/gopkg v0.1.2-0.20240828084325-780ca9ee70fb
github.com/bytedance/sonic v1.12.7
github.com/bytedance/sonic v1.12.8
github.com/carlmjohnson/requests v0.24.3
github.com/duke-git/lancet/v2 v2.3.4
github.com/go-sql-driver/mysql v1.8.1
@@ -48,7 +48,7 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/arch v0.13.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect

4
go.sum
View File

@@ -8,6 +8,8 @@ github.com/bytedance/gopkg v0.1.2-0.20240828084325-780ca9ee70fb h1:glte+Ka6C5efX
github.com/bytedance/gopkg v0.1.2-0.20240828084325-780ca9ee70fb/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q=
github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I=
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
@@ -115,6 +117,8 @@ golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=

View File

@@ -40,6 +40,10 @@ func (c *Config) GetWalMartSubmitUrl() (string, error) {
return web.AppConfig.String("walMart::submit_card_url")
}
func (c *Config) GetDomain() string {
return web.AppConfig.DefaultString("domain", "http://localhost:12309")
}
func (c *Config) GetWalMartNotifyUrl() string {
notifyUrl, err := web.AppConfig.String("walMart::notify_url")
if err != nil {

View File

@@ -17,6 +17,7 @@ var supplierCode2Name = map[string]string{
"APPLESHARK": "苹果itunes充值(苹果鲨鱼)",
"FAT_SIX": "胖小六",
"SELF_THIRD": "自有上游",
"STAR_SILENCE": "星寂",
}
var registerSupplier = make(map[string]supplier.PayInterface)
@@ -43,6 +44,8 @@ func init() {
logs.Notice(CheckSupplierByCode("FAT_SIX"))
registerSupplier["SELF_THIRD"] = new(SelfThirdImpl)
logs.Notice(CheckSupplierByCode("SELF_THIRD"))
registerSupplier["STAR_SILENCE"] = new(StarSilenceImpl)
logs.Notice(CheckSupplierByCode("STAR_SILENCE"))
}
func GetPaySupplierByCode(code string) supplier.PayInterface {

View File

@@ -0,0 +1,384 @@
package third_party
import (
"encoding/base64"
"encoding/json"
"fmt"
"gateway/internal/config"
"gateway/internal/entities/supplier"
"gateway/internal/models/merchant"
"gateway/internal/models/order"
"gateway/internal/models/payfor"
"gateway/internal/models/road"
"gateway/internal/models/supply_model"
"gateway/internal/service"
"gateway/internal/utils"
"github.com/beego/beego/v2/client/httplib"
"github.com/beego/beego/v2/core/logs"
"github.com/duke-git/lancet/v2/convertor"
"github.com/duke-git/lancet/v2/cryptor"
"github.com/duke-git/lancet/v2/netutil"
"io"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/beego/beego/v2/server/web"
"github.com/bytedance/sonic"
"github.com/widuu/gojson"
)
type StarSilenceImpl struct {
web.Controller
mu sync.Mutex
}
// HasDependencyHTML 是否有单独的支付页面
func (c *StarSilenceImpl) HasDependencyHTML() bool {
return false
}
func (c *StarSilenceImpl) SendCard(jsonStr string, cardInfo supplier.RedeemCardInfo, orderInfo *order.OrderInfo) (bool, string) {
params := map[string]string{
"supplierId": gojson.Json(jsonStr).Get("supplierId").Tostring(),
"supplierOrderId": orderInfo.BankOrderId,
"productTypeId": gojson.Json(jsonStr).Get("productTypeId").Tostring(),
"asynNoticeUrl": fmt.Sprintf("%s/starSilence/notify", config.GetConfig().GetDomain()),
"cardInfo": base64.StdEncoding.EncodeToString(cryptor.DesEcbEncrypt(
[]byte(fmt.Sprintf("%d#%s#%s", int(orderInfo.OrderAmount), cardInfo.CardNo, cardInfo.Data)),
[]byte(gojson.Json(jsonStr).Get("cardKey").Tostring()),
)),
}
params["sign"] = strings.ToUpper(cryptor.Md5String(fmt.Sprintf("%s%s%s%s%s%s", params["supplierId"], params["supplierOrderId"],
params["productTypeId"], params["cardInfo"], params["asynNoticeUrl"],
gojson.Json(jsonStr).Get("signKey").Tostring())),
)
formData := url.Values{}
for s, s2 := range params {
formData.Set(s, s2)
}
logs.Info("formData", formData)
resp, _ := netutil.NewHttpClient().SendRequest(&netutil.HttpRequest{
RawURL: "https://www.xingjiyou.cn/cardapi/submitOrder",
Method: "POST",
QueryParams: formData,
})
result, _ := io.ReadAll(resp.Body)
logs.Info("远端请求返回数据:%s", string(result))
return gojson.Json(string(result)).Get("code").Tostring() == "0", gojson.Json(string(result)).Get("msg").Tostring()
}
func (c *StarSilenceImpl) Scan(orderInfo order.OrderInfo, roadInfo road.RoadInfo, merchantInfo merchant.MerchantInfo) supplier.ScanData {
cdata := supplier.RedeemCardInfo{}
err := json.Unmarshal([]byte(orderInfo.ExValue), &cdata)
if err != nil {
logs.Error("格式化数据失败", orderInfo.ExValue)
return supplier.ScanData{Status: "-1", Msg: "订单有有误,请稍后再试"}
}
ok, str := c.SendCard(roadInfo.Params, cdata, &orderInfo)
if !ok {
return supplier.ScanData{Status: "-1", Msg: "订单有有误,请稍后再试:" + str}
}
if str == "" {
return supplier.ScanData{Status: "-1", Msg: "订单有有误,请稍后再试:" + str}
}
var scanData supplier.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
}
func (c *StarSilenceImpl) PayNotify() {
logs.Info("消息回调成功!!!")
params := map[string]string{
"supplierOrderId": strings.TrimSpace(c.GetString("supplierOrderId")),
"deservedValue": strings.TrimSpace(c.GetString("supplierOrderStatus")),
"realParvalue": strings.TrimSpace(c.GetString("realParvalue")),
"code": strings.TrimSpace(c.GetString("code")),
"msg": strings.TrimSpace(c.GetString("msg")),
"sign": strings.TrimSpace(c.GetString("sign")),
}
//定义一个结构体
type OrderInfo struct {
SupplierOrderId string `json:"supplierOrderId"`
DeservedValue float64 `json:"deservedValue"`
RealParvalue float64 `json:"realParvalue"`
Code string `json:"code"`
Msg string `json:"msg"`
Sign string `json:"sign"`
}
retOrderInfo := OrderInfo{}
err := c.Bind(&retOrderInfo)
if err != nil {
c.Ctx.WriteString("解析返回参数失败")
return
}
tmpSign := strings.ToUpper(cryptor.Md5String(fmt.Sprintf("%s%s%s%s%s", params["supplierOrderId"], params["realParvalue"],
params["deservedValue"], params["code"], params["msg"]),
))
if tmpSign != params["sign"] {
c.Ctx.WriteString("签名验证失败")
c.StopRun()
}
orderInfo := order.GetOrderByBankOrderId(params["supplierOrderId"]) // OrderId
if orderInfo.BankOrderId == "" || len(orderInfo.BankOrderId) == 0 {
logs.Error("【星寂】回调的订单号不存在,订单号=", params["supplierOrderId"])
c.Ctx.WriteString("订单不存在")
return
}
roadInfo := road.GetRoadInfoByRoadUid(orderInfo.RoadUid)
if roadInfo.RoadUid == "" || len(roadInfo.RoadUid) == 0 {
c.Ctx.WriteString("通道不存在")
c.StopRun()
return
}
merchantUid := orderInfo.MerchantUid
merchantInfo := merchant.GetMerchantByUid(merchantUid)
if merchantInfo.MerchantUid == "" || len(merchantInfo.MerchantUid) == 0 {
logs.Error("【星寂】快付回调失败该商户不存在或者已经删除商户uid=", merchantUid)
c.Ctx.WriteString("商户不存在")
c.StopRun()
return
}
// 实际支付金额
factAmount, err := strconv.ParseFloat(convertor.ToString(params["deservedValue"]), 64)
if err != nil {
logs.Error("【星寂】处理失败,%s", err)
c.Ctx.WriteString("订单转换失败")
return
}
if params["code"] == "1" {
service.SolvePaySuccess(convertor.ToString(params["supplierOrderId"]), factAmount, "")
} else {
if !service.SolvePayFail(convertor.ToString(params["supplierOrderId"]), "") {
logs.Error("【星寂】处理失败")
}
}
c.Ctx.WriteString("SUCCESS")
}
func (c *StarSilenceImpl) PayQuery(orderInfo order.OrderInfo, roadInfo road.RoadInfo) bool {
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 {
logs.Error("Map转化为byte数组失败,异常。", err)
// fmt.Printf("Map转化为byte数组失败,异常:%s\n", err)
return false
}
logs.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 {
logs.Error("MF GetToken 请求失败:", err)
return false
}
logs.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 *StarSilenceImpl) PayQueryV2(orderInfo order.OrderInfo, roadInfo road.RoadInfo) supply_model.MsgModel {
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 {
logs.Error("Map转化为byte数组失败,异常。", err)
// fmt.Printf("Map转化为byte数组失败,异常:%s\n", err)
return supply_model.DataErr
}
logs.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
}
logs.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 *StarSilenceImpl) PayFor(info payfor.PayforInfo) string {
return ""
}
func (c *StarSilenceImpl) PayForQuery(payFor payfor.PayforInfo) (string, string) {
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 {
logs.Error("Map转化为byte数组失败,异常。", err)
// fmt.Printf("Map转化为byte数组失败,异常:%s\n", err)
return config.PAYFOR_FAIL, "内部错误请稍后再试试01"
}
logs.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 {
logs.Error("MF GetToken 请求失败:", err)
return config.PAYFOR_FAIL, ""
}
logs.Info("远端请求返回数据:" + response)
if gojson.Json(response).Get("code").Tostring() == "" {
logs.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, ""
}
}
logs.Error("远程调用失败")
return config.PAYFOR_BANKING, ""
}
func (c *StarSilenceImpl) BalanceQuery(roadInfo road.RoadInfo) float64 {
return 0.00
}
func (c *StarSilenceImpl) PayForNotify() string {
return ""
}

View File

@@ -0,0 +1,63 @@
package third_party
import (
"gateway/internal/entities/supplier"
"gateway/internal/models/order"
"github.com/duke-git/lancet/v2/random"
"testing"
"time"
)
func TestStarSilenceImpl_SendCard(t *testing.T) {
s := StarSilenceImpl{}
id, _ := random.UUIdV4()
s.SendCard("{\"supplierId\":\"1035\",\"productTypeId\":\"4025\",\"signKey\":\"e90db0fea5be42f98a376b0049c9aa8c\",\"cardKey\":\"98750963\"}", supplier.RedeemCardInfo{
CardNo: "1234567890",
Data: "1234567890",
FaceType: "1234567890",
RecoveryType: "1234567890",
}, &order.OrderInfo{
Id: 0,
ShopName: "",
OrderPeriod: "",
MerchantOrderId: "",
BankOrderId: id,
BankTransId: "",
OrderAmount: 10,
ShowAmount: 0,
FactAmount: 0,
RollPoolCode: "",
RollPoolName: "",
RoadUid: "",
RoadName: "",
PayProductName: "",
PayProductCode: "",
PayTypeCode: "",
PayTypeName: "",
OsType: "",
Status: "",
Refund: "",
RefundTime: "",
Freeze: "",
FreezeTime: "",
Unfreeze: "",
UnfreezeTime: "",
NotifyUrl: "",
MerchantUid: "",
MerchantName: "",
AgentUid: "",
AgentName: "",
ExValue: "",
CardData: "",
UpdateTime: time.Time{},
CreateTime: time.Time{},
PayTime: time.Time{},
Operator: "",
CardReturnData: "",
Ip: "",
TransactionType: "",
IsIpRestricted: 0,
PayUrl: "",
IsReplace: 0,
})
}

View File

@@ -34,4 +34,5 @@ func init() {
web.Router("/walMartSelf/notify", &third_party.WalmartSelfImpl{}, "*:PayNotify")
web.Router("/sixFat/notify", &third_party.FatSixImpl{}, "*:PayNotify")
web.Router("/selfThird/notify", &third_party.SelfThirdImpl{}, "*:PayNotify")
web.Router("/starSilence/notify", &third_party.StarSilenceImpl{}, "*:PayNotify")
}