fix payment qr fallback and admin guidance
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
import PaymentProviderDialog from '@/components/payment/PaymentProviderDialog.vue'
|
||||
|
||||
const messages: Record<string, string> = {
|
||||
'admin.settings.payment.providerConfig': 'Credentials',
|
||||
'admin.settings.payment.paymentGuideTrigger': 'View payment guide',
|
||||
'admin.settings.payment.alipayGuideSummary': 'Desktop prefers QR precreate and falls back to cashier; mobile prefers WAP checkout.',
|
||||
'admin.settings.payment.wxpayGuideSummary': 'Desktop prefers Native QR; mobile routes to JSAPI or H5 based on browser context.',
|
||||
}
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key: string) => messages[key] ?? key,
|
||||
}),
|
||||
}))
|
||||
|
||||
function mountDialog() {
|
||||
return mount(PaymentProviderDialog, {
|
||||
props: {
|
||||
show: true,
|
||||
saving: false,
|
||||
editing: null,
|
||||
allKeyOptions: [
|
||||
{ value: 'alipay', label: 'Alipay' },
|
||||
{ value: 'wxpay', label: 'WeChat Pay' },
|
||||
{ value: 'stripe', label: 'Stripe' },
|
||||
],
|
||||
enabledKeyOptions: [
|
||||
{ value: 'alipay', label: 'Alipay' },
|
||||
{ value: 'wxpay', label: 'WeChat Pay' },
|
||||
],
|
||||
allPaymentTypes: [
|
||||
{ value: 'alipay', label: 'Alipay' },
|
||||
{ value: 'wxpay', label: 'WeChat Pay' },
|
||||
],
|
||||
redirectLabel: 'Redirect',
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
BaseDialog: {
|
||||
template: '<div><slot /><slot name="footer" /></div>',
|
||||
},
|
||||
Select: {
|
||||
props: ['modelValue', 'options', 'disabled'],
|
||||
template: '<div />',
|
||||
},
|
||||
ToggleSwitch: {
|
||||
template: '<div />',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('PaymentProviderDialog payment guide', () => {
|
||||
it('shows no payment guide for providers without a flow guide', () => {
|
||||
const wrapper = mountDialog()
|
||||
|
||||
expect(wrapper.text()).not.toContain(messages['admin.settings.payment.alipayGuideSummary'])
|
||||
expect(wrapper.text()).not.toContain(messages['admin.settings.payment.wxpayGuideSummary'])
|
||||
expect(wrapper.find('button[title="View payment guide"]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['alipay', 'admin.settings.payment.alipayGuideSummary'],
|
||||
['wxpay', 'admin.settings.payment.wxpayGuideSummary'],
|
||||
])('shows the payment guide summary for %s', async (providerKey, summaryKey) => {
|
||||
const wrapper = mountDialog()
|
||||
|
||||
;(wrapper.vm as unknown as { reset: (key: string) => void }).reset(providerKey)
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.text()).toContain(messages[summaryKey])
|
||||
expect(wrapper.find('button[title="View payment guide"]').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -96,4 +96,36 @@ describe('PaymentStatusPanel', () => {
|
||||
expect(wrapper.text()).toContain('payment.result.success')
|
||||
expect(wrapper.emitted('success')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('shows reopen button in QR mode when payUrl is also available', async () => {
|
||||
const openSpy = vi.spyOn(window, 'open').mockReturnValue({ closed: false } as Window)
|
||||
|
||||
const wrapper = mount(PaymentStatusPanel, {
|
||||
props: {
|
||||
orderId: 42,
|
||||
qrCode: 'https://pay.example.com/qr/42',
|
||||
payUrl: 'https://pay.example.com/session/42',
|
||||
expiresAt: '2099-01-01T12:30:00Z',
|
||||
paymentType: 'alipay',
|
||||
orderType: 'balance',
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
Icon: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('payment.qr.openPayWindow')
|
||||
|
||||
await wrapper.get('button.btn.btn-secondary.text-sm').trigger('click')
|
||||
expect(openSpy).toHaveBeenCalledWith(
|
||||
'https://pay.example.com/session/42',
|
||||
'paymentPopup',
|
||||
expect.any(String),
|
||||
)
|
||||
|
||||
openSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -190,12 +190,14 @@ describe('buildCreateOrderPayload', () => {
|
||||
paymentType: 'alipay_direct',
|
||||
orderType: 'balance',
|
||||
origin: 'https://app.example.com/',
|
||||
isMobile: true,
|
||||
isWechatBrowser: false,
|
||||
})).toEqual({
|
||||
amount: 88,
|
||||
payment_type: 'alipay',
|
||||
order_type: 'balance',
|
||||
return_url: 'https://app.example.com/payment/result',
|
||||
is_mobile: true,
|
||||
payment_source: 'hosted_redirect',
|
||||
})
|
||||
})
|
||||
@@ -207,6 +209,7 @@ describe('buildCreateOrderPayload', () => {
|
||||
orderType: 'subscription',
|
||||
planId: 7,
|
||||
origin: 'https://app.example.com',
|
||||
isMobile: false,
|
||||
isWechatBrowser: true,
|
||||
})).toEqual({
|
||||
amount: 128,
|
||||
@@ -214,6 +217,7 @@ describe('buildCreateOrderPayload', () => {
|
||||
order_type: 'subscription',
|
||||
plan_id: 7,
|
||||
return_url: 'https://app.example.com/payment/result',
|
||||
is_mobile: false,
|
||||
payment_source: 'wechat_in_app_resume',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,9 +12,9 @@ describe('PROVIDER_CONFIG_FIELDS.wxpay', () => {
|
||||
expect(findField('certSerial')?.optional).toBeFalsy()
|
||||
})
|
||||
|
||||
it('exposes optional mp and H5 metadata fields for WeChat-specific flows', () => {
|
||||
expect(findField('mpAppId')?.optional).toBe(true)
|
||||
expect(findField('h5AppName')?.optional).toBe(true)
|
||||
expect(findField('h5AppUrl')?.optional).toBe(true)
|
||||
it('only keeps the simplified visible credential set in the admin form', () => {
|
||||
expect(findField('mpAppId')).toBeUndefined()
|
||||
expect(findField('h5AppName')).toBeUndefined()
|
||||
expect(findField('h5AppUrl')).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user