优化结构
This commit is contained in:
@@ -1,223 +0,0 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"github.com/go-pay/gopay"
|
||||
"github.com/go-pay/gopay/saobei"
|
||||
)
|
||||
|
||||
type saobeiClient interface {
|
||||
MiniPay(context.Context, gopay.BodyMap) (*saobei.MiniPayRsp, error)
|
||||
BarcodePay(context.Context, gopay.BodyMap) (*saobei.BarcodePayRsp, error)
|
||||
Query(context.Context, gopay.BodyMap) (*saobei.QueryRsp, error)
|
||||
Refund(context.Context, gopay.BodyMap) (*saobei.RefundRsp, error)
|
||||
}
|
||||
|
||||
type saobeiAdapter struct {
|
||||
newClient func(map[string]any) (saobeiClient, error)
|
||||
}
|
||||
|
||||
func saobeiBarcodeOK(returnCode, resultCode string) bool {
|
||||
return saobeiResultOK(returnCode, resultCode)
|
||||
}
|
||||
|
||||
func (a *saobeiAdapter) client(c map[string]any) (saobeiClient, error) {
|
||||
if a.newClient != nil {
|
||||
return a.newClient(c)
|
||||
}
|
||||
return saobei.NewClient(text(c, "inst_no"), text(c, "key"), text(c, "merchant_no"), text(c, "terminal_id"), text(c, "access_token"), !strings.EqualFold(text(c, "environment"), "sandbox"))
|
||||
}
|
||||
|
||||
func (a *saobeiAdapter) Create(ctx context.Context, req *biz.PaymentRequest, c map[string]any) (*biz.PaymentResult, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("扫呗支付下单请求为空")
|
||||
}
|
||||
method, err := saobeiCreateMethod(req.Extra, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := a.client(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
now := time.Now().Format("20060102150405")
|
||||
bm := gopay.BodyMap{"pay_type": firstAnyOr(req.Extra, "010", "pay_type", "payment_type"), "terminal_ip": req.ClientIP, "terminal_trace": req.TradeNo, "terminal_time": now, "total_fee": req.Amount}
|
||||
mergeGoPayExtras(bm, req.Extra, "pay_type", "payment_type", "terminal_ip", "terminal_trace", "terminal_time", "total_fee", "method", "pay_method", "trade_type")
|
||||
if method == "barcode" {
|
||||
authNo := strings.TrimSpace(firstAny(req.Extra, "auth_no", "auth_code", "authcode", "barcode", "pay_code"))
|
||||
if authNo == "" {
|
||||
return nil, errors.New("扫呗付款码支付缺少 auth_no")
|
||||
}
|
||||
bm.Set("auth_no", authNo)
|
||||
rsp, callErr := client.BarcodePay(ctx, bm)
|
||||
if callErr != nil {
|
||||
return nil, callErr
|
||||
}
|
||||
if rsp == nil || !saobeiBarcodeOK(rsp.ReturnCode, rsp.ResultCode) {
|
||||
return nil, fmt.Errorf("扫呗付款码支付失败: %s", firstNonEmpty(rspMap(rsp)["return_msg"], rspMap(rsp)["result_code"]))
|
||||
}
|
||||
if strings.TrimSpace(rsp.OutTradeNo) == "" {
|
||||
return nil, errors.New("扫呗支付下单响应缺少 out_trade_no")
|
||||
}
|
||||
status := "created"
|
||||
if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying {
|
||||
status = "pending"
|
||||
}
|
||||
return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: status, TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil
|
||||
}
|
||||
rsp, err := client.MiniPay(ctx, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rsp == nil || !saobeiOK(rsp.ReturnCode, rsp.ResultCode) {
|
||||
return nil, fmt.Errorf("扫呗支付下单失败: %s", firstNonEmpty(rspMap(rsp)["return_msg"], rspMap(rsp)["result_code"]))
|
||||
}
|
||||
if strings.TrimSpace(rsp.OutTradeNo) == "" {
|
||||
return nil, errors.New("扫呗支付下单响应缺少 out_trade_no")
|
||||
}
|
||||
return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: "client_pending", TradeNo: req.TradeNo, QueryID: strings.TrimSpace(rsp.OutTradeNo), Payload: mustJSON(rsp)}, nil
|
||||
}
|
||||
|
||||
func saobeiCreateMethod(extra, config map[string]any) (string, error) {
|
||||
value := firstAny(extra, "method", "pay_method", "trade_type")
|
||||
if value == "" {
|
||||
value = firstAny(config, "method", "pay_method", "trade_type")
|
||||
}
|
||||
normalized := strings.NewReplacer(".", "_", "-", "_", " ", "_").Replace(strings.ToLower(strings.TrimSpace(value)))
|
||||
switch normalized {
|
||||
case "", "mini", "mini_pay", "miniapp", "mini_app", "mini_program", "miniprogram", "jsapi", "js_api":
|
||||
return "mini", nil
|
||||
case "barcode", "barcode_pay", "micropay", "micro_pay", "scan", "scan_pay", "pay_code", "payment_code":
|
||||
return "barcode", nil
|
||||
default:
|
||||
return "", fmt.Errorf("扫呗支付不支持的下单方式: %s", value)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *saobeiAdapter) Query(ctx context.Context, tradeNo string, c map[string]any) (*biz.PaymentResult, error) {
|
||||
tradeNo = strings.TrimSpace(tradeNo)
|
||||
if tradeNo == "" {
|
||||
return nil, errors.New("扫呗支付查单缺少 out_trade_no")
|
||||
}
|
||||
client, err := a.client(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// terminal_trace is the query request's own trace ID. The durable platform
|
||||
// order identity is carried by out_trade_no and comes from PaymentResult.QueryID.
|
||||
bm := gopay.BodyMap{"pay_type": firstAnyOr(c, "010", "pay_type", "payment_type"), "terminal_trace": nonce(), "terminal_time": time.Now().Format("20060102150405"), "out_trade_no": tradeNo}
|
||||
mergeGoPayConfigExtras(bm, c, "query_extra", "pay_type", "terminal_trace", "terminal_time", "out_trade_no")
|
||||
rsp, err := client.Query(ctx, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rsp == nil {
|
||||
return nil, errors.New("扫呗支付查单响应为空")
|
||||
}
|
||||
if !saobeiResultOK(rsp.ReturnCode, rsp.ResultCode) {
|
||||
return nil, fmt.Errorf("扫呗支付查单失败: %s", firstNonEmpty(rspMap(rsp)["return_msg"], rspMap(rsp)["result_code"]))
|
||||
}
|
||||
queryID := strings.TrimSpace(tradeNo)
|
||||
if responseQueryID := strings.TrimSpace(rsp.OutTradeNo); responseQueryID == "" {
|
||||
return nil, errors.New("扫呗支付查单响应缺少 out_trade_no")
|
||||
} else if responseQueryID != queryID {
|
||||
return nil, errors.New("扫呗支付查单平台订单号不匹配")
|
||||
}
|
||||
tradeNoResult := strings.TrimSpace(rsp.TerminalTrace)
|
||||
if tradeNoResult == "" {
|
||||
tradeNoResult = strings.TrimSpace(rsp.PayTrace)
|
||||
}
|
||||
if tradeNoResult == "" {
|
||||
return nil, errors.New("扫呗支付查单响应缺少商户订单号")
|
||||
}
|
||||
status := normalizeSaobeiState(rsp.TradeState)
|
||||
if strings.TrimSpace(rsp.ResultCode) == saobei.ResultCodePaying {
|
||||
status = "pending"
|
||||
}
|
||||
result := &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: status, TradeNo: tradeNoResult, ProviderTradeNo: strings.TrimSpace(rsp.ChannelTradeNo), QueryID: queryID, Payload: mustJSON(rsp), Currency: strings.ToUpper(firstAnyOr(c, "CNY", "currency", "default_currency"))}
|
||||
if rsp.TotalFee != "" {
|
||||
result.Amount, err = parseIntegerAmount(rsp.TotalFee)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析扫呗订单金额: %w", err)
|
||||
}
|
||||
}
|
||||
if rsp.BuyerPayFee != "" {
|
||||
result.PayerPaidAmount, err = parseIntegerAmount(rsp.BuyerPayFee)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析扫呗买家实付金额: %w", err)
|
||||
}
|
||||
result.CashPaidAmount = result.PayerPaidAmount
|
||||
if result.Amount >= result.PayerPaidAmount {
|
||||
result.DiscountAmount = result.Amount - result.PayerPaidAmount
|
||||
result.AmountBreakdownKnown = true
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (a *saobeiAdapter) Refund(ctx context.Context, req *biz.PaymentRefundRequest, c map[string]any) (*biz.PaymentResult, error) {
|
||||
if req == nil {
|
||||
return nil, errors.New("扫呗支付退款请求为空")
|
||||
}
|
||||
tradeNo := strings.TrimSpace(req.TradeNo)
|
||||
refundNo := strings.TrimSpace(req.RefundNo)
|
||||
if tradeNo == "" || refundNo == "" {
|
||||
return nil, errors.New("扫呗支付退款缺少商户订单号或退款单号")
|
||||
}
|
||||
if req.Amount <= 0 {
|
||||
return nil, errors.New("扫呗支付退款金额无效")
|
||||
}
|
||||
client, err := a.client(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outTradeNo := strings.TrimSpace(req.QueryID)
|
||||
if outTradeNo == "" {
|
||||
outTradeNo = tradeNo
|
||||
}
|
||||
bm := gopay.BodyMap{"pay_type": firstAnyOr(c, "010", "pay_type", "payment_type"), "terminal_trace": refundNo, "terminal_time": time.Now().Format("20060102150405"), "refund_fee": req.Amount, "out_trade_no": outTradeNo}
|
||||
mergeGoPayConfigExtras(bm, c, "refund_extra", "pay_type", "terminal_trace", "terminal_time", "refund_fee", "out_trade_no", "out_refund_no")
|
||||
rsp, err := client.Refund(ctx, bm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rsp == nil {
|
||||
return nil, errors.New("扫呗支付退款响应为空")
|
||||
}
|
||||
if !saobeiResultOK(rsp.ReturnCode, rsp.ResultCode) {
|
||||
return nil, fmt.Errorf("扫呗支付退款失败: %s", firstNonEmpty(rspMap(rsp)["return_msg"], rspMap(rsp)["result_code"]))
|
||||
}
|
||||
if responseOrderID := strings.TrimSpace(rsp.OutTradeNo); responseOrderID != "" && responseOrderID != outTradeNo {
|
||||
return nil, errors.New("扫呗支付退款响应平台订单号不匹配")
|
||||
}
|
||||
returnedTrace := strings.TrimSpace(rsp.TerminalTrace)
|
||||
if returnedTrace == "" {
|
||||
return nil, errors.New("扫呗支付退款响应缺少 terminal_trace")
|
||||
}
|
||||
if returnedTrace != refundNo {
|
||||
return nil, errors.New("扫呗支付退款响应的 terminal_trace 不匹配")
|
||||
}
|
||||
providerRefundID := strings.TrimSpace(rsp.OutRefundNo)
|
||||
if providerRefundID == "" {
|
||||
return nil, errors.New("扫呗支付退款响应缺少 out_refund_no")
|
||||
}
|
||||
refundAmount, err := parseIntegerAmount(rsp.RefundFee)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析扫呗退款金额: %w", err)
|
||||
}
|
||||
if refundAmount != req.Amount {
|
||||
return nil, errors.New("扫呗支付退款响应金额不匹配")
|
||||
}
|
||||
return &biz.PaymentResult{Provider: biz.PaymentSaobei, Status: "pending", TradeNo: tradeNo, ProviderTradeNo: providerRefundID, Amount: refundAmount, Currency: strings.ToUpper(req.Currency), Payload: mustJSON(rsp)}, nil
|
||||
}
|
||||
|
||||
func (a *saobeiAdapter) Callback(_ context.Context, _ *biz.PaymentCallback, _ map[string]any) (*biz.PaymentResult, error) {
|
||||
return nil, errors.New("扫呗支付回调没有可复用的 GoPay 验签器,请改用主动查单")
|
||||
}
|
||||
Reference in New Issue
Block a user