mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 09:43:12 +08:00
fix: harden easypay custom method validation
This commit is contained in:
@@ -226,7 +226,7 @@ func validateProviderRequest(providerKey, name, supportedTypes string) error {
|
||||
var easyPayCustomMethodCodePattern = regexp.MustCompile(`^[a-z0-9_-]+$`)
|
||||
|
||||
type easyPayCustomMethodConfig struct {
|
||||
Type string `json:"type"`
|
||||
Type string `json:"type"`
|
||||
UpstreamType string `json:"upstreamType"`
|
||||
DisplayName string `json:"displayName"`
|
||||
}
|
||||
@@ -245,8 +245,8 @@ func validateEasyPayCustomMethods(config map[string]string, supportedTypes strin
|
||||
|
||||
customTypes := make(map[string]struct{}, len(methods))
|
||||
for _, method := range methods {
|
||||
method.Type = strings.TrimSpace(strings.ToLower(method.Type))
|
||||
method.UpstreamType = strings.TrimSpace(strings.ToLower(method.UpstreamType))
|
||||
method.Type = strings.TrimSpace(method.Type)
|
||||
method.UpstreamType = strings.TrimSpace(method.UpstreamType)
|
||||
if method.Type == "" || method.UpstreamType == "" {
|
||||
return infraerrors.BadRequest("VALIDATION_ERROR", "customMethods upstreamType is required")
|
||||
}
|
||||
@@ -266,10 +266,13 @@ func validateEasyPayCustomMethods(config map[string]string, supportedTypes strin
|
||||
}
|
||||
|
||||
for _, supportedType := range splitTypes(supportedTypes) {
|
||||
supportedType = strings.TrimSpace(strings.ToLower(supportedType))
|
||||
supportedType = strings.TrimSpace(supportedType)
|
||||
if supportedType == "" || supportedType == payment.TypeAlipay || supportedType == payment.TypeWxpay {
|
||||
continue
|
||||
}
|
||||
if !easyPayCustomMethodCodePattern.MatchString(supportedType) {
|
||||
return infraerrors.BadRequest("VALIDATION_ERROR", fmt.Sprintf("supported EasyPay custom type %s may only contain lowercase letters, digits, underscores, and hyphens", supportedType))
|
||||
}
|
||||
if _, exists := customTypes[supportedType]; !exists {
|
||||
return infraerrors.BadRequest("VALIDATION_ERROR", fmt.Sprintf("supported EasyPay custom type %s has no customMethods mapping", supportedType))
|
||||
}
|
||||
|
||||
@@ -146,6 +146,18 @@ func TestValidateEasyPayCustomMethods(t *testing.T) {
|
||||
supportedTypes: "alipay,wxpay,ldc",
|
||||
wantErr: "duplicate customMethods type",
|
||||
},
|
||||
{
|
||||
name: "custom type must already be lowercase",
|
||||
config: map[string]string{"customMethods": `[{"type":"LDC","upstreamType":"epay"}]`},
|
||||
supportedTypes: "alipay,wxpay,ldc",
|
||||
wantErr: "customMethods type may only contain lowercase letters",
|
||||
},
|
||||
{
|
||||
name: "upstream type must already be lowercase",
|
||||
config: map[string]string{"customMethods": `[{"type":"ldc","upstreamType":"ALIPAY"}]`},
|
||||
supportedTypes: "alipay,wxpay,ldc",
|
||||
wantErr: "customMethods upstreamType may only contain lowercase letters",
|
||||
},
|
||||
{
|
||||
name: "custom type uses alipay prefix",
|
||||
config: map[string]string{"customMethods": `[{"type":"alipay_hk","upstreamType":"hkpay"}]`},
|
||||
@@ -164,6 +176,12 @@ func TestValidateEasyPayCustomMethods(t *testing.T) {
|
||||
supportedTypes: "alipay,wxpay,ldc,usdt_trc20",
|
||||
wantErr: "supported EasyPay custom type usdt_trc20 has no customMethods mapping",
|
||||
},
|
||||
{
|
||||
name: "supported custom type must already be lowercase",
|
||||
config: map[string]string{"customMethods": `[{"type":"ldc","upstreamType":"epay"}]`},
|
||||
supportedTypes: "alipay,wxpay,LDC",
|
||||
wantErr: "supported EasyPay custom type LDC may only contain lowercase letters",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
||||
@@ -1119,6 +1119,7 @@ onMounted(async () => {
|
||||
paymentState.value = restored
|
||||
paymentPhase.value = 'paying'
|
||||
const restoredMethod = normalizeVisibleMethod(restored.paymentType)
|
||||
|| (visibleMethods.value[restored.paymentType] ? restored.paymentType : '')
|
||||
if (restoredMethod) {
|
||||
selectedMethod.value = restoredMethod
|
||||
}
|
||||
|
||||
@@ -326,6 +326,88 @@ describe('PaymentView subscription confirmation amounts', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('PaymentView payment recovery', () => {
|
||||
beforeEach(() => {
|
||||
vi.useRealTimers()
|
||||
routeState.path = '/purchase'
|
||||
routeState.query = {}
|
||||
routerReplace.mockReset().mockResolvedValue(undefined)
|
||||
routerPush.mockReset().mockResolvedValue(undefined)
|
||||
routerResolve.mockClear()
|
||||
createOrder.mockReset()
|
||||
refreshUser.mockReset()
|
||||
fetchActiveSubscriptions.mockReset().mockResolvedValue(undefined)
|
||||
showError.mockReset()
|
||||
showInfo.mockReset()
|
||||
showWarning.mockReset()
|
||||
bridgeInvoke.mockReset()
|
||||
window.localStorage.clear()
|
||||
;(window as Window & { WeixinJSBridge?: { invoke: typeof bridgeInvoke } }).WeixinJSBridge = undefined
|
||||
})
|
||||
|
||||
it('restores a custom EasyPay method as the selected payment method', async () => {
|
||||
getCheckoutInfo.mockResolvedValue(checkoutInfoFixture({
|
||||
methods: {
|
||||
wxpay: checkoutInfoFixture().data.methods.wxpay,
|
||||
ldc: {
|
||||
daily_limit: 0,
|
||||
daily_used: 0,
|
||||
daily_remaining: 0,
|
||||
single_min: 0,
|
||||
single_max: 0,
|
||||
fee_rate: 0,
|
||||
available: true,
|
||||
display_name: 'LDC Pay',
|
||||
},
|
||||
},
|
||||
}))
|
||||
window.localStorage.setItem(PAYMENT_RECOVERY_STORAGE_KEY, JSON.stringify({
|
||||
orderId: 888,
|
||||
amount: 66,
|
||||
qrCode: 'ldc-qr',
|
||||
expiresAt: '2099-01-01T00:10:00.000Z',
|
||||
paymentType: 'ldc',
|
||||
payUrl: 'https://pay.example.com/ldc',
|
||||
outTradeNo: 'sub2_ldc_888',
|
||||
clientSecret: '',
|
||||
intentId: '',
|
||||
currency: '',
|
||||
countryCode: '',
|
||||
paymentEnv: '',
|
||||
payAmount: 66,
|
||||
orderType: 'balance',
|
||||
paymentMode: 'popup',
|
||||
resumeToken: '',
|
||||
createdAt: Date.now(),
|
||||
}))
|
||||
|
||||
const wrapper = shallowMount(PaymentView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AppLayout: {
|
||||
template: '<div><slot /></div>',
|
||||
},
|
||||
PaymentStatusPanel: {
|
||||
template: '<button data-test="payment-done" @click="$emit(\'done\')" />',
|
||||
},
|
||||
PaymentMethodSelector: {
|
||||
props: ['selected'],
|
||||
template: '<div data-test="method-selector">{{ selected }}</div>',
|
||||
},
|
||||
Teleport: true,
|
||||
Transition: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
await flushPromises()
|
||||
await flushPromises()
|
||||
await wrapper.find('[data-test="payment-done"]').trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('[data-test="method-selector"]').text()).toBe('ldc')
|
||||
})
|
||||
})
|
||||
|
||||
describe('PaymentView WeChat JSAPI flow', () => {
|
||||
beforeEach(() => {
|
||||
routeState.path = '/purchase'
|
||||
|
||||
Reference in New Issue
Block a user