完善商福通支付接口,修复证书加载问题;数据库扩展支付平台字段并更新支付信息保存逻辑;日志中间件异步记录日志
This commit is contained in:
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
var Asynq *asynq.Client
|
||||
|
||||
func InitAsynq() {
|
||||
func initAsynq() {
|
||||
var client = asynq.NewClientFromRedisClient(Redis)
|
||||
Asynq = client
|
||||
}
|
||||
|
||||
@@ -9,5 +9,6 @@ func Init() {
|
||||
initRedis()
|
||||
initOrm()
|
||||
initProxy()
|
||||
InitAsynq()
|
||||
initAsynq()
|
||||
initSft()
|
||||
}
|
||||
|
||||
@@ -6,13 +6,15 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -21,29 +23,31 @@ var SFTPay SftClient
|
||||
|
||||
type SftClient struct {
|
||||
appid string
|
||||
routeId string
|
||||
privateKey *rsa.PrivateKey
|
||||
publicKey *rsa.PublicKey
|
||||
}
|
||||
|
||||
func init() {
|
||||
func initSft() {
|
||||
if !env.SftPayEnable {
|
||||
return
|
||||
panic("商福通支付未启用,请检查环境变量 SFTPAY_ENABLE")
|
||||
}
|
||||
|
||||
SFTPay = SftClient{
|
||||
appid: env.SftPayAppId,
|
||||
appid: env.SftPayAppId,
|
||||
routeId: env.SftPayRouteId,
|
||||
}
|
||||
|
||||
// 加载私钥
|
||||
block, _ := pem.Decode([]byte(env.SftPayAppPrivateKey))
|
||||
if block == nil || block.Type != "RSA PRIVATE KEY" {
|
||||
panic("加载商福通私钥失败")
|
||||
private, err := base64.StdEncoding.DecodeString(env.SftPayAppPrivateKey)
|
||||
if err != nil {
|
||||
panic("解析商福通私钥失败: " + err.Error())
|
||||
}
|
||||
|
||||
var privateKey *rsa.PrivateKey
|
||||
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
privateKey, err = x509.ParsePKCS1PrivateKey(private)
|
||||
if err != nil {
|
||||
pkcs8, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
pkcs8, err := x509.ParsePKCS8PrivateKey(private)
|
||||
if err != nil {
|
||||
panic("解析商福通私钥失败: " + err.Error())
|
||||
}
|
||||
@@ -57,13 +61,13 @@ func init() {
|
||||
SFTPay.privateKey = privateKey
|
||||
|
||||
// 加载公钥
|
||||
block, _ = pem.Decode([]byte(env.SftPayPublicKey))
|
||||
if block == nil || block.Type != "PUBLIC KEY" {
|
||||
panic("加载商福通公钥失败")
|
||||
public, err := base64.StdEncoding.DecodeString(env.SftPayPublicKey)
|
||||
if err != nil {
|
||||
panic("解析商福通公钥失败: " + err.Error())
|
||||
}
|
||||
|
||||
var publicKey *rsa.PublicKey
|
||||
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
pkix, err := x509.ParsePKIXPublicKey(public)
|
||||
if err != nil {
|
||||
panic("解析商福通公钥失败: " + err.Error())
|
||||
}
|
||||
@@ -78,11 +82,13 @@ func init() {
|
||||
|
||||
func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) {
|
||||
const url = "https://pay.rscygroup.com/api/open/payment/scanpay"
|
||||
req.RouteNo = u.P(s.routeId)
|
||||
return call[PaymentScanPayResp](s, url, req)
|
||||
}
|
||||
|
||||
func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) {
|
||||
const url = "https://pay.rscygroup.com/api/open/payment/h5pay"
|
||||
req.RouteNo = u.P(s.routeId)
|
||||
return call[PaymentH5PayResp](s, url, req)
|
||||
}
|
||||
|
||||
@@ -91,6 +97,11 @@ func (s *SftClient) OrderClose(req *OrderCloseReq) (*OrderCloseResp, error) {
|
||||
return call[OrderCloseResp](s, url, req)
|
||||
}
|
||||
|
||||
func (s *SftClient) QueryTrade(req *QueryTradeReq) (*QueryTradeResp, error) {
|
||||
const url = "https://pay.rscygroup.com/api/open/query/trade"
|
||||
return call[QueryTradeResp](s, url, req)
|
||||
}
|
||||
|
||||
type PaymentScanPayReq struct {
|
||||
Subject string `json:"subject"`
|
||||
Body string `json:"body"`
|
||||
@@ -132,6 +143,11 @@ type PaymentH5PayReq struct {
|
||||
LimitPay *int `json:"limitPay"`
|
||||
}
|
||||
|
||||
type QueryTradeReq struct {
|
||||
PayOrderId *string `json:"payOrderId"`
|
||||
MchOrderNo *string `json:"mchOrderNo"`
|
||||
}
|
||||
|
||||
type OrderCloseReq struct {
|
||||
MchOrderNo *string `json:"mchOrderNo"`
|
||||
PayOrderId *string `json:"payOrderId"`
|
||||
@@ -197,6 +213,29 @@ type PaymentH5PayResp struct {
|
||||
SettlementType *string `json:"settlementType"`
|
||||
}
|
||||
|
||||
type QueryTradeResp struct {
|
||||
Amount int64 `json:"amount"`
|
||||
ChannelSendNo *string `json:"channelSendNo"`
|
||||
IfCode *string `json:"ifCode"`
|
||||
MercNo string `json:"mercNo"`
|
||||
MchOrderNo string `json:"mchOrderNo"`
|
||||
PayOrderId *string `json:"payOrderId"`
|
||||
PayType string `json:"payType"`
|
||||
ChannelTradeNo *string `json:"channelTradeNo"`
|
||||
State SftTradeState `json:"state"`
|
||||
RefundAmt *int64 `json:"refundAmt"`
|
||||
RefundState int32 `json:"refundState"`
|
||||
DrType *string `json:"drType"`
|
||||
ExtParam *string `json:"extParam"`
|
||||
PayTime *string `json:"payTime"`
|
||||
Subject string `json:"subject"`
|
||||
TradeFee *int64 `json:"tradeFee"`
|
||||
CashFee *int64 `json:"cashFee"`
|
||||
StoreId *string `json:"storeId"`
|
||||
UserId *string `json:"userId"`
|
||||
SettlementType *string `json:"settlementType"`
|
||||
}
|
||||
|
||||
type OrderCloseResp struct {
|
||||
MchOrderNo string `json:"mchOrderNo"`
|
||||
PayOrderId string `json:"payOrderId"`
|
||||
@@ -224,19 +263,30 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建请求失败:%w", err)
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
|
||||
reqDump, err := httputil.DumpRequest(request, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求内容转储失败:%w", err)
|
||||
}
|
||||
println(string(reqDump) + "\n\n")
|
||||
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求失败:%w", err)
|
||||
}
|
||||
|
||||
respDump, err := httputil.DumpResponse(response, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("响应内容转储失败:%w", err)
|
||||
}
|
||||
println(string(respDump) + "\n\n")
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("请求响应失败:%d", response.StatusCode)
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
defer func(body io.ReadCloser) {
|
||||
_ = body.Close()
|
||||
}(response.Body)
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
@@ -248,15 +298,9 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解密响应内容失败:%w", err)
|
||||
}
|
||||
if decode.Code != "000000" {
|
||||
return nil, fmt.Errorf("请求业务响应失败:%s", u.Z(decode.Msg))
|
||||
}
|
||||
if decode.BizData == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var resp = new(T)
|
||||
err = json.Unmarshal([]byte(*decode.BizData), resp)
|
||||
err = json.Unmarshal([]byte(decode), resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("响应正文解析失败:%w", err)
|
||||
}
|
||||
@@ -290,24 +334,34 @@ func (s *SftClient) sign(msg any) (*request, error) {
|
||||
return &body, nil
|
||||
}
|
||||
|
||||
func (s *SftClient) verify(str []byte) (*response, error) {
|
||||
func (s *SftClient) verify(str []byte) (string, error) {
|
||||
|
||||
var resp = new(response)
|
||||
err := json.Unmarshal(str, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析响应正文失败:%w", err)
|
||||
return "", fmt.Errorf("解析响应正文失败:%w", err)
|
||||
}
|
||||
|
||||
if resp.Sign != nil || resp.SignType != nil || resp.BizData != nil {
|
||||
|
||||
hashed := sha256.Sum256([]byte(resp.String()))
|
||||
err := rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], []byte(*resp.Sign))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("验签失败:%w", err)
|
||||
}
|
||||
if resp.Code != "000000" {
|
||||
return "", fmt.Errorf("请求业务响应失败:%s", u.Z(resp.Msg))
|
||||
}
|
||||
|
||||
return resp, err
|
||||
if resp.Sign == nil {
|
||||
return "", core.NewServErr("响应数据签名为空")
|
||||
}
|
||||
|
||||
ser, err := resp.String()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("格式化响应内容失败:%w", err)
|
||||
}
|
||||
|
||||
hashed := sha256.Sum256([]byte(ser))
|
||||
err = rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], []byte(*resp.Sign))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("验签失败:%w", err)
|
||||
}
|
||||
|
||||
return *resp.BizData, nil
|
||||
}
|
||||
|
||||
type request struct {
|
||||
@@ -336,11 +390,17 @@ type response struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
func (r response) String() string {
|
||||
func (r response) String() (string, error) {
|
||||
if r.BizData == nil || r.Msg == nil || r.SignType == nil {
|
||||
return "", core.NewServErr(fmt.Sprintf(
|
||||
"上游数据返回有空值:BizData %v,Msg %v, SignType %v",
|
||||
r.BizData == nil, r.Msg == nil, r.SignType == nil,
|
||||
))
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"bizData=%s&code=%s&msg=%s&signType=%s×tamp=%s",
|
||||
u.Z(r.BizData), r.Code, u.Z(r.Msg), u.Z(r.SignType), r.Timestamp,
|
||||
)
|
||||
), nil
|
||||
}
|
||||
|
||||
type SftPayType string
|
||||
@@ -350,3 +410,16 @@ const (
|
||||
SftWeChat SftPayType = "WECHAT"
|
||||
SftUnionPay SftPayType = "UNIONPAY"
|
||||
)
|
||||
|
||||
type SftTradeState string
|
||||
|
||||
const (
|
||||
SftInit SftTradeState = "INIT"
|
||||
SftTradeAWAIT SftTradeState = "TRADE_WAIT"
|
||||
SftTradeSuccess SftTradeState = "TRADE_SUCCESS"
|
||||
SftTradeFail SftTradeState = "TRADE_FAIL"
|
||||
SftTradeCancel SftTradeState = "TRADE_CANCEL"
|
||||
SftTradeRefund SftTradeState = "TRADE_REFUND"
|
||||
SftRefundIng SftTradeState = "REFUND_ING"
|
||||
SftTradeClosed SftTradeState = "TRADE_CLOSED"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user