fix payment qr fallback and admin guidance

This commit is contained in:
IanShaw027
2026-04-22 07:33:14 -07:00
parent 5551349349
commit f35e967516
20 changed files with 845 additions and 43 deletions

View File

@@ -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)
})
})

View File

@@ -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()
})
})

View File

@@ -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',
})
})

View File

@@ -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()
})
})