币种汇率改变

This commit is contained in:
zhx 2026-07-03 11:34:28 +08:00
parent c6ec9fe6aa
commit 9e46ce0b02
2 changed files with 20 additions and 6 deletions

View File

@ -688,8 +688,13 @@ func applyThirdPartyPaymentRateMarkup(rateText string, markupPercent float64) (s
if err != nil || rawRate <= 0 || !validThirdPartyPaymentRateMarkup(markupPercent) { if err != nil || rawRate <= 0 || !validThirdPartyPaymentRateMarkup(markupPercent) {
return "", false return "", false
} }
// 汇率源返回的是实时 USD->本币原值;写库前统一叠加运营设置的上浮比例,再按 1 位小数向上取,保证 H5 下单看到的支付汇率不低于实时汇率。 // 汇率源返回的是实时 USD->本币原值;写库前先叠加运营上浮比例,再按金额量级向上取整,保证 H5 下单汇率不低于实时汇率。
markedRate := rawRate * (1 + markupPercent/100) markedRate := rawRate * (1 + markupPercent/100)
if markedRate > 50 {
// 高汇率币种(例如 IDR展示和支付金额都不应出现小数减去极小量只抵消 float 乘法毛刺,真实 18510.1 仍会进到 18511。
return strconv.FormatFloat(math.Ceil(markedRate-1e-9), 'f', 0, 64), true
}
// 低汇率币种仍保留原有“最多 1 位小数且向上取”的策略,避免 SAR/BHD 这类币种因强行整数化放大支付金额。
roundedRate := math.Ceil(markedRate*10-1e-9) / 10 roundedRate := math.Ceil(markedRate*10-1e-9) / 10
return trimOneDecimalRate(strconv.FormatFloat(roundedRate, 'f', 1, 64)), true return trimOneDecimalRate(strconv.FormatFloat(roundedRate, 'f', 1, 64)), true
} }

View File

@ -482,7 +482,7 @@ func TestSyncThirdPartyPaymentMethodsDefaultsToV5Pay(t *testing.T) {
} }
} }
func TestSyncThirdPartyPaymentRatesAppliesMarkupAndCeilsToOneDecimal(t *testing.T) { func TestSyncThirdPartyPaymentRatesAppliesMarkupAndCeilRules(t *testing.T) {
wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{ wallet := &mockPaymentWallet{thirdPartyChannelsResp: &walletv1.ListThirdPartyPaymentChannelsResponse{
Channels: []*walletv1.ThirdPartyPaymentChannel{{ Channels: []*walletv1.ThirdPartyPaymentChannel{{
AppCode: "lalu", AppCode: "lalu",
@ -500,12 +500,13 @@ func TestSyncThirdPartyPaymentRatesAppliesMarkupAndCeilsToOneDecimal(t *testing.
Status: "active", Status: "active",
Methods: []*walletv1.ThirdPartyPaymentMethod{ Methods: []*walletv1.ThirdPartyPaymentMethod{
{MethodId: 2310, CurrencyCode: "SAR", Status: "active"}, {MethodId: 2310, CurrencyCode: "SAR", Status: "active"},
{MethodId: 2311, CurrencyCode: "IDR", Status: "active"},
}, },
}}, }},
}} }}
handler := New(wallet, nil, nil, nil, nil) handler := New(wallet, nil, nil, nil, nil)
handler.exchangeRates = fakeExchangeRateClient{result: exchangeRateResult{ handler.exchangeRates = fakeExchangeRateClient{result: exchangeRateResult{
Rates: map[string]string{"SAR": "3.75000000", "BHD": "0.37600000"}, Rates: map[string]string{"SAR": "3.75000000", "BHD": "0.37600000", "IDR": "18510.10000000"},
SourceNames: []string{"open.er-api.com"}, SourceNames: []string{"open.er-api.com"},
}} }}
router := newPaymentHandlerTestRouter(handler) router := newPaymentHandlerTestRouter(handler)
@ -521,7 +522,7 @@ func TestSyncThirdPartyPaymentRatesAppliesMarkupAndCeilsToOneDecimal(t *testing.
if wallet.lastThirdPartyChannels == nil || !wallet.lastThirdPartyChannels.GetIncludeDisabledMethods() { if wallet.lastThirdPartyChannels == nil || !wallet.lastThirdPartyChannels.GetIncludeDisabledMethods() {
t.Fatalf("sync must read active and disabled methods: %+v", wallet.lastThirdPartyChannels) t.Fatalf("sync must read active and disabled methods: %+v", wallet.lastThirdPartyChannels)
} }
if len(wallet.updateRateRequests) != 3 { if len(wallet.updateRateRequests) != 4 {
t.Fatalf("sync should update every method with a fetched currency rate: %+v", wallet.updateRateRequests) t.Fatalf("sync should update every method with a fetched currency rate: %+v", wallet.updateRateRequests)
} }
got := map[int64]string{} got := map[int64]string{}
@ -531,18 +532,26 @@ func TestSyncThirdPartyPaymentRatesAppliesMarkupAndCeilsToOneDecimal(t *testing.
} }
got[req.GetMethodId()] = req.GetUsdToCurrencyRate() got[req.GetMethodId()] = req.GetUsdToCurrencyRate()
} }
if got[810] != "3.9" || got[811] != "0.4" || got[2310] != "3.9" { if got[810] != "3.9" || got[811] != "0.4" || got[2310] != "3.9" || got[2311] != "19066" {
t.Fatalf("synced rates mismatch: %+v", got) t.Fatalf("synced rates mismatch: %+v", got)
} }
var response adminPaymentTestResponse var response adminPaymentTestResponse
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
t.Fatalf("decode response failed: %v", err) t.Fatalf("decode response failed: %v", err)
} }
if response.Code != 0 || response.Data["updatedCount"].(float64) != 3 { if response.Code != 0 || response.Data["updatedCount"].(float64) != 4 {
t.Fatalf("sync response mismatch: %+v", response) t.Fatalf("sync response mismatch: %+v", response)
} }
} }
func TestApplyThirdPartyPaymentRateMarkupCeilsHighRateToInteger(t *testing.T) {
rate, ok := applyThirdPartyPaymentRateMarkup("18510.10000000", 0)
if !ok || rate != "18511" {
t.Fatalf("IDR high rate must ceil to integer: rate=%q ok=%v", rate, ok)
}
}
func TestSyncThirdPartyPaymentRatesRejectsInvalidMarkupPercent(t *testing.T) { func TestSyncThirdPartyPaymentRatesRejectsInvalidMarkupPercent(t *testing.T) {
wallet := &mockPaymentWallet{} wallet := &mockPaymentWallet{}
router := newPaymentHandlerTestRouter(New(wallet, nil, nil, nil, nil)) router := newPaymentHandlerTestRouter(New(wallet, nil, nil, nil, nil))