hyapp-server/services/wallet-service/internal/service/wallet/external_recharge_payment_methods.go

186 lines
7.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package wallet
import (
"context"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/domain/ledger"
"strings"
)
// sharedPaymentConfigAppCode 是三方收单方式的唯一国家/币种配置 owner。
// Yumi、Aslan 等 App 可以有自己的商品和用户区域,但不能复制一套 V5Pay/MiFaPay
// 国家、币种或汇率配置,否则人工收款与 H5 下单会因配置漂移得到不同的支付结果。
const sharedPaymentConfigAppCode = appcode.Default
func usesSharedPaymentConfig(providerCode string) bool {
switch ledger.NormalizePaymentProvider(providerCode) {
case ledger.PaymentProviderMifaPay, ledger.PaymentProviderV5Pay:
return true
default:
return false
}
}
// ListThirdPartyPaymentChannels 返回后台三方支付管理页需要的渠道、国家和方式树。
func (s *Service) ListThirdPartyPaymentChannels(ctx context.Context, query ledger.ListThirdPartyPaymentChannelsQuery) ([]ledger.ThirdPartyPaymentChannel, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
if strings.TrimSpace(query.Status) != "" {
query.Status = ledger.NormalizeThirdPartyPaymentStatus(query.Status)
if query.Status == "" {
return nil, xerr.New(xerr.InvalidArgument, "status is invalid")
}
}
// 指定渠道时V5Pay/MiFaPay 的国家和币种统一由 Lalu 维护。读取端始终切到
// 该配置 owner避免某个 App 没有复制支付方式时,临时链接和人工查单退化为无候选可用。
if usesSharedPaymentConfig(query.ProviderCode) {
query.AppCode = sharedPaymentConfigAppCode
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.ListThirdPartyPaymentChannels(ctx, query)
}
ctx = appcode.WithContext(ctx, query.AppCode)
channels, err := s.repository.ListThirdPartyPaymentChannels(ctx, query)
if err != nil || query.AppCode == sharedPaymentConfigAppCode || query.ProviderCode != "" {
return channels, err
}
// 未指定渠道的临时收款页面仍要同时展示两个共享渠道。保留当前 App 的非共享渠道,
// 但移除其历史副本,确保 UI 不会把同一国家/币种配置展示为两套来源。
filtered := make([]ledger.ThirdPartyPaymentChannel, 0, len(channels)+2)
for _, channel := range channels {
if !usesSharedPaymentConfig(channel.ProviderCode) {
filtered = append(filtered, channel)
}
}
for _, providerCode := range []string{ledger.PaymentProviderMifaPay, ledger.PaymentProviderV5Pay} {
sharedQuery := query
sharedQuery.AppCode = sharedPaymentConfigAppCode
sharedQuery.ProviderCode = providerCode
sharedCtx := appcode.WithContext(ctx, sharedPaymentConfigAppCode)
sharedChannels, err := s.repository.ListThirdPartyPaymentChannels(sharedCtx, sharedQuery)
if err != nil {
return nil, err
}
filtered = append(filtered, sharedChannels...)
}
return filtered, nil
}
// sharedPaymentMethod 返回支付配置 owner 的方式。订单本身仍按 command.AppCode 落库,
// 因此只共享国家/币种、产品类型和汇率,不会把 Yumi/Aslan 的账务事实写入 Lalu。
func (s *Service) sharedPaymentMethod(ctx context.Context, appCode string, providerCode string, methodID int64) (ledger.ThirdPartyPaymentMethod, error) {
configAppCode := appcode.Normalize(appCode)
if usesSharedPaymentConfig(providerCode) {
configAppCode = sharedPaymentConfigAppCode
}
return s.repository.GetThirdPartyPaymentMethod(ctx, configAppCode, methodID)
}
func (s *Service) SetThirdPartyPaymentMethodStatus(ctx context.Context, command ledger.ThirdPartyPaymentMethodStatusCommand) (ledger.ThirdPartyPaymentMethod, error) {
if command.MethodID <= 0 || command.OperatorUserID <= 0 {
return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.InvalidArgument, "payment method and operator are required")
}
if s.repository == nil {
return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.SetThirdPartyPaymentMethodStatus(ctx, command)
}
func (s *Service) UpdateThirdPartyPaymentRate(ctx context.Context, command ledger.ThirdPartyPaymentRateCommand) (ledger.ThirdPartyPaymentMethod, error) {
command.AppCode = appcode.Normalize(command.AppCode)
command.USDToCurrencyRate = strings.TrimSpace(command.USDToCurrencyRate)
if command.MethodID <= 0 || command.OperatorUserID <= 0 || !validNonNegativeDecimal(command.USDToCurrencyRate) {
return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.InvalidArgument, "payment rate command is invalid")
}
if s.repository == nil {
return ledger.ThirdPartyPaymentMethod{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.UpdateThirdPartyPaymentRate(ctx, command)
}
// ListH5RechargeOptions 只返回目标用户当前可买商品和已配置汇率的支付方式。
func (s *Service) ListH5RechargeOptions(ctx context.Context, query ledger.H5RechargeOptionsQuery) (ledger.H5RechargeOptions, error) {
query.AppCode = appcode.Normalize(query.AppCode)
query.AudienceType = ledger.NormalizeRechargeAudienceType(query.AudienceType)
query.TargetCountryCode = strings.ToUpper(strings.TrimSpace(query.TargetCountryCode))
if query.TargetUserID <= 0 || query.TargetRegionID <= 0 || query.AudienceType == "" {
return ledger.H5RechargeOptions{}, xerr.New(xerr.InvalidArgument, "h5 recharge options query is incomplete")
}
if s.repository == nil {
return ledger.H5RechargeOptions{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, query.AppCode)
options, err := s.repository.ListH5RechargeOptions(ctx, query)
if err != nil {
return ledger.H5RechargeOptions{}, err
}
if s.mifaPay == nil && s.v5Pay == nil {
options.PaymentMethods = nil
} else {
filtered := options.PaymentMethods[:0]
for _, method := range options.PaymentMethods {
if method.ProviderCode == ledger.PaymentProviderMifaPay && s.mifaPay != nil {
filtered = append(filtered, method)
}
if method.ProviderCode == ledger.PaymentProviderV5Pay && s.v5Pay != nil {
filtered = append(filtered, method)
}
}
options.PaymentMethods = s.filterV5PayRuntimeProductTypes(ctx, filtered)
}
usdtTRC20Address := s.externalRecharge.usdtTRC20AddressForApp(query.AppCode)
options.USDTTRC20Enabled = s.externalRecharge.USDTTRC20Enabled && usdtTRC20Address != ""
options.USDTTRC20Address = usdtTRC20Address
return options, nil
}
func (s *Service) filterV5PayRuntimeProductTypes(ctx context.Context, methods []ledger.ThirdPartyPaymentMethod) []ledger.ThirdPartyPaymentMethod {
if s.v5Pay == nil || len(methods) == 0 {
return methods
}
type productKey struct {
country string
currency string
}
allowedByKey := map[productKey]map[string]bool{}
filtered := methods[:0]
for _, method := range methods {
if method.ProviderCode != ledger.PaymentProviderV5Pay {
filtered = append(filtered, method)
continue
}
key := productKey{country: strings.ToUpper(strings.TrimSpace(method.CountryCode)), currency: strings.ToUpper(strings.TrimSpace(method.CurrencyCode))}
allowed, ok := allowedByKey[key]
if !ok {
allowed = map[string]bool{}
products, err := s.v5Pay.ListProductTypes(ctx, V5PayProductTypesRequest{CountryCode: key.country, CurrencyCode: key.currency})
if err == nil {
for _, product := range products.PayinList {
if product.SupportCashierMode == 0 {
continue
}
if product.Currency != "" && !strings.EqualFold(product.Currency, key.currency) {
continue
}
if productType := strings.ToUpper(strings.TrimSpace(product.ProductType)); productType != "" {
allowed[productType] = true
}
}
}
allowedByKey[key] = allowed
}
if allowed[strings.ToUpper(strings.TrimSpace(method.PayType))] {
filtered = append(filtered, method)
}
}
return filtered
}