修复商福通客户端加解密逻辑,交易表新增收单机构字段用来保存实际支付方式,取消交易接口实现
This commit is contained in:
19
pkg/env/env.go
vendored
19
pkg/env/env.go
vendored
@@ -350,8 +350,8 @@ func loadAliyun() {
|
|||||||
// region 商福通
|
// region 商福通
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
SftPayEnable = false
|
||||||
SftPayAppId string
|
SftPayAppId string
|
||||||
SftPayAppSecret string
|
|
||||||
SftPayAppPrivateKey string
|
SftPayAppPrivateKey string
|
||||||
SftPayPublicKey string
|
SftPayPublicKey string
|
||||||
)
|
)
|
||||||
@@ -359,6 +359,15 @@ var (
|
|||||||
func loadSftPay() {
|
func loadSftPay() {
|
||||||
var value string
|
var value string
|
||||||
|
|
||||||
|
value = os.Getenv("SFTPAY_ENABLE")
|
||||||
|
if value != "" {
|
||||||
|
enabled, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
panic("环境变量 SFTPAY_ENABLE 的值不是布尔值")
|
||||||
|
}
|
||||||
|
SftPayEnable = enabled
|
||||||
|
}
|
||||||
|
|
||||||
value = os.Getenv("SFTPAY_APP_ID")
|
value = os.Getenv("SFTPAY_APP_ID")
|
||||||
if value == "" {
|
if value == "" {
|
||||||
panic("环境变量 ALIYUN_SMS_TEMPLATE_LOGIN 的值不能为空")
|
panic("环境变量 ALIYUN_SMS_TEMPLATE_LOGIN 的值不能为空")
|
||||||
@@ -379,13 +388,6 @@ func loadSftPay() {
|
|||||||
} else {
|
} else {
|
||||||
SftPayPublicKey = value
|
SftPayPublicKey = value
|
||||||
}
|
}
|
||||||
|
|
||||||
value = os.Getenv("SFTPAY_APP_SECRET")
|
|
||||||
if value == "" {
|
|
||||||
panic("环境变量 SFTPAY_APP_SECRET 的值不能为空")
|
|
||||||
} else {
|
|
||||||
SftPayAppSecret = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@@ -440,4 +442,5 @@ func Init() {
|
|||||||
loadAlipay()
|
loadAlipay()
|
||||||
loadWechatPay()
|
loadWechatPay()
|
||||||
loadAliyun()
|
loadAliyun()
|
||||||
|
// loadSftPay()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -805,6 +805,7 @@ create table trade (
|
|||||||
amount decimal(12, 2) not null default 0,
|
amount decimal(12, 2) not null default 0,
|
||||||
payment decimal(12, 2) not null default 0,
|
payment decimal(12, 2) not null default 0,
|
||||||
method int not null,
|
method int not null,
|
||||||
|
acquirer int not null,
|
||||||
status int not null default 0,
|
status int not null default 0,
|
||||||
pay_url text,
|
pay_url text,
|
||||||
paid_at timestamp,
|
paid_at timestamp,
|
||||||
@@ -830,7 +831,8 @@ comment on column trade.subject is '订单主题';
|
|||||||
comment on column trade.remark is '订单备注';
|
comment on column trade.remark is '订单备注';
|
||||||
comment on column trade.amount is '订单总金额';
|
comment on column trade.amount is '订单总金额';
|
||||||
comment on column trade.payment is '支付金额';
|
comment on column trade.payment is '支付金额';
|
||||||
comment on column trade.method is '支付方式:1-支付宝,2-微信';
|
comment on column trade.method is '支付方式:1-支付宝,2-微信,3-商福通渠道支付宝,4-商福通渠道微信';
|
||||||
|
comment on column trade.acquirer is '收单机构:1-支付宝,2-微信,3-银联';
|
||||||
comment on column trade.status is '订单状态:0-待支付,1-已支付,2-已取消,3-已退款';
|
comment on column trade.status is '订单状态:0-待支付,1-已支付,2-已取消,3-已退款';
|
||||||
comment on column trade.pay_url is '支付链接';
|
comment on column trade.pay_url is '支付链接';
|
||||||
comment on column trade.paid_at is '支付时间';
|
comment on column trade.paid_at is '支付时间';
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ type Method int32
|
|||||||
const (
|
const (
|
||||||
MethodAlipay Method = iota + 1 // 支付宝
|
MethodAlipay Method = iota + 1 // 支付宝
|
||||||
MethodWeChat // 微信
|
MethodWeChat // 微信
|
||||||
|
MethodSft // 商福通
|
||||||
|
MethodSftAlipay // 商福通渠道指定支付宝
|
||||||
|
MethodSftWeChat // 商福通渠道指定微信
|
||||||
|
)
|
||||||
|
|
||||||
|
type Acquirer int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
AcquirerAlipay Acquirer = iota + 1 // 支付宝
|
||||||
|
AcquirerWeChat // 微信
|
||||||
|
AcquirerUnionPay // 银联
|
||||||
)
|
)
|
||||||
|
|
||||||
type Status int32
|
type Status int32
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"crypto"
|
"crypto"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
@@ -20,15 +21,17 @@ var SFTPay SftClient
|
|||||||
|
|
||||||
type SftClient struct {
|
type SftClient struct {
|
||||||
appid string
|
appid string
|
||||||
appSecret string
|
|
||||||
privateKey *rsa.PrivateKey
|
privateKey *rsa.PrivateKey
|
||||||
publicKey *rsa.PublicKey
|
publicKey *rsa.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
if !env.SftPayEnable {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
SFTPay = SftClient{
|
SFTPay = SftClient{
|
||||||
appid: env.SftPayAppId,
|
appid: env.SftPayAppId,
|
||||||
appSecret: env.SftPayAppSecret,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载私钥
|
// 加载私钥
|
||||||
@@ -73,6 +76,21 @@ func init() {
|
|||||||
SFTPay.publicKey = publicKey
|
SFTPay.publicKey = publicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SftClient) PaymentScanPay(req *PaymentScanPayReq) (*PaymentScanPayResp, error) {
|
||||||
|
const url = "https://pay.rscygroup.com/api/open/payment/scanpay"
|
||||||
|
return call[PaymentScanPayResp](s, url, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SftClient) PaymentH5Pay(req *PaymentH5PayReq) (*PaymentH5PayResp, error) {
|
||||||
|
const url = "https://pay.rscygroup.com/api/open/payment/h5pay"
|
||||||
|
return call[PaymentH5PayResp](s, url, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SftClient) OrderClose(req *OrderCloseReq) (*OrderCloseResp, error) {
|
||||||
|
const url = "https://pay.rscygroup.com/api/open/order/close"
|
||||||
|
return call[OrderCloseResp](s, url, req)
|
||||||
|
}
|
||||||
|
|
||||||
type PaymentScanPayReq struct {
|
type PaymentScanPayReq struct {
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
@@ -93,40 +111,12 @@ type PaymentScanPayReq struct {
|
|||||||
LimitPay *int `json:"limitPay"`
|
LimitPay *int `json:"limitPay"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaymentScanPayResp struct {
|
|
||||||
Amount int64 `json:"amount"`
|
|
||||||
MchOrderNo string `json:"mchOrderNo"`
|
|
||||||
PayOrderId string `json:"payOrderId"`
|
|
||||||
MercNo string `json:"mercNo"`
|
|
||||||
ChannelSendNo *string `json:"channelSendNo"`
|
|
||||||
ChannelTradeNo *string `json:"channelTradeNo"`
|
|
||||||
State string `json:"state"`
|
|
||||||
PayType string `json:"payType"`
|
|
||||||
IfCode string `json:"ifCode"`
|
|
||||||
ExtParam *string `json:"extParam"`
|
|
||||||
PayInfo *string `json:"payInfo"`
|
|
||||||
Note *string `json:"note"`
|
|
||||||
TradeFee *int64 `json:"tradeFee"`
|
|
||||||
StoreId *string `json:"storeId"`
|
|
||||||
Subject *string `json:"subject"`
|
|
||||||
DrType *string `json:"drType"`
|
|
||||||
RefundAmt *int64 `json:"refundAmt"`
|
|
||||||
RefundState *int `json:"refundState"`
|
|
||||||
CashFee *int64 `json:"cashFee"`
|
|
||||||
SettlementType *string `json:"settlementType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SftClient) PaymentScanPay(req *PaymentScanPayResp) (*PaymentScanPayResp, error) {
|
|
||||||
const url = "https://pay.rscygroup.com/api/open/payment/scanpay"
|
|
||||||
return call[PaymentScanPayResp](s, url, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaymentH5PayReq struct {
|
type PaymentH5PayReq struct {
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
Currency string `json:"currency"`
|
Currency string `json:"currency"`
|
||||||
PayType string `json:"payType"`
|
PayType SftPayType `json:"payType"`
|
||||||
ClientIp string `json:"clientIp"`
|
ClientIp string `json:"clientIp"`
|
||||||
MchOrderNo string `json:"mchOrderNo"`
|
MchOrderNo string `json:"mchOrderNo"`
|
||||||
StoreId *string `json:"storeId"`
|
StoreId *string `json:"storeId"`
|
||||||
@@ -142,7 +132,22 @@ type PaymentH5PayReq struct {
|
|||||||
LimitPay *int `json:"limitPay"`
|
LimitPay *int `json:"limitPay"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaymentH5PayResp struct {
|
type OrderCloseReq struct {
|
||||||
|
MchOrderNo *string `json:"mchOrderNo"`
|
||||||
|
PayOrderId *string `json:"payOrderId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// type OrderRefundReq struct {
|
||||||
|
// mchRefundNo
|
||||||
|
// payOrderId
|
||||||
|
// mchOrderNo
|
||||||
|
// refundReason
|
||||||
|
// refundAmount
|
||||||
|
// notifyUrl
|
||||||
|
// extParam
|
||||||
|
// }
|
||||||
|
|
||||||
|
type PaymentScanPayResp struct {
|
||||||
Amount int64 `json:"amount"`
|
Amount int64 `json:"amount"`
|
||||||
MchOrderNo string `json:"mchOrderNo"`
|
MchOrderNo string `json:"mchOrderNo"`
|
||||||
PayOrderId string `json:"payOrderId"`
|
PayOrderId string `json:"payOrderId"`
|
||||||
@@ -150,10 +155,12 @@ type PaymentH5PayResp struct {
|
|||||||
ChannelSendNo *string `json:"channelSendNo"`
|
ChannelSendNo *string `json:"channelSendNo"`
|
||||||
ChannelTradeNo *string `json:"channelTradeNo"`
|
ChannelTradeNo *string `json:"channelTradeNo"`
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
PayType string `json:"payType"`
|
PayType SftPayType `json:"payType"`
|
||||||
IfCode string `json:"ifCode"`
|
IfCode string `json:"ifCode"`
|
||||||
ExtParam *string `json:"extParam"`
|
ExtParam *string `json:"extParam"`
|
||||||
PayInfo *string `json:"payInfo"`
|
PayInfo *struct {
|
||||||
|
QrCodeUrl *string `json:"qrCodeUrl"`
|
||||||
|
} `json:"payInfo"`
|
||||||
Note *string `json:"note"`
|
Note *string `json:"note"`
|
||||||
TradeFee *int64 `json:"tradeFee"`
|
TradeFee *int64 `json:"tradeFee"`
|
||||||
StoreId *string `json:"storeId"`
|
StoreId *string `json:"storeId"`
|
||||||
@@ -165,9 +172,37 @@ type PaymentH5PayResp struct {
|
|||||||
SettlementType *string `json:"settlementType"`
|
SettlementType *string `json:"settlementType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SftClient) CreateOrderByRedirect(req *PaymentH5PayReq) (*PaymentH5PayResp, error) {
|
type PaymentH5PayResp struct {
|
||||||
const url = "https://pay.rscygroup.com/api/open/payment/h5pay"
|
Amount int64 `json:"amount"`
|
||||||
return call[PaymentH5PayResp](s, url, req)
|
MchOrderNo string `json:"mchOrderNo"`
|
||||||
|
PayOrderId string `json:"payOrderId"`
|
||||||
|
MercNo string `json:"mercNo"`
|
||||||
|
ChannelSendNo *string `json:"channelSendNo"`
|
||||||
|
ChannelTradeNo *string `json:"channelTradeNo"`
|
||||||
|
State string `json:"state"`
|
||||||
|
PayType SftPayType `json:"payType"`
|
||||||
|
IfCode string `json:"ifCode"`
|
||||||
|
ExtParam *string `json:"extParam"`
|
||||||
|
PayInfo *struct {
|
||||||
|
PayUrl *string `json:"payUrl"`
|
||||||
|
} `json:"payInfo"`
|
||||||
|
Note *string `json:"note"`
|
||||||
|
TradeFee *int64 `json:"tradeFee"`
|
||||||
|
StoreId *string `json:"storeId"`
|
||||||
|
Subject *string `json:"subject"`
|
||||||
|
DrType *string `json:"drType"`
|
||||||
|
RefundAmt *int64 `json:"refundAmt"`
|
||||||
|
RefundState *int `json:"refundState"`
|
||||||
|
CashFee *int64 `json:"cashFee"`
|
||||||
|
SettlementType *string `json:"settlementType"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderCloseResp struct {
|
||||||
|
MchOrderNo string `json:"mchOrderNo"`
|
||||||
|
PayOrderId string `json:"payOrderId"`
|
||||||
|
MercNo string `json:"mercNo"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
State string `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func call[T any](s *SftClient, url string, req any) (*T, error) {
|
func call[T any](s *SftClient, url string, req any) (*T, error) {
|
||||||
@@ -231,7 +266,6 @@ func call[T any](s *SftClient, url string, req any) (*T, error) {
|
|||||||
|
|
||||||
func (s *SftClient) sign(msg any) (*request, error) {
|
func (s *SftClient) sign(msg any) (*request, error) {
|
||||||
|
|
||||||
// 处理请求正文
|
|
||||||
bytes, err := json.Marshal(msg)
|
bytes, err := json.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("格式化加密正文失败:%w", err)
|
return nil, fmt.Errorf("格式化加密正文失败:%w", err)
|
||||||
@@ -246,12 +280,13 @@ func (s *SftClient) sign(msg any) (*request, error) {
|
|||||||
BizData: string(bytes),
|
BizData: string(bytes),
|
||||||
}
|
}
|
||||||
|
|
||||||
encrypted, err := rsa.SignPKCS1v15(rand.Reader, s.privateKey, crypto.SHA256, []byte(body.String(s.appSecret)))
|
hashed := sha256.Sum256([]byte(body.String()))
|
||||||
|
signature, err := rsa.SignPKCS1v15(nil, s.privateKey, crypto.SHA256, hashed[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("签名失败:%w", err)
|
return nil, fmt.Errorf("签名失败:%w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body.Sign = string(encrypted)
|
body.Sign = string(signature)
|
||||||
return &body, nil
|
return &body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +299,9 @@ func (s *SftClient) verify(str []byte) (*response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if resp.Sign != nil || resp.SignType != nil || resp.BizData != nil {
|
if resp.Sign != nil || resp.SignType != nil || resp.BizData != nil {
|
||||||
err := rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, str, []byte(s.appSecret))
|
|
||||||
|
hashed := sha256.Sum256([]byte(resp.String()))
|
||||||
|
err := rsa.VerifyPKCS1v15(s.publicKey, crypto.SHA256, hashed[:], []byte(*resp.Sign))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("验签失败:%w", err)
|
return nil, fmt.Errorf("验签失败:%w", err)
|
||||||
}
|
}
|
||||||
@@ -283,10 +320,10 @@ type request struct {
|
|||||||
BizData string `json:"bizData"`
|
BizData string `json:"bizData"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r request) String(secret string) string {
|
func (r request) String() string {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"appId=%s&bizData=%s&reqId=%s&reqTime=%s&signType=%s&version=%s&appSecret=%s",
|
"appId=%s&bizData=%s&reqId=%s&reqTime=%s&signType=%s&version=%s",
|
||||||
r.AppId, r.BizData, r.ReqId, r.ReqTime, r.SignType, r.Version, secret,
|
r.AppId, r.BizData, r.ReqId, r.ReqTime, r.SignType, r.Version,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,3 +335,18 @@ type response struct {
|
|||||||
SignType *string `json:"signType"`
|
SignType *string `json:"signType"`
|
||||||
Timestamp string `json:"timestamp"`
|
Timestamp string `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r response) String() string {
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SftPayType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SftAlipay SftPayType = "ALIPAY"
|
||||||
|
SftWeChat SftPayType = "WECHAT"
|
||||||
|
SftUnionPay SftPayType = "UNIONPAY"
|
||||||
|
)
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ func RechargeConfirmAlipay(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 验证支付结果
|
// 验证支付结果
|
||||||
result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
|
result, err := s.Trade.VerifyCreateTrade(&s.TradeVerifyData{
|
||||||
TradeNo: req.TradeNo,
|
TradeNo: req.TradeNo,
|
||||||
Method: trade2.MethodAlipay,
|
Method: trade2.MethodAlipay,
|
||||||
})
|
})
|
||||||
@@ -289,7 +289,7 @@ func RechargeConfirmWechat(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 验证支付结果
|
// 验证支付结果
|
||||||
result, err := s.Trade.VerifyTrade(&s.TradeVerifyData{
|
result, err := s.Trade.VerifyCreateTrade(&s.TradeVerifyData{
|
||||||
TradeNo: req.TradeNo,
|
TradeNo: req.TradeNo,
|
||||||
Method: trade2.MethodWeChat,
|
Method: trade2.MethodWeChat,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type Trade struct {
|
|||||||
Remark *string `gorm:"column:remark;type:character varying(255);comment:订单备注" json:"remark"` // 订单备注
|
Remark *string `gorm:"column:remark;type:character varying(255);comment:订单备注" json:"remark"` // 订单备注
|
||||||
Amount decimal.Decimal `gorm:"column:amount;type:numeric(12,2);not null;comment:订单总金额" json:"amount"` // 订单总金额
|
Amount decimal.Decimal `gorm:"column:amount;type:numeric(12,2);not null;comment:订单总金额" json:"amount"` // 订单总金额
|
||||||
Payment decimal.Decimal `gorm:"column:payment;type:numeric(12,2);not null;comment:支付金额" json:"payment"` // 支付金额
|
Payment decimal.Decimal `gorm:"column:payment;type:numeric(12,2);not null;comment:支付金额" json:"payment"` // 支付金额
|
||||||
Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式:1-支付宝,2-微信" json:"method"` // 支付方式:1-支付宝,2-微信
|
Method int32 `gorm:"column:method;type:integer;not null;comment:支付方式:1-支付宝,2-微信,3-商福通" json:"method"` // 支付方式:1-支付宝,2-微信,3-商福通
|
||||||
Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态:0-待支付,1-已支付,2-已取消,3-已退款" json:"status"` // 订单状态:0-待支付,1-已支付,2-已取消,3-已退款
|
Status int32 `gorm:"column:status;type:integer;not null;comment:订单状态:0-待支付,1-已支付,2-已取消,3-已退款" json:"status"` // 订单状态:0-待支付,1-已支付,2-已取消,3-已退款
|
||||||
PayURL *string `gorm:"column:pay_url;type:text;comment:支付链接" json:"pay_url"` // 支付链接
|
PayURL *string `gorm:"column:pay_url;type:text;comment:支付链接" json:"pay_url"` // 支付链接
|
||||||
PaidAt *orm.LocalDateTime `gorm:"column:paid_at;type:timestamp without time zone;comment:支付时间" json:"paid_at"` // 支付时间
|
PaidAt *orm.LocalDateTime `gorm:"column:paid_at;type:timestamp without time zone;comment:支付时间" json:"paid_at"` // 支付时间
|
||||||
@@ -32,6 +32,7 @@ type Trade struct {
|
|||||||
CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
CreatedAt *orm.LocalDateTime `gorm:"column:created_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
|
||||||
UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
UpdatedAt *orm.LocalDateTime `gorm:"column:updated_at;type:timestamp without time zone;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
|
||||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp without time zone;comment:删除时间" json:"deleted_at"` // 删除时间
|
||||||
|
Acquirer int32 `gorm:"column:acquirer;type:integer;not null;comment:收单机构:1-支付宝,2-微信,3-银联" json:"acquirer"` // 收单机构:1-支付宝,2-微信,3-银联
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName Trade's table name
|
// TableName Trade's table name
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func newTrade(db *gorm.DB, opts ...gen.DOOption) trade {
|
|||||||
_trade.CreatedAt = field.NewField(tableName, "created_at")
|
_trade.CreatedAt = field.NewField(tableName, "created_at")
|
||||||
_trade.UpdatedAt = field.NewField(tableName, "updated_at")
|
_trade.UpdatedAt = field.NewField(tableName, "updated_at")
|
||||||
_trade.DeletedAt = field.NewField(tableName, "deleted_at")
|
_trade.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
_trade.Acquirer = field.NewInt32(tableName, "acquirer")
|
||||||
|
|
||||||
_trade.fillFieldMap()
|
_trade.fillFieldMap()
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ type trade struct {
|
|||||||
Remark field.String // 订单备注
|
Remark field.String // 订单备注
|
||||||
Amount field.Field // 订单总金额
|
Amount field.Field // 订单总金额
|
||||||
Payment field.Field // 支付金额
|
Payment field.Field // 支付金额
|
||||||
Method field.Int32 // 支付方式:1-支付宝,2-微信
|
Method field.Int32 // 支付方式:1-支付宝,2-微信,3-商福通
|
||||||
Status field.Int32 // 订单状态:0-待支付,1-已支付,2-已取消,3-已退款
|
Status field.Int32 // 订单状态:0-待支付,1-已支付,2-已取消,3-已退款
|
||||||
PayURL field.String // 支付链接
|
PayURL field.String // 支付链接
|
||||||
PaidAt field.Field // 支付时间
|
PaidAt field.Field // 支付时间
|
||||||
@@ -71,6 +72,7 @@ type trade struct {
|
|||||||
CreatedAt field.Field // 创建时间
|
CreatedAt field.Field // 创建时间
|
||||||
UpdatedAt field.Field // 更新时间
|
UpdatedAt field.Field // 更新时间
|
||||||
DeletedAt field.Field // 删除时间
|
DeletedAt field.Field // 删除时间
|
||||||
|
Acquirer field.Int32 // 收单机构:1-支付宝,2-微信,3-银联
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -104,6 +106,7 @@ func (t *trade) updateTableName(table string) *trade {
|
|||||||
t.CreatedAt = field.NewField(table, "created_at")
|
t.CreatedAt = field.NewField(table, "created_at")
|
||||||
t.UpdatedAt = field.NewField(table, "updated_at")
|
t.UpdatedAt = field.NewField(table, "updated_at")
|
||||||
t.DeletedAt = field.NewField(table, "deleted_at")
|
t.DeletedAt = field.NewField(table, "deleted_at")
|
||||||
|
t.Acquirer = field.NewInt32(table, "acquirer")
|
||||||
|
|
||||||
t.fillFieldMap()
|
t.fillFieldMap()
|
||||||
|
|
||||||
@@ -120,7 +123,7 @@ func (t *trade) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *trade) fillFieldMap() {
|
func (t *trade) fillFieldMap() {
|
||||||
t.fieldMap = make(map[string]field.Expr, 17)
|
t.fieldMap = make(map[string]field.Expr, 18)
|
||||||
t.fieldMap["id"] = t.ID
|
t.fieldMap["id"] = t.ID
|
||||||
t.fieldMap["user_id"] = t.UserID
|
t.fieldMap["user_id"] = t.UserID
|
||||||
t.fieldMap["inner_no"] = t.InnerNo
|
t.fieldMap["inner_no"] = t.InnerNo
|
||||||
@@ -138,6 +141,7 @@ func (t *trade) fillFieldMap() {
|
|||||||
t.fieldMap["created_at"] = t.CreatedAt
|
t.fieldMap["created_at"] = t.CreatedAt
|
||||||
t.fieldMap["updated_at"] = t.UpdatedAt
|
t.fieldMap["updated_at"] = t.UpdatedAt
|
||||||
t.fieldMap["deleted_at"] = t.DeletedAt
|
t.fieldMap["deleted_at"] = t.DeletedAt
|
||||||
|
t.fieldMap["acquirer"] = t.Acquirer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t trade) clone(db *gorm.DB) trade {
|
func (t trade) clone(db *gorm.DB) trade {
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func (s *resourceService) CompleteResource(tradeNo string, now time.Time, opResu
|
|||||||
rs = opResult[0]
|
rs = opResult[0]
|
||||||
} else {
|
} else {
|
||||||
var err error
|
var err error
|
||||||
rs, err = Trade.VerifyTrade(&TradeVerifyData{
|
rs, err = Trade.VerifyCreateTrade(&TradeVerifyData{
|
||||||
TradeNo: tradeNo,
|
TradeNo: tradeNo,
|
||||||
Method: cache.Method,
|
Method: cache.Method,
|
||||||
})
|
})
|
||||||
@@ -229,7 +229,7 @@ func (s *resourceService) CancelResource(tradeNo string, now time.Time, opRevoke
|
|||||||
|
|
||||||
// 取消交易
|
// 取消交易
|
||||||
if len(opRevoked) <= 0 {
|
if len(opRevoked) <= 0 {
|
||||||
err = Trade.SendCancelTrade(tradeNo, cache.Method)
|
err = Trade.CancelTrade(tradeNo, cache.Method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
|
|||||||
|
|
||||||
// 创建支付订单
|
// 创建支付订单
|
||||||
var payUrl string
|
var payUrl string
|
||||||
|
var acquirer trade2.Acquirer
|
||||||
switch method {
|
switch method {
|
||||||
|
|
||||||
// 调用支付宝支付接口
|
// 调用支付宝支付接口
|
||||||
@@ -122,6 +123,7 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
payUrl = resp.String()
|
payUrl = resp.String()
|
||||||
|
acquirer = trade2.AcquirerAlipay
|
||||||
|
|
||||||
// 调用微信支付接口
|
// 调用微信支付接口
|
||||||
case trade2.MethodWeChat:
|
case trade2.MethodWeChat:
|
||||||
@@ -140,6 +142,34 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
payUrl = *resp.CodeUrl
|
payUrl = *resp.CodeUrl
|
||||||
|
acquirer = trade2.AcquirerWeChat
|
||||||
|
|
||||||
|
// 调用商福通接口
|
||||||
|
case trade2.MethodSft:
|
||||||
|
resp, err := g.SFTPay.PaymentScanPay(&g.PaymentScanPayReq{
|
||||||
|
MchOrderNo: tradeNo,
|
||||||
|
Subject: subject,
|
||||||
|
Body: subject,
|
||||||
|
Amount: amountReal.Mul(decimal.NewFromInt(100)).Round(0).IntPart(),
|
||||||
|
Currency: "cny",
|
||||||
|
ClientIp: "",
|
||||||
|
OrderTimeout: u.P(expire.Format("2006-01-02 15:04:05")),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
payUrl = u.Z(u.Z(resp.PayInfo).QrCodeUrl)
|
||||||
|
if payUrl == "" {
|
||||||
|
return nil, errors.New("支付接口未返回正确的二维码地址")
|
||||||
|
}
|
||||||
|
switch resp.PayType {
|
||||||
|
case g.SftAlipay:
|
||||||
|
acquirer = trade2.AcquirerAlipay
|
||||||
|
case g.SftWeChat:
|
||||||
|
acquirer = trade2.AcquirerWeChat
|
||||||
|
case g.SftUnionPay:
|
||||||
|
acquirer = trade2.AcquirerUnionPay
|
||||||
|
}
|
||||||
|
|
||||||
// 不支持的支付方式
|
// 不支持的支付方式
|
||||||
default:
|
default:
|
||||||
@@ -163,6 +193,7 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
|
|||||||
Type: int32(tType),
|
Type: int32(tType),
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
PayURL: &payUrl,
|
PayURL: &payUrl,
|
||||||
|
Acquirer: int32(acquirer),
|
||||||
}
|
}
|
||||||
err = q.Trade.Create(&trade)
|
err = q.Trade.Create(&trade)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -198,105 +229,176 @@ func (s *tradeService) SendCreateTradeByQrcode(q *q.Query, uid int32, now time.T
|
|||||||
Trade: &trade,
|
Trade: &trade,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
func (s *tradeService) SendCreateTradeByRedirect() {
|
func (s *tradeService) SendCreateTradeByRedirect(q *q.Query, uid int32, now time.Time, data *TradeCreateData) (*TradeCreateResult, error) {
|
||||||
panic("todo")
|
var subject = data.Subject
|
||||||
}
|
var expire = data.ExpireAt
|
||||||
|
var tType = data.Type
|
||||||
|
var method = data.Method
|
||||||
|
var amount = data.Amount
|
||||||
|
|
||||||
func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.Trade, error) {
|
// 实际支付金额,只在创建真实订单时使用
|
||||||
var transId = data.TransId
|
var amountReal = data.Amount
|
||||||
var tradeNo = data.TradeNo
|
if env.RunMode == "debug" {
|
||||||
var payment = data.Payment
|
amountReal = decimal.NewFromFloat(0.01)
|
||||||
var paidAt = data.Time
|
}
|
||||||
|
|
||||||
// 获取交易信息
|
// 附加优惠券
|
||||||
trade, err := q.Trade.
|
if data.CouponCode != "" {
|
||||||
Where(q.Trade.InnerNo.Eq(tradeNo)).
|
coupon, err := q.Coupon.
|
||||||
First()
|
Where(
|
||||||
|
q.Coupon.Code.Eq(data.CouponCode),
|
||||||
|
q.Coupon.Status.Eq(int32(coupon2.StatusUnused)),
|
||||||
|
).
|
||||||
|
Take()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, errors.New("优惠券不存在或已失效")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var expireAt = time.Time(u.Z(coupon.ExpireAt))
|
||||||
|
if !expireAt.IsZero() && expireAt.Before(now) {
|
||||||
|
_, err = q.Coupon.
|
||||||
|
Where(q.Coupon.ID.Eq(coupon.ID)).
|
||||||
|
Update(q.Coupon.Status, coupon2.StatusExpired)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, errors.New("优惠券已过期")
|
||||||
|
}
|
||||||
|
|
||||||
|
if amount.Cmp(coupon.MinAmount) < 0 {
|
||||||
|
return nil, errors.New("订单金额未达到使用优惠券的条件")
|
||||||
|
}
|
||||||
|
|
||||||
|
if coupon.UserID != nil {
|
||||||
|
switch *coupon.UserID {
|
||||||
|
// 指定用户的优惠券
|
||||||
|
case uid:
|
||||||
|
amount = amount.Sub(coupon.Amount)
|
||||||
|
if expireAt.IsZero() {
|
||||||
|
_, err = q.Coupon.
|
||||||
|
Where(q.Coupon.ID.Eq(coupon.ID)).
|
||||||
|
Update(q.Coupon.Status, int32(coupon2.StatusUsed))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 该优惠券不属于当前用户
|
||||||
|
default:
|
||||||
|
return nil, errors.New("优惠券不属于当前用户")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 公开优惠券
|
||||||
|
amount = amount.Sub(coupon.Amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成订单号
|
||||||
|
tradeNo, err := ID.GenSerial(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查交易状态
|
// 创建支付订单
|
||||||
switch trade2.Status(trade.Status) {
|
var payUrl string
|
||||||
|
var acquirer trade2.Acquirer
|
||||||
// 如果已退款或取消,则返回错误
|
|
||||||
case trade2.StatusCanceled, trade2.StatusRefunded:
|
|
||||||
return nil, errors.New("交易已取消或已退款")
|
|
||||||
|
|
||||||
// 如果是未支付,则更新支付状态
|
|
||||||
case trade2.StatusPending:
|
|
||||||
trade.Status = int32(trade2.StatusSuccess)
|
|
||||||
trade.OuterNo = &transId
|
|
||||||
trade.Payment = payment
|
|
||||||
trade.PaidAt = u.P(orm.LocalDateTime(paidAt))
|
|
||||||
trade.PayURL = u.P("")
|
|
||||||
_, err = q.Trade.Updates(trade)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case trade2.StatusSuccess:
|
|
||||||
}
|
|
||||||
|
|
||||||
return trade, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *tradeService) SendCancelTrade(tradeNo string, method trade2.Method) error {
|
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
|
|
||||||
case trade2.MethodAlipay:
|
// 调用商福通接口
|
||||||
resp, err := g.Alipay.TradeCancel(context.Background(), alipay.TradeCancel{
|
case trade2.MethodSftAlipay, trade2.MethodSftWeChat:
|
||||||
OutTradeNo: tradeNo,
|
var payType g.SftPayType
|
||||||
|
if method == trade2.MethodSftAlipay {
|
||||||
|
payType = g.SftAlipay
|
||||||
|
} else {
|
||||||
|
payType = g.SftWeChat
|
||||||
|
}
|
||||||
|
resp, err := g.SFTPay.PaymentH5Pay(&g.PaymentH5PayReq{
|
||||||
|
MchOrderNo: tradeNo,
|
||||||
|
Subject: subject,
|
||||||
|
Body: subject,
|
||||||
|
Amount: amountReal.Mul(decimal.NewFromInt(100)).Round(0).IntPart(),
|
||||||
|
PayType: payType,
|
||||||
|
Currency: "cny",
|
||||||
|
ClientIp: "",
|
||||||
|
OrderTimeout: u.P(expire.Format("2006-01-02 15:04:05")),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.Code != alipay.CodeSuccess {
|
payUrl = u.Z(u.Z(resp.PayInfo).PayUrl)
|
||||||
slog.Warn("支付宝交易取消失败", "code", resp.Code, "sub_code", resp.SubCode, "msg", resp.Msg)
|
if payUrl == "" {
|
||||||
return errors.New("交易取消失败")
|
return nil, errors.New("支付接口未返回正确的二维码地址")
|
||||||
|
}
|
||||||
|
switch resp.PayType {
|
||||||
|
case g.SftAlipay:
|
||||||
|
acquirer = trade2.AcquirerAlipay
|
||||||
|
case g.SftWeChat:
|
||||||
|
acquirer = trade2.AcquirerWeChat
|
||||||
|
case g.SftUnionPay:
|
||||||
|
acquirer = trade2.AcquirerUnionPay
|
||||||
}
|
}
|
||||||
|
|
||||||
case trade2.MethodWeChat:
|
// 不支持的支付方式
|
||||||
resp, err := g.WechatPay.Native.CloseOrder(context.Background(), native.CloseOrderRequest{
|
default:
|
||||||
Mchid: &env.WechatPayMchId,
|
return nil, ErrTransactionNotSupported
|
||||||
OutTradeNo: &tradeNo,
|
}
|
||||||
})
|
|
||||||
|
// 保存交易订单
|
||||||
|
var billType bill2.Type
|
||||||
|
switch tType {
|
||||||
|
case trade2.TypeRecharge:
|
||||||
|
billType = bill2.TypeRecharge
|
||||||
|
case trade2.TypePurchase:
|
||||||
|
billType = bill2.TypeConsume
|
||||||
|
}
|
||||||
|
|
||||||
|
var trade = m.Trade{
|
||||||
|
UserID: uid,
|
||||||
|
InnerNo: tradeNo,
|
||||||
|
Subject: subject,
|
||||||
|
Method: int32(method),
|
||||||
|
Type: int32(tType),
|
||||||
|
Amount: amount,
|
||||||
|
PayURL: &payUrl,
|
||||||
|
Acquirer: int32(acquirer),
|
||||||
|
}
|
||||||
|
err = q.Trade.Create(&trade)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
|
||||||
if resp.Response.StatusCode != http.StatusNoContent {
|
|
||||||
body, _ := io.ReadAll(resp.Response.Body)
|
|
||||||
slog.Warn("微信交易取消失败", "code", resp.Response.StatusCode, "body", string(body))
|
|
||||||
return errors.New("交易取消失败")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// 保存用户帐单
|
||||||
}
|
var bill = m.Bill{
|
||||||
func (s *tradeService) OnTradeCanceled(q *q.Query, tradeNo string, now time.Time) error {
|
BillNo: ID.GenReadable("bil"),
|
||||||
_, err := q.Trade.
|
UserID: uid,
|
||||||
Where(q.Trade.InnerNo.Eq(tradeNo)).
|
TradeID: &trade.ID,
|
||||||
Select(q.Trade.Status, q.Trade.CancelAt, q.Trade.PayURL).
|
Info: &subject,
|
||||||
Updates(m.Trade{
|
Type: int32(billType),
|
||||||
Status: int32(trade2.StatusCanceled),
|
Amount: amount,
|
||||||
CancelAt: u.P(orm.LocalDateTime(now)),
|
}
|
||||||
PayURL: u.P(""),
|
err = q.Bill.
|
||||||
})
|
Omit(q.Bill.ResourceID, q.Bill.RefundID).
|
||||||
|
Create(&bill)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// 提交异步任务更新订单状态
|
||||||
}
|
_, err = g.Asynq.Enqueue(tasks.NewUpdateTrade(tradeNo, method))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *tradeService) SendRefundTrade(tradeNo string, method trade2.Method) error {
|
return &TradeCreateResult{
|
||||||
panic("todo")
|
TradeNo: tradeNo,
|
||||||
|
PayURL: payUrl,
|
||||||
|
Bill: &bill,
|
||||||
|
Trade: &trade,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
func (s *tradeService) OnTradeRefunded(q *q.Query, tradeNo string, now time.Time) error {
|
func (s *tradeService) VerifyCreateTrade(data *TradeVerifyData) (*TradeSuccessResult, error) {
|
||||||
panic("todo")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult, error) {
|
|
||||||
var tradeNo = data.TradeNo
|
var tradeNo = data.TradeNo
|
||||||
var method = data.Method
|
var method = data.Method
|
||||||
|
|
||||||
@@ -363,6 +465,114 @@ func (s *tradeService) VerifyTrade(data *TradeVerifyData) (*TradeSuccessResult,
|
|||||||
Time: paidAt,
|
Time: paidAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
func (s *tradeService) OnTradeCreated(q *q.Query, data *OnTradeCreateData) (*m.Trade, error) {
|
||||||
|
var transId = data.TransId
|
||||||
|
var tradeNo = data.TradeNo
|
||||||
|
var payment = data.Payment
|
||||||
|
var paidAt = data.Time
|
||||||
|
|
||||||
|
// 获取交易信息
|
||||||
|
trade, err := q.Trade.
|
||||||
|
Where(q.Trade.InnerNo.Eq(tradeNo)).
|
||||||
|
First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查交易状态
|
||||||
|
switch trade2.Status(trade.Status) {
|
||||||
|
|
||||||
|
// 如果已退款或取消,则返回错误
|
||||||
|
case trade2.StatusCanceled, trade2.StatusRefunded:
|
||||||
|
return nil, errors.New("交易已取消或已退款")
|
||||||
|
|
||||||
|
// 如果是未支付,则更新支付状态
|
||||||
|
case trade2.StatusPending:
|
||||||
|
trade.Status = int32(trade2.StatusSuccess)
|
||||||
|
trade.OuterNo = &transId
|
||||||
|
trade.Payment = payment
|
||||||
|
trade.PaidAt = u.P(orm.LocalDateTime(paidAt))
|
||||||
|
trade.PayURL = u.P("")
|
||||||
|
_, err = q.Trade.Updates(trade)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case trade2.StatusSuccess:
|
||||||
|
}
|
||||||
|
|
||||||
|
return trade, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tradeService) CancelTrade(tradeNo string, method trade2.Method) error {
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
|
||||||
|
case trade2.MethodAlipay:
|
||||||
|
resp, err := g.Alipay.TradeCancel(context.Background(), alipay.TradeCancel{
|
||||||
|
OutTradeNo: tradeNo,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Code != alipay.CodeSuccess {
|
||||||
|
slog.Warn("支付宝交易取消失败", "code", resp.Code, "sub_code", resp.SubCode, "msg", resp.Msg)
|
||||||
|
return errors.New("交易取消失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
case trade2.MethodWeChat:
|
||||||
|
resp, err := g.WechatPay.Native.CloseOrder(context.Background(), native.CloseOrderRequest{
|
||||||
|
Mchid: &env.WechatPayMchId,
|
||||||
|
OutTradeNo: &tradeNo,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Response.StatusCode != http.StatusNoContent {
|
||||||
|
body, _ := io.ReadAll(resp.Response.Body)
|
||||||
|
slog.Warn("微信交易取消失败", "code", resp.Response.StatusCode, "body", string(body))
|
||||||
|
return errors.New("交易取消失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
case trade2.MethodSft:
|
||||||
|
resp, err := g.SFTPay.OrderClose(&g.OrderCloseReq{
|
||||||
|
MchOrderNo: &tradeNo,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.State != "TRADE_CLOSE" {
|
||||||
|
slog.Warn("商福通交易取消失败", "state", resp.State)
|
||||||
|
return errors.New("交易取消失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ErrTransactionNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *tradeService) OnTradeCanceled(q *q.Query, tradeNo string, now time.Time) error {
|
||||||
|
_, err := q.Trade.
|
||||||
|
Where(q.Trade.InnerNo.Eq(tradeNo)).
|
||||||
|
Select(q.Trade.Status, q.Trade.CancelAt, q.Trade.PayURL).
|
||||||
|
Updates(m.Trade{
|
||||||
|
Status: int32(trade2.StatusCanceled),
|
||||||
|
CancelAt: u.P(orm.LocalDateTime(now)),
|
||||||
|
PayURL: u.P(""),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *tradeService) SendRefundTrade(tradeNo string, method trade2.Method) error {
|
||||||
|
panic("todo")
|
||||||
|
}
|
||||||
|
func (s *tradeService) OnTradeRefunded(q *q.Query, tradeNo string, now time.Time) error {
|
||||||
|
panic("todo")
|
||||||
|
}
|
||||||
|
|
||||||
type TradeCreateData struct {
|
type TradeCreateData struct {
|
||||||
Subject string
|
Subject string
|
||||||
@@ -396,12 +606,11 @@ type OnTradeCreateData struct {
|
|||||||
TradeSuccessResult
|
TradeSuccessResult
|
||||||
}
|
}
|
||||||
|
|
||||||
type TradeResult int
|
type TradePlatform int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TradeSuccess TradeResult = iota + 1
|
TradePlatformDesktop TradePlatform = iota + 1 // 桌面端
|
||||||
TradeCanceled
|
TradePlatformMobile // 移动端
|
||||||
TradeClosed
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TradeErr string
|
type TradeErr string
|
||||||
|
|||||||
Reference in New Issue
Block a user