支持拉卡拉多渠道支付和测试充值
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"hfb_sys/backend/internal/integrations/payment/lakala"
|
||||
"hfb_sys/backend/internal/integrations/payment/leshua"
|
||||
"hfb_sys/backend/internal/modules/paymentconfig"
|
||||
)
|
||||
|
||||
type channelClient interface {
|
||||
CreatePayment(ctx context.Context, req channelCreatePaymentRequest) (*channelCreatePaymentResponse, error)
|
||||
QueryPayment(ctx context.Context, thirdOrderID string, providerOrderID string) (*channelQueryPaymentResponse, error)
|
||||
CreateRefund(ctx context.Context, req channelCreateRefundRequest) (*channelCreateRefundResponse, error)
|
||||
QueryRefund(ctx context.Context, req channelQueryRefundRequest) (*channelQueryRefundResponse, error)
|
||||
VerifyNotify(params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error)
|
||||
}
|
||||
|
||||
type channelCreatePaymentRequest struct {
|
||||
ThirdOrderID string
|
||||
AmountCent int64
|
||||
PayWay string
|
||||
JSPayFlag string
|
||||
NotifyURL string
|
||||
JumpURL string
|
||||
ClientIP string
|
||||
Body string
|
||||
Attach string
|
||||
}
|
||||
|
||||
type channelCreatePaymentResponse struct {
|
||||
OK bool
|
||||
ErrorMessage string
|
||||
ProviderOrderID string
|
||||
PayWay string
|
||||
Status string
|
||||
PayTime string
|
||||
TDCode string
|
||||
JSPayURL string
|
||||
JSPayInfo string
|
||||
RawRequest map[string]string
|
||||
Raw map[string]string
|
||||
}
|
||||
|
||||
type channelQueryPaymentResponse struct {
|
||||
OK bool
|
||||
ErrorMessage string
|
||||
ProviderOrderID string
|
||||
Status string
|
||||
Amount string
|
||||
PayWay string
|
||||
PayTime string
|
||||
Raw map[string]string
|
||||
}
|
||||
|
||||
type channelCreateRefundRequest struct {
|
||||
ThirdOrderID string
|
||||
ProviderOrderID string
|
||||
MerchantRefundID string
|
||||
RefundAmountCent int64
|
||||
NotifyURL string
|
||||
Attach string
|
||||
Remark string
|
||||
ClientIP string
|
||||
}
|
||||
|
||||
type channelCreateRefundResponse struct {
|
||||
OK bool
|
||||
ErrorMessage string
|
||||
ProviderRefundID string
|
||||
Status string
|
||||
RefundAmount string
|
||||
RawRequest map[string]string
|
||||
Raw map[string]string
|
||||
}
|
||||
|
||||
type channelQueryRefundRequest struct {
|
||||
ThirdOrderID string
|
||||
ProviderOrderID string
|
||||
MerchantRefundID string
|
||||
ProviderRefundID string
|
||||
}
|
||||
|
||||
type channelQueryRefundResponse struct {
|
||||
OK bool
|
||||
ErrorMessage string
|
||||
ProviderRefundID string
|
||||
Status string
|
||||
RefundAmount string
|
||||
RefundTime string
|
||||
Raw map[string]string
|
||||
}
|
||||
|
||||
type channelVerifyNotifyResult struct {
|
||||
OK bool
|
||||
MatchedKey string
|
||||
Got string
|
||||
Expected map[string]string
|
||||
BaseString map[string]string
|
||||
ParamKeys []string
|
||||
}
|
||||
|
||||
type leshuaChannel struct {
|
||||
client *leshua.Client
|
||||
}
|
||||
|
||||
func newLeshuaChannel(cfg leshua.Config) channelClient {
|
||||
return leshuaChannel{client: leshua.NewClient(cfg)}
|
||||
}
|
||||
|
||||
func (c leshuaChannel) CreatePayment(ctx context.Context, req channelCreatePaymentRequest) (*channelCreatePaymentResponse, error) {
|
||||
resp, rawReq, err := c.client.CreatePayment(ctx, leshua.CreatePaymentRequest{
|
||||
ThirdOrderID: req.ThirdOrderID,
|
||||
AmountCent: req.AmountCent,
|
||||
PayWay: req.PayWay,
|
||||
JSPayFlag: req.JSPayFlag,
|
||||
NotifyURL: req.NotifyURL,
|
||||
JumpURL: req.JumpURL,
|
||||
ClientIP: req.ClientIP,
|
||||
Body: req.Body,
|
||||
Attach: req.Attach,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &channelCreatePaymentResponse{
|
||||
OK: resp.RespCode == "0" && resp.ResultCode == "0",
|
||||
ErrorMessage: resp.ErrorMessage,
|
||||
ProviderOrderID: resp.ProviderOrderID,
|
||||
PayWay: resp.PayWay,
|
||||
Status: normalizeLeshuaPaymentStatus(resp.Raw["status"]),
|
||||
PayTime: resp.Raw["pay_time"],
|
||||
TDCode: resp.TDCode,
|
||||
JSPayURL: resp.JSPayURL,
|
||||
JSPayInfo: resp.JSPayInfo,
|
||||
RawRequest: rawReq,
|
||||
Raw: resp.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c leshuaChannel) QueryPayment(ctx context.Context, thirdOrderID string, providerOrderID string) (*channelQueryPaymentResponse, error) {
|
||||
resp, err := c.client.QueryPayment(ctx, thirdOrderID, providerOrderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &channelQueryPaymentResponse{
|
||||
OK: resp.RespCode == "0" && resp.ResultCode == "0",
|
||||
ErrorMessage: resp.ErrorMessage,
|
||||
ProviderOrderID: resp.ProviderOrderID,
|
||||
Status: normalizeLeshuaPaymentStatus(resp.Status),
|
||||
Amount: resp.Amount,
|
||||
PayWay: resp.PayWay,
|
||||
PayTime: resp.PayTime,
|
||||
Raw: resp.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c leshuaChannel) CreateRefund(ctx context.Context, req channelCreateRefundRequest) (*channelCreateRefundResponse, error) {
|
||||
resp, rawReq, err := c.client.CreateRefund(ctx, leshua.CreateRefundRequest{
|
||||
ThirdOrderID: req.ThirdOrderID,
|
||||
LeshuaOrderID: req.ProviderOrderID,
|
||||
MerchantRefundID: req.MerchantRefundID,
|
||||
RefundAmountCent: req.RefundAmountCent,
|
||||
NotifyURL: req.NotifyURL,
|
||||
Attach: req.Attach,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &channelCreateRefundResponse{
|
||||
OK: resp.RespCode == "0" && resp.ResultCode == "0",
|
||||
ErrorMessage: resp.ErrorMessage,
|
||||
ProviderRefundID: resp.LeshuaRefundID,
|
||||
Status: normalizeLeshuaRefundStatus(resp.Status),
|
||||
RefundAmount: resp.RefundAmount,
|
||||
RawRequest: rawReq,
|
||||
Raw: resp.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c leshuaChannel) QueryRefund(ctx context.Context, req channelQueryRefundRequest) (*channelQueryRefundResponse, error) {
|
||||
resp, err := c.client.QueryRefund(ctx, leshua.QueryRefundRequest{
|
||||
ThirdOrderID: req.ThirdOrderID,
|
||||
LeshuaOrderID: req.ProviderOrderID,
|
||||
MerchantRefundID: req.MerchantRefundID,
|
||||
LeshuaRefundID: req.ProviderRefundID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &channelQueryRefundResponse{
|
||||
OK: resp.RespCode == "0" && resp.ResultCode == "0",
|
||||
ErrorMessage: resp.ErrorMessage,
|
||||
ProviderRefundID: resp.LeshuaRefundID,
|
||||
Status: normalizeLeshuaRefundStatus(resp.Status),
|
||||
RefundAmount: resp.RefundAmount,
|
||||
RefundTime: resp.RefundTime,
|
||||
Raw: resp.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c leshuaChannel) VerifyNotify(params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error) {
|
||||
verify := c.client.VerifyNotifyDetail(params)
|
||||
result := channelVerifyNotifyResult{
|
||||
OK: verify.OK,
|
||||
MatchedKey: verify.MatchedKey,
|
||||
Got: verify.Got,
|
||||
Expected: verify.Expected,
|
||||
BaseString: verify.BaseString,
|
||||
ParamKeys: verify.ParamKeys,
|
||||
}
|
||||
if !result.OK {
|
||||
return result, ErrPaymentVerifyFailed
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type lakalaChannel struct {
|
||||
client *lakala.Client
|
||||
}
|
||||
|
||||
func newLakalaChannel(cfg lakala.Config) channelClient {
|
||||
return lakalaChannel{client: lakala.NewClient(cfg)}
|
||||
}
|
||||
|
||||
func (c lakalaChannel) CreatePayment(ctx context.Context, req channelCreatePaymentRequest) (*channelCreatePaymentResponse, error) {
|
||||
resp, err := c.client.CreatePayment(ctx, lakala.CreatePaymentRequest{
|
||||
ThirdOrderID: req.ThirdOrderID,
|
||||
AmountCent: req.AmountCent,
|
||||
PayWay: req.PayWay,
|
||||
JSPayFlag: req.JSPayFlag,
|
||||
NotifyURL: req.NotifyURL,
|
||||
JumpURL: req.JumpURL,
|
||||
ClientIP: req.ClientIP,
|
||||
Body: req.Body,
|
||||
Attach: req.Attach,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &channelCreatePaymentResponse{
|
||||
OK: resp.OK,
|
||||
ErrorMessage: resp.ErrorMessage,
|
||||
ProviderOrderID: resp.ProviderOrderID,
|
||||
PayWay: resp.PayWay,
|
||||
Status: resp.Status,
|
||||
PayTime: resp.PayTime,
|
||||
TDCode: resp.TDCode,
|
||||
JSPayURL: resp.JSPayURL,
|
||||
JSPayInfo: resp.JSPayInfo,
|
||||
RawRequest: resp.RawRequest,
|
||||
Raw: resp.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c lakalaChannel) QueryPayment(ctx context.Context, thirdOrderID string, providerOrderID string) (*channelQueryPaymentResponse, error) {
|
||||
resp, err := c.client.QueryPayment(ctx, thirdOrderID, providerOrderID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &channelQueryPaymentResponse{
|
||||
OK: resp.OK,
|
||||
ErrorMessage: resp.ErrorMessage,
|
||||
ProviderOrderID: resp.ProviderOrderID,
|
||||
Status: resp.Status,
|
||||
Amount: resp.Amount,
|
||||
PayWay: resp.PayWay,
|
||||
PayTime: resp.PayTime,
|
||||
Raw: resp.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c lakalaChannel) CreateRefund(ctx context.Context, req channelCreateRefundRequest) (*channelCreateRefundResponse, error) {
|
||||
resp, err := c.client.CreateRefund(ctx, lakala.CreateRefundRequest{
|
||||
ThirdOrderID: req.ThirdOrderID,
|
||||
ProviderOrderID: req.ProviderOrderID,
|
||||
MerchantRefundID: req.MerchantRefundID,
|
||||
RefundAmountCent: req.RefundAmountCent,
|
||||
NotifyURL: req.NotifyURL,
|
||||
Attach: req.Attach,
|
||||
RefundReason: req.Remark,
|
||||
ClientIP: req.ClientIP,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &channelCreateRefundResponse{
|
||||
OK: resp.OK,
|
||||
ErrorMessage: resp.ErrorMessage,
|
||||
ProviderRefundID: resp.ProviderRefundID,
|
||||
Status: resp.Status,
|
||||
RefundAmount: resp.RefundAmount,
|
||||
RawRequest: resp.RawRequest,
|
||||
Raw: resp.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c lakalaChannel) QueryRefund(ctx context.Context, req channelQueryRefundRequest) (*channelQueryRefundResponse, error) {
|
||||
resp, err := c.client.QueryRefund(ctx, lakala.QueryRefundRequest{
|
||||
ThirdOrderID: req.ThirdOrderID,
|
||||
ProviderOrderID: req.ProviderOrderID,
|
||||
MerchantRefundID: req.MerchantRefundID,
|
||||
ProviderRefundID: req.ProviderRefundID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &channelQueryRefundResponse{
|
||||
OK: resp.OK,
|
||||
ErrorMessage: resp.ErrorMessage,
|
||||
ProviderRefundID: resp.ProviderRefundID,
|
||||
Status: resp.Status,
|
||||
RefundAmount: resp.RefundAmount,
|
||||
RefundTime: resp.RefundTime,
|
||||
Raw: resp.Raw,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c lakalaChannel) VerifyNotify(params map[string]string, rawPayload string, contentType string, authorization string) (channelVerifyNotifyResult, error) {
|
||||
verify := c.client.VerifyNotifyDetail(rawPayload, authorization)
|
||||
result := channelVerifyNotifyResult{
|
||||
OK: verify.OK,
|
||||
MatchedKey: verify.MatchedKey,
|
||||
Got: verify.Got,
|
||||
Expected: verify.Expected,
|
||||
BaseString: verify.BaseString,
|
||||
ParamKeys: verify.ParamKeys,
|
||||
}
|
||||
if !result.OK {
|
||||
return result, ErrPaymentVerifyFailed
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func buildChannelClient(dto *paymentconfig.ConfigDTO) (channelClient, error) {
|
||||
switch strings.ToLower(strings.TrimSpace(dto.Provider)) {
|
||||
case "leshua":
|
||||
return newLeshuaChannel(leshua.Config{
|
||||
GatewayURL: dto.GatewayURL,
|
||||
MerchantID: dto.MerchantID,
|
||||
SignKey: dto.SignKey,
|
||||
NotifyKey: dto.NotifyKey,
|
||||
NotifyURL: dto.NotifyURL,
|
||||
JumpURL: dto.JumpURL,
|
||||
PayWay: firstNonEmpty(dto.PayWay, "ZFBZF"),
|
||||
JSPayFlag: firstNonEmpty(dto.JSPayFlag, "2"),
|
||||
SignType: firstNonEmpty(dto.SignType, "MD5"),
|
||||
}), nil
|
||||
case "lakala":
|
||||
return newLakalaChannel(lakala.Config{
|
||||
GatewayURL: dto.GatewayURL,
|
||||
AppID: extraString(dto.ExtraConfig, "app_id"),
|
||||
SerialNo: extraString(dto.ExtraConfig, "serial_no"),
|
||||
MerchantID: dto.MerchantID,
|
||||
TermNo: extraString(dto.ExtraConfig, "term_no"),
|
||||
PrivateKey: dto.SignKey,
|
||||
LakalaCert: extraString(dto.ExtraConfig, "lakala_cert"),
|
||||
NotifyCert: dto.NotifyKey,
|
||||
NotifyURL: dto.NotifyURL,
|
||||
JumpURL: dto.JumpURL,
|
||||
PayWay: firstNonEmpty(dto.PayWay, "ZFBZF"),
|
||||
JSPayFlag: firstNonEmpty(dto.JSPayFlag, "2"),
|
||||
PayMode: extraString(dto.ExtraConfig, "pay_mode"),
|
||||
OrderExpireMinutes: extraInt(dto.ExtraConfig, "order_expire_minutes"),
|
||||
}), nil
|
||||
case "mock":
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported payment provider: %s", dto.Provider)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeLeshuaPaymentStatus(status string) string {
|
||||
switch status {
|
||||
case "2", "30":
|
||||
return "paid"
|
||||
case "6":
|
||||
return "closed"
|
||||
case "8":
|
||||
return "failed"
|
||||
default:
|
||||
return "paying"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeLeshuaRefundStatus(status string) string {
|
||||
switch status {
|
||||
case "11":
|
||||
return "refunded"
|
||||
case "12":
|
||||
return "failed"
|
||||
default:
|
||||
return "refunding"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeNotifyPaymentStatus(provider string, status string) string {
|
||||
if provider == "leshua" {
|
||||
return normalizeLeshuaPaymentStatus(status)
|
||||
}
|
||||
switch status {
|
||||
case "paid", "closed", "failed", "paying":
|
||||
return status
|
||||
default:
|
||||
return "paying"
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeNotifyRefundStatus(provider string, status string) string {
|
||||
if provider == "leshua" {
|
||||
return normalizeLeshuaRefundStatus(status)
|
||||
}
|
||||
switch status {
|
||||
case "refunded", "failed", "refunding":
|
||||
return status
|
||||
default:
|
||||
return "refunding"
|
||||
}
|
||||
}
|
||||
|
||||
func extraString(config map[string]any, key string) string {
|
||||
if config == nil {
|
||||
return ""
|
||||
}
|
||||
value, ok := config[key]
|
||||
if !ok || value == nil {
|
||||
return ""
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(typed)
|
||||
default:
|
||||
return strings.TrimSpace(fmt.Sprint(typed))
|
||||
}
|
||||
}
|
||||
|
||||
func extraInt(config map[string]any, key string) int {
|
||||
value := extraString(config, key)
|
||||
if value == "" {
|
||||
return 0
|
||||
}
|
||||
n, _ := strconv.Atoi(value)
|
||||
return n
|
||||
}
|
||||
Reference in New Issue
Block a user