208 lines
7.5 KiB
Go
208 lines
7.5 KiB
Go
package wallet
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/pkg/xerr"
|
|
"hyapp/services/wallet-service/internal/domain/ledger"
|
|
)
|
|
|
|
type v5PayProductSyncCountry struct {
|
|
CountryCode string
|
|
CountryName string
|
|
CurrencyCode string
|
|
SortBase int32
|
|
}
|
|
|
|
var v5PayProductSyncCountries = []v5PayProductSyncCountry{
|
|
{CountryCode: "PH", CountryName: "Philippines", CurrencyCode: "PHP", SortBase: 2000},
|
|
{CountryCode: "SA", CountryName: "Saudi Arabia", CurrencyCode: "SAR", SortBase: 2300},
|
|
{CountryCode: "EG", CountryName: "Egypt", CurrencyCode: "EGP", SortBase: 2400},
|
|
{CountryCode: "AE", CountryName: "United Arab Emirates", CurrencyCode: "AED", SortBase: 2500},
|
|
{CountryCode: "ID", CountryName: "Indonesia", CurrencyCode: "IDR", SortBase: 2600},
|
|
{CountryCode: "IN", CountryName: "India", CurrencyCode: "INR", SortBase: 2800},
|
|
{CountryCode: "PK", CountryName: "Pakistan", CurrencyCode: "PKR", SortBase: 2900},
|
|
{CountryCode: "TR", CountryName: "Turkey", CurrencyCode: "TRY", SortBase: 3000},
|
|
{CountryCode: "MX", CountryName: "Mexico", CurrencyCode: "MXN", SortBase: 3100},
|
|
{CountryCode: "VN", CountryName: "Vietnam", CurrencyCode: "VND", SortBase: 3200},
|
|
{CountryCode: "BR", CountryName: "Brazil", CurrencyCode: "BRL", SortBase: 3300},
|
|
}
|
|
|
|
// SyncThirdPartyPaymentMethods 从支付商实时拉取已开通方式并落本地配置表;当前只有 V5Pay 提供 productTypes 能力。
|
|
func (s *Service) SyncThirdPartyPaymentMethods(ctx context.Context, command ledger.ThirdPartyPaymentMethodSyncCommand) (ledger.ThirdPartyPaymentMethodSyncResult, error) {
|
|
command.AppCode = appcode.Normalize(command.AppCode)
|
|
command.ProviderCode = ledger.NormalizePaymentProvider(command.ProviderCode)
|
|
result := ledger.ThirdPartyPaymentMethodSyncResult{ProviderCode: command.ProviderCode}
|
|
if command.ProviderCode == "" {
|
|
command.ProviderCode = ledger.PaymentProviderV5Pay
|
|
result.ProviderCode = command.ProviderCode
|
|
}
|
|
if command.OperatorUserID <= 0 {
|
|
return result, xerr.New(xerr.InvalidArgument, "payment method sync operator is required")
|
|
}
|
|
if command.ProviderCode != ledger.PaymentProviderV5Pay {
|
|
return result, xerr.New(xerr.InvalidArgument, "payment provider does not support method sync")
|
|
}
|
|
if s.repository == nil {
|
|
return result, xerr.New(xerr.Unavailable, "wallet repository is not configured")
|
|
}
|
|
if s.v5Pay == nil {
|
|
return result, xerr.New(xerr.Unavailable, "v5pay client is not configured")
|
|
}
|
|
|
|
ctx = appcode.WithContext(ctx, command.AppCode)
|
|
methods, skipped, issues := s.fetchV5PayPaymentMethodsForSync(ctx)
|
|
result.ScannedCountryCount = len(v5PayProductSyncCountries)
|
|
result.FetchedCountryCount = len(v5PayProductSyncCountries) - len(issues)
|
|
result.SkippedCount = skipped
|
|
result.Issues = issues
|
|
if len(methods) == 0 {
|
|
return result, nil
|
|
}
|
|
created, updated, err := s.repository.UpsertThirdPartyPaymentMethods(ctx, command.AppCode, ledger.PaymentProviderV5Pay, methods)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
result.CreatedCount = created
|
|
result.UpdatedCount = updated
|
|
return result, nil
|
|
}
|
|
|
|
func (s *Service) fetchV5PayPaymentMethodsForSync(ctx context.Context) ([]ledger.ThirdPartyPaymentMethod, int, []ledger.ThirdPartyPaymentMethodSyncIssue) {
|
|
methods := make([]ledger.ThirdPartyPaymentMethod, 0)
|
|
issues := make([]ledger.ThirdPartyPaymentMethodSyncIssue, 0)
|
|
skipped := 0
|
|
seen := map[string]struct{}{}
|
|
for _, country := range v5PayProductSyncCountries {
|
|
// V5Pay 的 productTypes 接口必须逐国家/币种查询,没有“全部国家”接口;候选集覆盖当前种子国家和后台已开通截图里的新增国家。
|
|
products, err := s.v5Pay.ListProductTypes(ctx, V5PayProductTypesRequest{
|
|
CountryCode: country.CountryCode,
|
|
CurrencyCode: country.CurrencyCode,
|
|
})
|
|
if err != nil {
|
|
issues = append(issues, v5PayProductSyncIssue(country, err))
|
|
continue
|
|
}
|
|
for index, product := range products.PayinList {
|
|
method, ok := v5PayProductToPaymentMethod(country, product, index)
|
|
if !ok {
|
|
skipped++
|
|
continue
|
|
}
|
|
key := thirdPartyPaymentMethodSyncKey(method)
|
|
if _, exists := seen[key]; exists {
|
|
skipped++
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
methods = append(methods, method)
|
|
}
|
|
}
|
|
return methods, skipped, issues
|
|
}
|
|
|
|
func v5PayProductToPaymentMethod(country v5PayProductSyncCountry, product V5PayProductType, index int) (ledger.ThirdPartyPaymentMethod, bool) {
|
|
productType := strings.ToUpper(strings.TrimSpace(product.ProductType))
|
|
currency := strings.ToUpper(strings.TrimSpace(product.Currency))
|
|
if currency == "" {
|
|
currency = country.CurrencyCode
|
|
}
|
|
if productType == "" || product.SupportCashierMode == 0 || !strings.EqualFold(currency, country.CurrencyCode) {
|
|
return ledger.ThirdPartyPaymentMethod{}, false
|
|
}
|
|
methodName := strings.TrimSpace(product.ProductName)
|
|
if methodName == "" {
|
|
methodName = productType
|
|
}
|
|
return ledger.ThirdPartyPaymentMethod{
|
|
ProviderCode: ledger.PaymentProviderV5Pay,
|
|
CountryCode: country.CountryCode,
|
|
CountryName: country.CountryName,
|
|
CurrencyCode: currency,
|
|
PayWay: v5PayProductPayWay(product),
|
|
PayType: productType,
|
|
MethodName: methodName,
|
|
LogoURL: strings.TrimSpace(product.Logo),
|
|
Status: ledger.ThirdPartyPaymentStatusActive,
|
|
USDToCurrencyRate: "0",
|
|
SortOrder: country.SortBase + int32(index+1)*10,
|
|
}, true
|
|
}
|
|
|
|
func v5PayProductPayWay(product V5PayProductType) string {
|
|
if value := normalizeV5PayProductMode(product.ProductMode); value != "" {
|
|
return value
|
|
}
|
|
value := strings.ToUpper(strings.TrimSpace(product.ProductType + " " + product.ProductName + " " + product.Description))
|
|
switch {
|
|
case strings.Contains(value, "CARD"):
|
|
return "Card"
|
|
case strings.Contains(value, "QR"):
|
|
return "QR"
|
|
case strings.Contains(value, "BANK") || strings.Contains(value, "TRANSFER") || strings.Contains(value, "_VA") || strings.Contains(value, "_ONLINE"):
|
|
return "BankTransfer"
|
|
case strings.Contains(value, "UPI"):
|
|
return "UPI"
|
|
case strings.Contains(value, "BILL"):
|
|
return "Billing"
|
|
default:
|
|
return "Ewallet"
|
|
}
|
|
}
|
|
|
|
func normalizeV5PayProductMode(value string) string {
|
|
mode := strings.ReplaceAll(strings.TrimSpace(value), "_", " ")
|
|
mode = strings.ReplaceAll(mode, "-", " ")
|
|
switch strings.ToLower(strings.ReplaceAll(mode, " ", "")) {
|
|
case "":
|
|
return ""
|
|
case "card":
|
|
return "Card"
|
|
case "bank", "banktransfer", "bankonline", "onlinebanking":
|
|
return "BankTransfer"
|
|
case "ewallet", "wallet":
|
|
return "Ewallet"
|
|
case "qr":
|
|
return "QR"
|
|
case "upi":
|
|
return "UPI"
|
|
case "billing", "bill":
|
|
return "Billing"
|
|
default:
|
|
return strings.TrimSpace(value)
|
|
}
|
|
}
|
|
|
|
func v5PayProductSyncIssue(country v5PayProductSyncCountry, err error) ledger.ThirdPartyPaymentMethodSyncIssue {
|
|
issue := ledger.ThirdPartyPaymentMethodSyncIssue{
|
|
CountryCode: country.CountryCode,
|
|
CurrencyCode: country.CurrencyCode,
|
|
Code: "REQUEST_FAILED",
|
|
Message: err.Error(),
|
|
}
|
|
if raw := providerPayloadJSON(err); raw != "" {
|
|
var payload struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
if json.Unmarshal([]byte(raw), &payload) == nil {
|
|
if strings.TrimSpace(payload.Code) != "" {
|
|
issue.Code = strings.TrimSpace(payload.Code)
|
|
}
|
|
if strings.TrimSpace(payload.Message) != "" {
|
|
issue.Message = strings.TrimSpace(payload.Message)
|
|
}
|
|
}
|
|
}
|
|
return issue
|
|
}
|
|
|
|
func thirdPartyPaymentMethodSyncKey(method ledger.ThirdPartyPaymentMethod) string {
|
|
return strings.ToUpper(strings.TrimSpace(method.CountryCode)) + "|" +
|
|
strings.TrimSpace(method.PayWay) + "|" +
|
|
strings.ToUpper(strings.TrimSpace(method.PayType))
|
|
}
|