让代理充值复用现有网页支付能力
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m7s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m7s
微信按当前 v2/v3 配置分别生成 MWEB/H5 链接,支付宝复用 C 端 WAP 链接,并让可用支付方式基于生效配置判断。 Constraint: 支付链接统一通过 qr_content 返回,由前端渲染二维码;按要求不运行测试 Rejected: 微信 Native 与支付宝当面付 | 会引入非当前商户配置所需的额外产品开通 Confidence: high Scope-risk: moderate Directive: 微信 H5/MWEB 二维码仅承诺系统相机或外部浏览器扫码链路 Tested: 相关 Go 包编译通过;gofmt 与 git diff --check 通过 Not-tested: 按用户要求未运行自动化测试及真实支付联调
This commit is contained in:
142
internal/infrastructure/payment/alipay_wap.go
Normal file
142
internal/infrastructure/payment/alipay_wap.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"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"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// AlipayWapAdapter 使用现有 C 端支付宝能力生成 WAP 支付链接并主动查单。
|
||||
type AlipayWapAdapter struct {
|
||||
integration *integrationlog.Repository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAlipayWapAdapter 创建支付宝 WAP 支付适配器。
|
||||
func NewAlipayWapAdapter(integration *integrationlog.Repository, logger *zap.Logger) *AlipayWapAdapter {
|
||||
return &AlipayWapAdapter{integration: integration, logger: logger}
|
||||
}
|
||||
|
||||
// Available 判断配置是否完整支持支付宝 WAP 支付、验签与查单。
|
||||
func (a *AlipayWapAdapter) Available(config *model.WechatConfig) bool {
|
||||
return alipayConfigComplete(config, true)
|
||||
}
|
||||
|
||||
// CreatePaymentURL 使用与 C 端相同的手机网站支付能力生成签名 URL。
|
||||
func (a *AlipayWapAdapter) CreatePaymentURL(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentResult, error) {
|
||||
payment := &model.Payment{PaymentNo: request.PaymentNo, Amount: request.Amount, ExpireAt: &request.ExpireAt}
|
||||
payURL, err := alipaypkg.BuildWapPayURL(ctx, request.Config, payment, request.Description)
|
||||
if err != nil {
|
||||
return agentrecharge.OnlinePaymentResult{}, err
|
||||
}
|
||||
return agentrecharge.OnlinePaymentResult{QRContent: payURL}, nil
|
||||
}
|
||||
|
||||
// Query 查询支付宝 WAP 支付单状态。
|
||||
func (a *AlipayWapAdapter) Query(ctx context.Context, request agentrecharge.OnlinePaymentRequest) (agentrecharge.OnlinePaymentQueryResult, error) {
|
||||
client, err := a.queryClient(request.Config)
|
||||
if err != nil {
|
||||
return agentrecharge.OnlinePaymentQueryResult{}, err
|
||||
}
|
||||
attempt, err := a.startAttempt(ctx, request, constants.IntegrationOperationPaymentQuery, request.Config.ID, 0)
|
||||
if err != nil {
|
||||
return agentrecharge.OnlinePaymentQueryResult{}, err
|
||||
}
|
||||
startedAt := time.Now()
|
||||
response, callErr := client.TradeQuery(ctx, sdkalipay.TradeQuery{OutTradeNo: request.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 *AlipayWapAdapter) 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 *AlipayWapAdapter) 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.IntegrationProviderAlipay, 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 *AlipayWapAdapter) 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", ProviderMessage: "支付宝支付请求结果未知",
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user