富友支付支持
This commit is contained in:
172
internal/infrastructure/payment/fuiou_scan.go
Normal file
172
internal/infrastructure/payment/fuiou_scan.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
agentrecharge "github.com/break/junhong_cmp_fiber/internal/application/agentrecharge"
|
||||
"github.com/break/junhong_cmp_fiber/internal/infrastructure/integrationlog"
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/fuiou"
|
||||
)
|
||||
|
||||
// FuiouScanAdapter 按富友主扫统一下单生成微信扫码支付链接并主动查单。
|
||||
type FuiouScanAdapter struct {
|
||||
integration *integrationlog.Repository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewFuiouScanAdapter 创建富友扫码支付适配器。
|
||||
func NewFuiouScanAdapter(integration *integrationlog.Repository, logger *zap.Logger) *FuiouScanAdapter {
|
||||
return &FuiouScanAdapter{integration: integration, logger: logger}
|
||||
}
|
||||
|
||||
// Available 判断富友主扫下单、验签与查单所需的配置是否完整。
|
||||
func (a *FuiouScanAdapter) Available(config *model.WechatConfig) bool {
|
||||
return fuiouConfigComplete(config, true)
|
||||
}
|
||||
|
||||
// CreatePaymentURL 调用富友主扫统一下单并返回二维码链接。
|
||||
func (a *FuiouScanAdapter) CreatePaymentURL(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentResult, error) {
|
||||
attempt, err := a.startAttempt(ctx, request, constants.IntegrationOperationPaymentPreCreate, request.Config.ID, request.Amount)
|
||||
if err != nil {
|
||||
return agentrecharge.OnlinePaymentResult{}, err
|
||||
}
|
||||
startedAt := time.Now()
|
||||
client, err := a.newClient(request.Config)
|
||||
if err != nil {
|
||||
return agentrecharge.OnlinePaymentResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, err)
|
||||
}
|
||||
expireMinutes := int(time.Until(request.ExpireAt).Minutes())
|
||||
if expireMinutes < 1 {
|
||||
expireMinutes = 1
|
||||
}
|
||||
resp, callErr := client.PreCreate(
|
||||
request.PaymentNo, strconv.FormatInt(request.Amount, 10), request.Description,
|
||||
fuiou.GetServerIP(), fuiou.OrderTypeWechat, strconv.Itoa(expireMinutes),
|
||||
)
|
||||
if callErr != nil {
|
||||
return agentrecharge.OnlinePaymentResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
|
||||
}
|
||||
if strings.TrimSpace(resp.QrCode) == "" {
|
||||
return agentrecharge.OnlinePaymentResult{}, a.completeFailed(ctx, attempt.IntegrationID, startedAt, "empty_qr_code", "富友主扫下单未返回二维码链接")
|
||||
}
|
||||
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
|
||||
Result: constants.IntegrationResultSuccess, ResponseSummary: map[string]any{"success": true},
|
||||
DurationMS: time.Since(startedAt).Milliseconds(), StateChanged: true,
|
||||
}); err != nil {
|
||||
return agentrecharge.OnlinePaymentResult{}, err
|
||||
}
|
||||
return agentrecharge.OnlinePaymentResult{QRContent: resp.QrCode}, nil
|
||||
}
|
||||
|
||||
// Query 调用富友订单查询并按 trans_stat 返回统一查询结果。
|
||||
func (a *FuiouScanAdapter) Query(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentQueryResult, error) {
|
||||
attempt, err := a.startAttempt(ctx, request, constants.IntegrationOperationPaymentQuery, request.Config.ID, 0)
|
||||
if err != nil {
|
||||
return agentrecharge.OnlinePaymentQueryResult{}, err
|
||||
}
|
||||
startedAt := time.Now()
|
||||
client, err := a.newClient(request.Config)
|
||||
if err != nil {
|
||||
return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, err)
|
||||
}
|
||||
resp, callErr := client.CommonQuery(request.PaymentNo, fuiou.OrderTypeWechat)
|
||||
if callErr != nil {
|
||||
return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
|
||||
}
|
||||
result := agentrecharge.OnlinePaymentQueryResult{State: mapFuiouTransStat(resp.TransStat)}
|
||||
if result.State == agentrecharge.OnlinePaymentStatePaid {
|
||||
result.ThirdPartyTradeNo = strings.TrimSpace(resp.TransactionId)
|
||||
result.Amount, _ = strconv.ParseInt(strings.TrimSpace(resp.OrderAmt), 10, 64)
|
||||
if paidAt, ok := parseWechatPaidAt(strings.TrimSpace(resp.ReservedTxnFinTs)); ok {
|
||||
result.PaidAt = &paidAt
|
||||
}
|
||||
}
|
||||
providerCode := resp.TransStat
|
||||
if strings.TrimSpace(providerCode) == "" {
|
||||
providerCode = "unknown"
|
||||
}
|
||||
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
|
||||
Result: constants.IntegrationResultSuccess, ProviderCode: providerCode,
|
||||
ResponseSummary: map[string]any{"state": result.State, "has_trade_no": result.ThirdPartyTradeNo != ""},
|
||||
DurationMS: time.Since(startedAt).Milliseconds(),
|
||||
}); err != nil {
|
||||
return agentrecharge.OnlinePaymentQueryResult{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func fuiouConfigComplete(config *model.WechatConfig, requireActive bool) bool {
|
||||
return config != nil && (!requireActive || config.IsActive) && config.ProviderType == model.ProviderTypeFuiou &&
|
||||
config.FyInsCd != "" && config.FyMchntCd != "" && config.FyTermID != "" &&
|
||||
config.FyPrivateKey != "" && config.FyPublicKey != "" && config.FyAPIURL != "" && config.FyNotifyURL != ""
|
||||
}
|
||||
|
||||
func (a *FuiouScanAdapter) newClient(config *model.WechatConfig) (*fuiou.Client, error) {
|
||||
if !fuiouConfigComplete(config, false) {
|
||||
return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "富友扫码支付配置不可用")
|
||||
}
|
||||
return fuiou.NewClient(
|
||||
config.FyInsCd, config.FyMchntCd, config.FyTermID, config.FyAPIURL, config.FyNotifyURL,
|
||||
config.FyPrivateKey, config.FyPublicKey, a.logger,
|
||||
)
|
||||
}
|
||||
|
||||
func mapFuiouTransStat(transStat string) string {
|
||||
switch transStat {
|
||||
case "SUCCESS":
|
||||
return agentrecharge.OnlinePaymentStatePaid
|
||||
case "PAYERROR", "CLOSED", "REVOKED":
|
||||
return agentrecharge.OnlinePaymentStateClosed
|
||||
case "USERPAYING", "NOTPAY":
|
||||
return agentrecharge.OnlinePaymentStatePending
|
||||
default:
|
||||
return agentrecharge.OnlinePaymentStateUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func (a *FuiouScanAdapter) startAttempt(ctx context.Context, request agentrecharge.OnlinePaymentRequest, operation string, configID uint, amount int64) (*model.IntegrationLog, error) {
|
||||
resourceID, resourceKey := strconv.FormatUint(uint64(request.PaymentID), 10), request.PaymentNo
|
||||
series := "agent-recharge-payment:" + resourceID + ":" + operation
|
||||
correlationID := request.CorrelationID
|
||||
return a.integration.Start(ctx, integrationlog.Attempt{
|
||||
Provider: constants.IntegrationProviderFuiou, Direction: constants.IntegrationDirectionOutbound,
|
||||
Operation: operation, ResourceType: constants.IntegrationResourceTypeAgentRechargePayment,
|
||||
ResourceID: &resourceID, ResourceKey: &resourceKey, ExternalID: &resourceKey,
|
||||
TriggerSeries: &series, CorrelationID: &correlationID,
|
||||
RequestSummary: map[string]any{"payment_config_id": configID, "amount": amount},
|
||||
})
|
||||
}
|
||||
|
||||
func (a *FuiouScanAdapter) completeUnknown(ctx context.Context, integrationID string, startedAt time.Time, cause error) error {
|
||||
if a.logger != nil {
|
||||
a.logger.Warn("富友支付请求结果未知", zap.String("integration_id", integrationID), zap.Error(cause))
|
||||
}
|
||||
_, err := a.integration.Complete(ctx, integrationID, integrationlog.Completion{
|
||||
Result: constants.IntegrationResultUnknown, ProviderCode: "request_unknown", SafeProviderMessage: "富友支付请求结果未知",
|
||||
ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(),
|
||||
RecoveryStrategy: "使用原支付单号主动查单,确认不存在或关闭后才允许关闭本地支付单",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apperrors.Wrap(apperrors.CodeTimeout, cause, "富友支付请求结果未知")
|
||||
}
|
||||
|
||||
func (a *FuiouScanAdapter) completeFailed(ctx context.Context, integrationID string, startedAt time.Time, providerCode, providerMessage string) error {
|
||||
_, err := a.integration.Complete(ctx, integrationID, integrationlog.Completion{
|
||||
Result: constants.IntegrationResultFailed, ProviderCode: providerCode, SafeProviderMessage: providerMessage,
|
||||
ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return apperrors.New(apperrors.CodeServiceUnavailable, providerMessage)
|
||||
}
|
||||
Reference in New Issue
Block a user