All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m6s
179 lines
8.2 KiB
Go
179 lines
8.2 KiB
Go
package payment
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
sdkalipay "github.com/smartwalle/alipay/v3"
|
|
|
|
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"
|
|
alipaypkg "github.com/break/junhong_cmp_fiber/pkg/alipay"
|
|
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
|
apperrors "github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
)
|
|
|
|
// AlipayPreCreateAdapter 使用现有 smartwalle/alipay 实现当面付预下单与查单。
|
|
type AlipayPreCreateAdapter struct {
|
|
integration *integrationlog.Repository
|
|
}
|
|
|
|
// NewAlipayPreCreateAdapter 创建支付宝当面付适配器。
|
|
func NewAlipayPreCreateAdapter(integration *integrationlog.Repository) *AlipayPreCreateAdapter {
|
|
return &AlipayPreCreateAdapter{integration: integration}
|
|
}
|
|
|
|
// Available 判断配置是否完整支持支付宝预下单、验签与查单。
|
|
func (a *AlipayPreCreateAdapter) Available(config *model.WechatConfig) bool {
|
|
return alipayConfigComplete(config, true)
|
|
}
|
|
|
|
// PreCreate 创建支付宝当面付扫码订单。
|
|
func (a *AlipayPreCreateAdapter) PreCreate(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentResult, error) {
|
|
client, err := a.client(request.Config)
|
|
if err != nil {
|
|
return agentrecharge.OnlinePaymentResult{}, err
|
|
}
|
|
attempt, err := a.startAttempt(ctx, request.PaymentNo, constants.IntegrationOperationPaymentPreCreate, request.Config.ID, request.Amount)
|
|
if err != nil {
|
|
return agentrecharge.OnlinePaymentResult{}, err
|
|
}
|
|
startedAt := time.Now()
|
|
response, callErr := client.TradePreCreate(ctx, sdkalipay.TradePreCreate{Trade: sdkalipay.Trade{
|
|
NotifyURL: request.Config.AliNotifyURL, Subject: request.Description, OutTradeNo: request.PaymentNo,
|
|
TotalAmount: alipaypkg.FenToYuan(request.Amount), ProductCode: "FACE_TO_FACE_PAYMENT",
|
|
TimeExpire: request.ExpireAt.Format("2006-01-02 15:04:05"),
|
|
}})
|
|
if callErr != nil {
|
|
return agentrecharge.OnlinePaymentResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
|
|
}
|
|
if response == nil || response.IsFailure() || strings.TrimSpace(response.QRCode) == "" {
|
|
providerCode, providerMessage := "empty_qr_code", "支付宝预下单未返回付款内容"
|
|
responseSummary := map[string]any{"success": false}
|
|
if response != nil && response.IsFailure() {
|
|
providerCode, providerMessage = string(response.Code), response.SubMsg
|
|
if response.SubCode != "" {
|
|
providerCode += ":" + response.SubCode
|
|
responseSummary["sub_code"] = response.SubCode
|
|
}
|
|
}
|
|
_, completeErr := a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
|
|
Result: constants.IntegrationResultFailed, ProviderCode: providerCode, ProviderMessage: providerMessage,
|
|
ResponseSummary: responseSummary, DurationMS: time.Since(startedAt).Milliseconds(),
|
|
})
|
|
if completeErr != nil {
|
|
return agentrecharge.OnlinePaymentResult{}, completeErr
|
|
}
|
|
return agentrecharge.OnlinePaymentResult{}, apperrors.New(apperrors.CodeServiceUnavailable, "支付宝预下单失败")
|
|
}
|
|
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
|
|
Result: constants.IntegrationResultSuccess, ProviderCode: string(response.Code),
|
|
ResponseSummary: map[string]any{"success": true}, DurationMS: time.Since(startedAt).Milliseconds(), StateChanged: true,
|
|
}); err != nil {
|
|
return agentrecharge.OnlinePaymentResult{}, err
|
|
}
|
|
return agentrecharge.OnlinePaymentResult{QRContent: response.QRCode}, nil
|
|
}
|
|
|
|
// Query 查询支付宝当面付订单状态。
|
|
func (a *AlipayPreCreateAdapter) Query(ctx context.Context, paymentNo string, config *model.WechatConfig) (agentrecharge.OnlinePaymentQueryResult, error) {
|
|
client, err := a.queryClient(config)
|
|
if err != nil {
|
|
return agentrecharge.OnlinePaymentQueryResult{}, err
|
|
}
|
|
attempt, err := a.startAttempt(ctx, paymentNo, constants.IntegrationOperationPaymentQuery, config.ID, 0)
|
|
if err != nil {
|
|
return agentrecharge.OnlinePaymentQueryResult{}, err
|
|
}
|
|
startedAt := time.Now()
|
|
response, callErr := client.TradeQuery(ctx, sdkalipay.TradeQuery{OutTradeNo: paymentNo})
|
|
if callErr != nil {
|
|
return agentrecharge.OnlinePaymentQueryResult{}, a.completeUnknown(ctx, attempt.IntegrationID, startedAt, callErr)
|
|
}
|
|
if response == nil || response.IsFailure() {
|
|
providerCode, providerMessage := "empty_response", "支付宝查单未返回有效响应"
|
|
if response != nil {
|
|
providerCode, providerMessage = string(response.Code), response.SubMsg
|
|
}
|
|
_, completeErr := a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
|
|
Result: constants.IntegrationResultFailed, ProviderCode: providerCode, ProviderMessage: providerMessage,
|
|
ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(),
|
|
})
|
|
if completeErr != nil {
|
|
return agentrecharge.OnlinePaymentQueryResult{}, completeErr
|
|
}
|
|
return agentrecharge.OnlinePaymentQueryResult{}, apperrors.New(apperrors.CodeServiceUnavailable, "支付宝查单失败")
|
|
}
|
|
result := agentrecharge.OnlinePaymentQueryResult{
|
|
State: mapAlipayTradeState(response.TradeStatus), ThirdPartyTradeNo: response.TradeNo,
|
|
}
|
|
result.Amount, _ = alipaypkg.YuanToFen(response.TotalAmount)
|
|
if paidAt, parseErr := time.ParseInLocation("2006-01-02 15:04:05", response.SendPayDate, time.Local); parseErr == nil {
|
|
result.PaidAt = &paidAt
|
|
}
|
|
if _, err = a.integration.Complete(ctx, attempt.IntegrationID, integrationlog.Completion{
|
|
Result: constants.IntegrationResultSuccess, ProviderCode: string(response.TradeStatus),
|
|
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 (a *AlipayPreCreateAdapter) client(config *model.WechatConfig) (*sdkalipay.Client, error) {
|
|
if !a.Available(config) {
|
|
return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "支付宝扫码支付配置不可用")
|
|
}
|
|
return alipaypkg.NewClientFromConfig(config)
|
|
}
|
|
|
|
func (a *AlipayPreCreateAdapter) queryClient(config *model.WechatConfig) (*sdkalipay.Client, error) {
|
|
if !alipayConfigComplete(config, false) {
|
|
return nil, apperrors.New(apperrors.CodeNoPaymentConfig, "支付宝查单配置不可用")
|
|
}
|
|
return alipaypkg.NewClientFromConfig(config)
|
|
}
|
|
|
|
func alipayConfigComplete(config *model.WechatConfig, requireActive bool) bool {
|
|
return config != nil && (!requireActive || config.IsActive) && config.AliAppID != "" && config.AliPrivateKey != "" &&
|
|
config.AliPublicKey != "" && config.AliNotifyURL != ""
|
|
}
|
|
|
|
func (a *AlipayPreCreateAdapter) startAttempt(ctx context.Context, paymentNo, operation string, configID uint, amount int64) (*model.IntegrationLog, error) {
|
|
resourceID := paymentNo
|
|
return a.integration.Start(ctx, integrationlog.Attempt{
|
|
Provider: constants.IntegrationProviderAlipay, Direction: constants.IntegrationDirectionOutbound,
|
|
Operation: operation, ResourceType: constants.IntegrationResourceTypeAgentRechargePayment,
|
|
ResourceID: &resourceID, ExternalID: &resourceID,
|
|
RequestSummary: map[string]any{"payment_config_id": configID, "amount": amount},
|
|
})
|
|
}
|
|
|
|
func (a *AlipayPreCreateAdapter) completeUnknown(ctx context.Context, integrationID string, startedAt time.Time, cause error) error {
|
|
_, err := a.integration.Complete(ctx, integrationID, integrationlog.Completion{
|
|
Result: constants.IntegrationResultUnknown, ProviderCode: "request_unknown", ProviderMessage: cause.Error(),
|
|
ResponseSummary: map[string]any{"success": false}, DurationMS: time.Since(startedAt).Milliseconds(),
|
|
RecoveryStrategy: "使用原支付单号主动查单,确认不存在或关闭后才允许关闭本地支付单",
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return apperrors.Wrap(apperrors.CodeTimeout, cause, "支付宝支付请求结果未知")
|
|
}
|
|
|
|
func mapAlipayTradeState(state sdkalipay.TradeStatus) string {
|
|
switch state {
|
|
case sdkalipay.TradeStatusSuccess, sdkalipay.TradeStatusFinished:
|
|
return agentrecharge.OnlinePaymentStatePaid
|
|
case sdkalipay.TradeStatusClosed:
|
|
return agentrecharge.OnlinePaymentStateClosed
|
|
case sdkalipay.TradeStatusWaitBuyerPay:
|
|
return agentrecharge.OnlinePaymentStatePending
|
|
default:
|
|
return agentrecharge.OnlinePaymentStateUnknown
|
|
}
|
|
}
|