173 lines
7.8 KiB
Go
173 lines
7.8 KiB
Go
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)
|
|
}
|