236 lines
10 KiB
Go
236 lines
10 KiB
Go
package wallet
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/pkg/xerr"
|
|
"hyapp/services/wallet-service/internal/domain/ledger"
|
|
"strings"
|
|
)
|
|
|
|
type mifaPayProviderPayloadError interface {
|
|
ProviderPayloadJSON() string
|
|
}
|
|
|
|
// CreateH5RechargeOrder 创建 H5 外部充值订单;只有回调或链上校验成功后才会入账。
|
|
func (s *Service) CreateH5RechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand) (ledger.ExternalRechargeOrder, error) {
|
|
command = normalizeCreateExternalRechargeOrderCommand(command)
|
|
if err := validateCreateExternalRechargeOrderCommand(command); err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
if s.repository == nil {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
|
}
|
|
ctx = appcode.WithContext(ctx, command.AppCode)
|
|
product, err := s.repository.GetRechargeProduct(ctx, command.AppCode, command.ProductID)
|
|
if err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
if err := validateExternalRechargeProduct(product, command); err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
switch command.ProviderCode {
|
|
case ledger.PaymentProviderUSDTTRC20:
|
|
return s.createUSDTRechargeOrder(ctx, command, product)
|
|
case ledger.PaymentProviderMifaPay:
|
|
return s.createMifaPayRechargeOrder(ctx, command, product)
|
|
case ledger.PaymentProviderV5Pay:
|
|
return s.createV5PayRechargeOrder(ctx, command, product)
|
|
default:
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.InvalidArgument, "payment provider is invalid")
|
|
}
|
|
}
|
|
|
|
func (s *Service) createUSDTRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct) (ledger.ExternalRechargeOrder, error) {
|
|
if !s.externalRecharge.USDTTRC20Enabled || s.externalRecharge.USDTTRC20Address == "" {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "usdt trc20 recharge is not configured")
|
|
}
|
|
return s.repository.CreateExternalRechargeOrder(ctx, command, product, nil, amountMicroToUSDMinor(product.AmountMicro), s.externalRecharge.USDTTRC20Address)
|
|
}
|
|
|
|
func (s *Service) createMifaPayRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct) (ledger.ExternalRechargeOrder, error) {
|
|
if s.mifaPay == nil {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "mifapay client is not configured")
|
|
}
|
|
method, err := s.repository.GetThirdPartyPaymentMethod(ctx, command.AppCode, command.PaymentMethodID)
|
|
if err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
if err := validateMifaPayMethodForOrder(method, command); err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
providerAmountMinor, err := calculateProviderAmountMinor(amountMicroToUSDMinor(product.AmountMicro), method.USDToCurrencyRate)
|
|
if err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
order, err := s.repository.CreateExternalRechargeOrder(ctx, command, product, &method, providerAmountMinor, "")
|
|
if err != nil || order.IdempotentReplay {
|
|
return order, err
|
|
}
|
|
notifyURL := firstNonEmpty(command.NotifyURL, s.externalRecharge.MifaPayNotifyURL)
|
|
returnURL := firstNonEmpty(command.ReturnURL, s.externalRecharge.MifaPayReturnURL)
|
|
if notifyURL == "" {
|
|
_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, "mifapay notify_url is not configured", "")
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "mifapay notify_url is not configured")
|
|
}
|
|
resp, err := s.mifaPay.CreateOrder(ctx, MifaPayCreateOrderRequest{
|
|
OrderID: order.OrderID,
|
|
TargetUserID: order.TargetUserID,
|
|
ProductName: order.ProductName,
|
|
CoinAmount: order.CoinAmount,
|
|
AmountMinor: order.USDMinorAmount,
|
|
CurrencyCode: order.CurrencyCode,
|
|
CountryCode: order.CountryCode,
|
|
PayWay: order.PayWay,
|
|
PayType: order.PayType,
|
|
NotifyURL: notifyURL,
|
|
ReturnURL: mifaPayProviderReturnURL(returnURL),
|
|
ClientIP: command.ClientIP,
|
|
Language: command.Language,
|
|
ProviderAmountMinor: providerAmountMinor,
|
|
PayerName: command.PayerName,
|
|
PayerAccount: command.PayerAccount,
|
|
PayerEmail: command.PayerEmail,
|
|
})
|
|
if err != nil {
|
|
_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, err.Error(), mifaPayProviderPayloadJSON(err))
|
|
return ledger.ExternalRechargeOrder{}, normalizeMifaPayOrderError(err)
|
|
}
|
|
return s.repository.MarkExternalRechargeOrderRedirected(ctx, command.AppCode, order.OrderID, resp.ProviderOrderID, resp.PayURL, resp.RawJSON)
|
|
}
|
|
|
|
func (s *Service) createV5PayRechargeOrder(ctx context.Context, command ledger.CreateExternalRechargeOrderCommand, product ledger.RechargeProduct) (ledger.ExternalRechargeOrder, error) {
|
|
if s.v5Pay == nil {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "v5pay client is not configured")
|
|
}
|
|
method, err := s.repository.GetThirdPartyPaymentMethod(ctx, command.AppCode, command.PaymentMethodID)
|
|
if err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
if err := validateV5PayMethodForOrder(method, command); err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
providerAmountMinor, err := calculateProviderAmountMinor(amountMicroToUSDMinor(product.AmountMicro), method.USDToCurrencyRate)
|
|
if err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
order, err := s.repository.CreateExternalRechargeOrder(ctx, command, product, &method, providerAmountMinor, "")
|
|
if err != nil || order.IdempotentReplay {
|
|
return order, err
|
|
}
|
|
notifyURL := firstNonEmpty(command.NotifyURL, s.externalRecharge.V5PayNotifyURL)
|
|
returnURL := firstNonEmpty(command.ReturnURL, s.externalRecharge.V5PayReturnURL)
|
|
if notifyURL == "" {
|
|
_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, "v5pay notify_url is not configured", "")
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "v5pay notify_url is not configured")
|
|
}
|
|
resp, err := s.v5Pay.CreateOrder(ctx, V5PayCreateOrderRequest{
|
|
OrderID: order.OrderID,
|
|
TargetUserID: order.TargetUserID,
|
|
ProductName: order.ProductName,
|
|
CoinAmount: order.CoinAmount,
|
|
AmountMinor: order.USDMinorAmount,
|
|
CurrencyCode: order.CurrencyCode,
|
|
CountryCode: order.CountryCode,
|
|
ProductType: order.PayType,
|
|
NotifyURL: notifyURL,
|
|
ReturnURL: v5PayProviderReturnURL(returnURL),
|
|
Language: command.Language,
|
|
ProviderAmountMinor: providerAmountMinor,
|
|
PayerName: command.PayerName,
|
|
PayerAccount: command.PayerAccount,
|
|
PayerEmail: command.PayerEmail,
|
|
})
|
|
if err != nil {
|
|
_, _ = s.repository.MarkExternalRechargeOrderFailed(ctx, command.AppCode, order.OrderID, err.Error(), providerPayloadJSON(err))
|
|
return ledger.ExternalRechargeOrder{}, normalizeExternalProviderOrderError(err, "v5pay")
|
|
}
|
|
return s.repository.MarkExternalRechargeOrderRedirected(ctx, command.AppCode, order.OrderID, resp.ProviderOrderID, resp.PayURL, resp.RawJSON)
|
|
}
|
|
|
|
func normalizeMifaPayOrderError(err error) error {
|
|
return normalizeExternalProviderOrderError(err, "mifapay")
|
|
}
|
|
|
|
func mifaPayProviderPayloadJSON(err error) string {
|
|
return providerPayloadJSON(err)
|
|
}
|
|
|
|
func normalizeExternalProviderOrderError(err error, provider string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if _, ok := xerr.As(err); ok {
|
|
return err
|
|
}
|
|
|
|
return xerr.New(xerr.Unavailable, provider+" order upstream response is invalid")
|
|
}
|
|
|
|
func providerPayloadJSON(err error) string {
|
|
var payloadErr mifaPayProviderPayloadError
|
|
if errors.As(err, &payloadErr) {
|
|
|
|
return strings.TrimSpace(payloadErr.ProviderPayloadJSON())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// SubmitH5RechargeTx 把用户提交的 tx_hash 交给 TRON 适配器校验,通过后立即入账。
|
|
func (s *Service) SubmitH5RechargeTx(ctx context.Context, command ledger.SubmitExternalRechargeTxCommand) (ledger.ExternalRechargeOrder, error) {
|
|
command.AppCode = appcode.Normalize(command.AppCode)
|
|
command.OrderID = strings.TrimSpace(command.OrderID)
|
|
command.TxHash = strings.TrimSpace(command.TxHash)
|
|
if command.OrderID == "" || command.TargetUserID <= 0 || command.TxHash == "" {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.InvalidArgument, "usdt tx command is incomplete")
|
|
}
|
|
if s.repository == nil {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
|
}
|
|
ctx = appcode.WithContext(ctx, command.AppCode)
|
|
order, err := s.repository.GetExternalRechargeOrder(ctx, command.AppCode, command.OrderID)
|
|
if err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
if order.TargetUserID != command.TargetUserID {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.PermissionDenied, "external recharge order does not belong to user")
|
|
}
|
|
if order.ProviderCode != ledger.PaymentProviderUSDTTRC20 || order.Status != ledger.ExternalRechargeStatusPending {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Conflict, "external recharge order is not pending usdt order")
|
|
}
|
|
if s.tronUSDT == nil {
|
|
return s.repository.AttachExternalRechargeTx(ctx, command, "")
|
|
}
|
|
rawJSON, err := s.tronUSDT.VerifyUSDTTransfer(ctx, command.TxHash, order.ReceiveAddress, order.USDMinorAmount)
|
|
if err != nil {
|
|
_, _ = s.repository.AttachExternalRechargeTx(ctx, command, rawJSON)
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
if _, err := s.repository.AttachExternalRechargeTx(ctx, command, rawJSON); err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
return s.repository.CreditExternalRechargeOrder(ctx, command.AppCode, command.OrderID, "", command.TxHash, rawJSON)
|
|
}
|
|
|
|
func (s *Service) GetH5RechargeOrder(ctx context.Context, appCode string, orderID string, targetUserID int64) (ledger.ExternalRechargeOrder, error) {
|
|
if s.repository == nil {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
|
}
|
|
ctx = appcode.WithContext(ctx, appcode.Normalize(appCode))
|
|
order, err := s.repository.GetExternalRechargeOrder(ctx, appcode.FromContext(ctx), strings.TrimSpace(orderID))
|
|
if err != nil {
|
|
return ledger.ExternalRechargeOrder{}, err
|
|
}
|
|
if targetUserID > 0 && order.TargetUserID != targetUserID {
|
|
return ledger.ExternalRechargeOrder{}, xerr.New(xerr.PermissionDenied, "external recharge order does not belong to user")
|
|
}
|
|
if refreshed, err := s.refreshMifaPayRechargeOrder(ctx, order); err == nil {
|
|
order = refreshed
|
|
}
|
|
if refreshed, err := s.refreshV5PayRechargeOrder(ctx, order); err == nil {
|
|
order = refreshed
|
|
}
|
|
return order, nil
|
|
}
|