feat(payment): 支持强制移动端统一使用二维码支付
This commit is contained in:
parent
8927ab091e
commit
e4c7927eff
@ -278,6 +278,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
|
|||||||
PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow,
|
PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow,
|
||||||
PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit,
|
PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit,
|
||||||
PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode,
|
PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode,
|
||||||
|
PaymentAlipayForceQRCode: paymentCfg.AlipayForceQRCode,
|
||||||
|
|
||||||
ChannelMonitorEnabled: settings.ChannelMonitorEnabled,
|
ChannelMonitorEnabled: settings.ChannelMonitorEnabled,
|
||||||
ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds,
|
ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds,
|
||||||
@ -603,6 +604,9 @@ type UpdateSettingsRequest struct {
|
|||||||
PaymentCancelRateLimitUnit *string `json:"payment_cancel_rate_limit_unit"`
|
PaymentCancelRateLimitUnit *string `json:"payment_cancel_rate_limit_unit"`
|
||||||
PaymentCancelRateLimitMode *string `json:"payment_cancel_rate_limit_window_mode"`
|
PaymentCancelRateLimitMode *string `json:"payment_cancel_rate_limit_window_mode"`
|
||||||
|
|
||||||
|
// Force Alipay mobile clients to use QR code payment instead of mobile redirect
|
||||||
|
PaymentAlipayForceQRCode *bool `json:"payment_alipay_force_qrcode"`
|
||||||
|
|
||||||
// Channel Monitor feature switch
|
// Channel Monitor feature switch
|
||||||
ChannelMonitorEnabled *bool `json:"channel_monitor_enabled"`
|
ChannelMonitorEnabled *bool `json:"channel_monitor_enabled"`
|
||||||
ChannelMonitorDefaultIntervalSeconds *int `json:"channel_monitor_default_interval_seconds"`
|
ChannelMonitorDefaultIntervalSeconds *int `json:"channel_monitor_default_interval_seconds"`
|
||||||
@ -1774,6 +1778,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
CancelRateLimitWindow: req.PaymentCancelRateLimitWindow,
|
CancelRateLimitWindow: req.PaymentCancelRateLimitWindow,
|
||||||
CancelRateLimitUnit: req.PaymentCancelRateLimitUnit,
|
CancelRateLimitUnit: req.PaymentCancelRateLimitUnit,
|
||||||
CancelRateLimitMode: req.PaymentCancelRateLimitMode,
|
CancelRateLimitMode: req.PaymentCancelRateLimitMode,
|
||||||
|
AlipayForceQRCode: req.PaymentAlipayForceQRCode,
|
||||||
}
|
}
|
||||||
if err := h.paymentConfigService.UpdatePaymentConfig(c.Request.Context(), paymentReq); err != nil {
|
if err := h.paymentConfigService.UpdatePaymentConfig(c.Request.Context(), paymentReq); err != nil {
|
||||||
response.ErrorFrom(c, err)
|
response.ErrorFrom(c, err)
|
||||||
@ -1981,6 +1986,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
|||||||
PaymentCancelRateLimitWindow: updatedPaymentCfg.CancelRateLimitWindow,
|
PaymentCancelRateLimitWindow: updatedPaymentCfg.CancelRateLimitWindow,
|
||||||
PaymentCancelRateLimitUnit: updatedPaymentCfg.CancelRateLimitUnit,
|
PaymentCancelRateLimitUnit: updatedPaymentCfg.CancelRateLimitUnit,
|
||||||
PaymentCancelRateLimitMode: updatedPaymentCfg.CancelRateLimitMode,
|
PaymentCancelRateLimitMode: updatedPaymentCfg.CancelRateLimitMode,
|
||||||
|
PaymentAlipayForceQRCode: updatedPaymentCfg.AlipayForceQRCode,
|
||||||
|
|
||||||
ChannelMonitorEnabled: updatedSettings.ChannelMonitorEnabled,
|
ChannelMonitorEnabled: updatedSettings.ChannelMonitorEnabled,
|
||||||
ChannelMonitorDefaultIntervalSeconds: updatedSettings.ChannelMonitorDefaultIntervalSeconds,
|
ChannelMonitorDefaultIntervalSeconds: updatedSettings.ChannelMonitorDefaultIntervalSeconds,
|
||||||
@ -2022,7 +2028,8 @@ func hasPaymentFields(req UpdateSettingsRequest) bool {
|
|||||||
req.PaymentProductNameSuffix != nil || req.PaymentHelpImageURL != nil ||
|
req.PaymentProductNameSuffix != nil || req.PaymentHelpImageURL != nil ||
|
||||||
req.PaymentHelpText != nil || req.PaymentCancelRateLimitEnabled != nil ||
|
req.PaymentHelpText != nil || req.PaymentCancelRateLimitEnabled != nil ||
|
||||||
req.PaymentCancelRateLimitMax != nil || req.PaymentCancelRateLimitWindow != nil ||
|
req.PaymentCancelRateLimitMax != nil || req.PaymentCancelRateLimitWindow != nil ||
|
||||||
req.PaymentCancelRateLimitUnit != nil || req.PaymentCancelRateLimitMode != nil
|
req.PaymentCancelRateLimitUnit != nil || req.PaymentCancelRateLimitMode != nil ||
|
||||||
|
req.PaymentAlipayForceQRCode != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SettingHandler) auditSettingsUpdate(c *gin.Context, before *service.SystemSettings, after *service.SystemSettings, beforeAuthSourceDefaults *service.AuthSourceDefaultSettings, afterAuthSourceDefaults *service.AuthSourceDefaultSettings, req UpdateSettingsRequest) {
|
func (h *SettingHandler) auditSettingsUpdate(c *gin.Context, before *service.SystemSettings, after *service.SystemSettings, beforeAuthSourceDefaults *service.AuthSourceDefaultSettings, afterAuthSourceDefaults *service.AuthSourceDefaultSettings, req UpdateSettingsRequest) {
|
||||||
|
|||||||
@ -218,6 +218,9 @@ type SystemSettings struct {
|
|||||||
PaymentCancelRateLimitUnit string `json:"payment_cancel_rate_limit_unit"`
|
PaymentCancelRateLimitUnit string `json:"payment_cancel_rate_limit_unit"`
|
||||||
PaymentCancelRateLimitMode string `json:"payment_cancel_rate_limit_window_mode"`
|
PaymentCancelRateLimitMode string `json:"payment_cancel_rate_limit_window_mode"`
|
||||||
|
|
||||||
|
// Force Alipay mobile clients to use QR code payment instead of mobile redirect
|
||||||
|
PaymentAlipayForceQRCode bool `json:"payment_alipay_force_qrcode"`
|
||||||
|
|
||||||
// Balance low notification
|
// Balance low notification
|
||||||
BalanceLowNotifyEnabled bool `json:"balance_low_notify_enabled"`
|
BalanceLowNotifyEnabled bool `json:"balance_low_notify_enabled"`
|
||||||
BalanceLowNotifyThreshold float64 `json:"balance_low_notify_threshold"`
|
BalanceLowNotifyThreshold float64 `json:"balance_low_notify_threshold"`
|
||||||
|
|||||||
@ -141,6 +141,7 @@ func (h *PaymentHandler) GetCheckoutInfo(c *gin.Context) {
|
|||||||
HelpText: cfg.HelpText,
|
HelpText: cfg.HelpText,
|
||||||
HelpImageURL: cfg.HelpImageURL,
|
HelpImageURL: cfg.HelpImageURL,
|
||||||
StripePublishableKey: cfg.StripePublishableKey,
|
StripePublishableKey: cfg.StripePublishableKey,
|
||||||
|
AlipayForceQRCode: cfg.AlipayForceQRCode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +156,7 @@ type checkoutInfoResponse struct {
|
|||||||
HelpText string `json:"help_text"`
|
HelpText string `json:"help_text"`
|
||||||
HelpImageURL string `json:"help_image_url"`
|
HelpImageURL string `json:"help_image_url"`
|
||||||
StripePublishableKey string `json:"stripe_publishable_key"`
|
StripePublishableKey string `json:"stripe_publishable_key"`
|
||||||
|
AlipayForceQRCode bool `json:"alipay_force_qrcode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type checkoutPlan struct {
|
type checkoutPlan struct {
|
||||||
|
|||||||
@ -858,6 +858,7 @@ func TestAPIContracts(t *testing.T) {
|
|||||||
"payment_cancel_rate_limit_window": 0,
|
"payment_cancel_rate_limit_window": 0,
|
||||||
"payment_cancel_rate_limit_unit": "",
|
"payment_cancel_rate_limit_unit": "",
|
||||||
"payment_cancel_rate_limit_window_mode": "",
|
"payment_cancel_rate_limit_window_mode": "",
|
||||||
|
"payment_alipay_force_qrcode": false,
|
||||||
"balance_low_notify_enabled": false,
|
"balance_low_notify_enabled": false,
|
||||||
"account_quota_notify_enabled": false,
|
"account_quota_notify_enabled": false,
|
||||||
"balance_low_notify_threshold": 0,
|
"balance_low_notify_threshold": 0,
|
||||||
@ -1080,6 +1081,7 @@ func TestAPIContracts(t *testing.T) {
|
|||||||
"payment_cancel_rate_limit_window": 0,
|
"payment_cancel_rate_limit_window": 0,
|
||||||
"payment_cancel_rate_limit_unit": "",
|
"payment_cancel_rate_limit_unit": "",
|
||||||
"payment_cancel_rate_limit_window_mode": "",
|
"payment_cancel_rate_limit_window_mode": "",
|
||||||
|
"payment_alipay_force_qrcode": false,
|
||||||
"balance_low_notify_enabled": false,
|
"balance_low_notify_enabled": false,
|
||||||
"account_quota_notify_enabled": false,
|
"account_quota_notify_enabled": false,
|
||||||
"balance_low_notify_threshold": 0,
|
"balance_low_notify_threshold": 0,
|
||||||
|
|||||||
@ -34,6 +34,7 @@ const (
|
|||||||
SettingCancelWindowSize = "CANCEL_RATE_LIMIT_WINDOW"
|
SettingCancelWindowSize = "CANCEL_RATE_LIMIT_WINDOW"
|
||||||
SettingCancelWindowUnit = "CANCEL_RATE_LIMIT_UNIT"
|
SettingCancelWindowUnit = "CANCEL_RATE_LIMIT_UNIT"
|
||||||
SettingCancelWindowMode = "CANCEL_RATE_LIMIT_WINDOW_MODE"
|
SettingCancelWindowMode = "CANCEL_RATE_LIMIT_WINDOW_MODE"
|
||||||
|
SettingAlipayForceQRCode = "ALIPAY_FORCE_QRCODE"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default values for payment configuration settings.
|
// Default values for payment configuration settings.
|
||||||
@ -67,6 +68,9 @@ type PaymentConfig struct {
|
|||||||
CancelRateLimitWindow int `json:"cancel_rate_limit_window"`
|
CancelRateLimitWindow int `json:"cancel_rate_limit_window"`
|
||||||
CancelRateLimitUnit string `json:"cancel_rate_limit_unit"`
|
CancelRateLimitUnit string `json:"cancel_rate_limit_unit"`
|
||||||
CancelRateLimitMode string `json:"cancel_rate_limit_window_mode"`
|
CancelRateLimitMode string `json:"cancel_rate_limit_window_mode"`
|
||||||
|
|
||||||
|
// Force Alipay mobile users to use QR code instead of mobile redirect
|
||||||
|
AlipayForceQRCode bool `json:"alipay_force_qrcode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePaymentConfigRequest contains fields to update payment configuration.
|
// UpdatePaymentConfigRequest contains fields to update payment configuration.
|
||||||
@ -94,6 +98,9 @@ type UpdatePaymentConfigRequest struct {
|
|||||||
CancelRateLimitUnit *string `json:"cancel_rate_limit_unit"`
|
CancelRateLimitUnit *string `json:"cancel_rate_limit_unit"`
|
||||||
CancelRateLimitMode *string `json:"cancel_rate_limit_window_mode"`
|
CancelRateLimitMode *string `json:"cancel_rate_limit_window_mode"`
|
||||||
|
|
||||||
|
// Force Alipay mobile users to use QR code instead of mobile redirect
|
||||||
|
AlipayForceQRCode *bool `json:"alipay_force_qrcode"`
|
||||||
|
|
||||||
VisibleMethodAlipaySource *string `json:"payment_visible_method_alipay_source"`
|
VisibleMethodAlipaySource *string `json:"payment_visible_method_alipay_source"`
|
||||||
VisibleMethodWxpaySource *string `json:"payment_visible_method_wxpay_source"`
|
VisibleMethodWxpaySource *string `json:"payment_visible_method_wxpay_source"`
|
||||||
VisibleMethodAlipayEnabled *bool `json:"payment_visible_method_alipay_enabled"`
|
VisibleMethodAlipayEnabled *bool `json:"payment_visible_method_alipay_enabled"`
|
||||||
@ -202,6 +209,7 @@ func (s *PaymentConfigService) GetPaymentConfig(ctx context.Context) (*PaymentCo
|
|||||||
SettingHelpImageURL, SettingHelpText,
|
SettingHelpImageURL, SettingHelpText,
|
||||||
SettingCancelRateLimitOn, SettingCancelRateLimitMax,
|
SettingCancelRateLimitOn, SettingCancelRateLimitMax,
|
||||||
SettingCancelWindowSize, SettingCancelWindowUnit, SettingCancelWindowMode,
|
SettingCancelWindowSize, SettingCancelWindowUnit, SettingCancelWindowMode,
|
||||||
|
SettingAlipayForceQRCode,
|
||||||
SettingPaymentVisibleMethodAlipayEnabled, SettingPaymentVisibleMethodAlipaySource,
|
SettingPaymentVisibleMethodAlipayEnabled, SettingPaymentVisibleMethodAlipaySource,
|
||||||
SettingPaymentVisibleMethodWxpayEnabled, SettingPaymentVisibleMethodWxpaySource,
|
SettingPaymentVisibleMethodWxpayEnabled, SettingPaymentVisibleMethodWxpaySource,
|
||||||
}
|
}
|
||||||
@ -237,6 +245,8 @@ func (s *PaymentConfigService) parsePaymentConfig(vals map[string]string) *Payme
|
|||||||
CancelRateLimitWindow: pcParseInt(vals[SettingCancelWindowSize], 1),
|
CancelRateLimitWindow: pcParseInt(vals[SettingCancelWindowSize], 1),
|
||||||
CancelRateLimitUnit: vals[SettingCancelWindowUnit],
|
CancelRateLimitUnit: vals[SettingCancelWindowUnit],
|
||||||
CancelRateLimitMode: vals[SettingCancelWindowMode],
|
CancelRateLimitMode: vals[SettingCancelWindowMode],
|
||||||
|
|
||||||
|
AlipayForceQRCode: vals[SettingAlipayForceQRCode] == "true",
|
||||||
}
|
}
|
||||||
if cfg.LoadBalanceStrategy == "" {
|
if cfg.LoadBalanceStrategy == "" {
|
||||||
cfg.LoadBalanceStrategy = payment.DefaultLoadBalanceStrategy
|
cfg.LoadBalanceStrategy = payment.DefaultLoadBalanceStrategy
|
||||||
@ -314,6 +324,7 @@ func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req Upda
|
|||||||
SettingCancelWindowSize: formatPositiveInt(req.CancelRateLimitWindow),
|
SettingCancelWindowSize: formatPositiveInt(req.CancelRateLimitWindow),
|
||||||
SettingCancelWindowUnit: derefStr(req.CancelRateLimitUnit),
|
SettingCancelWindowUnit: derefStr(req.CancelRateLimitUnit),
|
||||||
SettingCancelWindowMode: derefStr(req.CancelRateLimitMode),
|
SettingCancelWindowMode: derefStr(req.CancelRateLimitMode),
|
||||||
|
SettingAlipayForceQRCode: formatBoolOrEmpty(req.AlipayForceQRCode),
|
||||||
SettingPaymentVisibleMethodAlipaySource: derefStr(req.VisibleMethodAlipaySource),
|
SettingPaymentVisibleMethodAlipaySource: derefStr(req.VisibleMethodAlipaySource),
|
||||||
SettingPaymentVisibleMethodWxpaySource: derefStr(req.VisibleMethodWxpaySource),
|
SettingPaymentVisibleMethodWxpaySource: derefStr(req.VisibleMethodWxpaySource),
|
||||||
SettingPaymentVisibleMethodAlipayEnabled: formatBoolOrEmpty(req.VisibleMethodAlipayEnabled),
|
SettingPaymentVisibleMethodAlipayEnabled: formatBoolOrEmpty(req.VisibleMethodAlipayEnabled),
|
||||||
|
|||||||
@ -528,6 +528,7 @@ export interface SystemSettings {
|
|||||||
payment_cancel_rate_limit_window: number;
|
payment_cancel_rate_limit_window: number;
|
||||||
payment_cancel_rate_limit_unit: string;
|
payment_cancel_rate_limit_unit: string;
|
||||||
payment_cancel_rate_limit_window_mode: string;
|
payment_cancel_rate_limit_window_mode: string;
|
||||||
|
payment_alipay_force_qrcode?: boolean;
|
||||||
payment_visible_method_alipay_source?: string;
|
payment_visible_method_alipay_source?: string;
|
||||||
payment_visible_method_wxpay_source?: string;
|
payment_visible_method_wxpay_source?: string;
|
||||||
payment_visible_method_alipay_enabled?: boolean;
|
payment_visible_method_alipay_enabled?: boolean;
|
||||||
@ -745,6 +746,7 @@ export interface UpdateSettingsRequest {
|
|||||||
payment_cancel_rate_limit_window?: number;
|
payment_cancel_rate_limit_window?: number;
|
||||||
payment_cancel_rate_limit_unit?: string;
|
payment_cancel_rate_limit_unit?: string;
|
||||||
payment_cancel_rate_limit_window_mode?: string;
|
payment_cancel_rate_limit_window_mode?: string;
|
||||||
|
payment_alipay_force_qrcode?: boolean;
|
||||||
payment_visible_method_alipay_source?: string;
|
payment_visible_method_alipay_source?: string;
|
||||||
payment_visible_method_wxpay_source?: string;
|
payment_visible_method_wxpay_source?: string;
|
||||||
payment_visible_method_alipay_enabled?: boolean;
|
payment_visible_method_alipay_enabled?: boolean;
|
||||||
|
|||||||
@ -220,6 +220,36 @@ describe('decidePaymentLaunch', () => {
|
|||||||
expect(decision.jsapi?.appId).toBe('wx123')
|
expect(decision.jsapi?.appId).toBe('wx123')
|
||||||
expect(decision.paymentState.orderType).toBe('subscription')
|
expect(decision.paymentState.orderType).toBe('subscription')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('forces qr_waiting for mobile alipay when forceQRCode is enabled', () => {
|
||||||
|
const decision = decidePaymentLaunch(createOrderResult({
|
||||||
|
pay_url: 'https://pay.example.com/mobile/session',
|
||||||
|
qr_code: 'https://pay.example.com/qr/session',
|
||||||
|
}), {
|
||||||
|
visibleMethod: 'alipay',
|
||||||
|
orderType: 'balance',
|
||||||
|
isMobile: true,
|
||||||
|
forceQRCode: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(decision.kind).toBe('qr_waiting')
|
||||||
|
expect(decision.paymentState.qrCode).toBe('https://pay.example.com/qr/session')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not affect non-alipay methods when forceQRCode is enabled', () => {
|
||||||
|
const decision = decidePaymentLaunch(createOrderResult({
|
||||||
|
pay_url: 'https://pay.example.com/mobile/session',
|
||||||
|
qr_code: 'https://pay.example.com/qr/session',
|
||||||
|
}), {
|
||||||
|
visibleMethod: 'wxpay',
|
||||||
|
orderType: 'balance',
|
||||||
|
isMobile: true,
|
||||||
|
forceQRCode: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// wxpay mobile with pay_url still redirects
|
||||||
|
expect(decision.kind).toBe('redirect_waiting')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('buildCreateOrderPayload', () => {
|
describe('buildCreateOrderPayload', () => {
|
||||||
@ -260,6 +290,34 @@ describe('buildCreateOrderPayload', () => {
|
|||||||
payment_source: 'wechat_in_app_resume',
|
payment_source: 'wechat_in_app_resume',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('passes is_mobile: false when forceQRCode is enabled for alipay', () => {
|
||||||
|
expect(buildCreateOrderPayload({
|
||||||
|
amount: 50,
|
||||||
|
paymentType: 'alipay',
|
||||||
|
orderType: 'balance',
|
||||||
|
origin: 'https://app.example.com',
|
||||||
|
isMobile: true,
|
||||||
|
isWechatBrowser: false,
|
||||||
|
forceQRCode: true,
|
||||||
|
})).toMatchObject({
|
||||||
|
is_mobile: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('still passes is_mobile: true when forceQRCode is enabled for non-alipay methods', () => {
|
||||||
|
expect(buildCreateOrderPayload({
|
||||||
|
amount: 50,
|
||||||
|
paymentType: 'wxpay',
|
||||||
|
orderType: 'balance',
|
||||||
|
origin: 'https://app.example.com',
|
||||||
|
isMobile: true,
|
||||||
|
isWechatBrowser: false,
|
||||||
|
forceQRCode: true,
|
||||||
|
})).toMatchObject({
|
||||||
|
is_mobile: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('readPaymentRecoverySnapshot', () => {
|
describe('readPaymentRecoverySnapshot', () => {
|
||||||
|
|||||||
@ -55,6 +55,8 @@ export interface PaymentLaunchContext {
|
|||||||
orderType: OrderType
|
orderType: OrderType
|
||||||
isMobile: boolean
|
isMobile: boolean
|
||||||
isWechatBrowser?: boolean
|
isWechatBrowser?: boolean
|
||||||
|
/** When true, Alipay payments always use QR code regardless of device type */
|
||||||
|
forceQRCode?: boolean
|
||||||
now?: number
|
now?: number
|
||||||
stripePopupUrl?: string
|
stripePopupUrl?: string
|
||||||
stripeRouteUrl?: string
|
stripeRouteUrl?: string
|
||||||
@ -78,6 +80,8 @@ export interface BuildCreateOrderPayloadInput {
|
|||||||
origin?: string
|
origin?: string
|
||||||
isMobile: boolean
|
isMobile: boolean
|
||||||
isWechatBrowser: boolean
|
isWechatBrowser: boolean
|
||||||
|
/** When true, Alipay payments always use QR code (passes is_mobile: false to backend) */
|
||||||
|
forceQRCode?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateOrderFlowResult = CreateOrderResult & {
|
type CreateOrderFlowResult = CreateOrderResult & {
|
||||||
@ -111,11 +115,16 @@ export function getVisibleMethods(methods: Record<string, MethodLimit>): Record<
|
|||||||
export function buildCreateOrderPayload(input: BuildCreateOrderPayloadInput): CreateOrderRequest {
|
export function buildCreateOrderPayload(input: BuildCreateOrderPayloadInput): CreateOrderRequest {
|
||||||
const visibleMethod = normalizeVisibleMethod(input.paymentType) || input.paymentType.trim()
|
const visibleMethod = normalizeVisibleMethod(input.paymentType) || input.paymentType.trim()
|
||||||
const normalizedOrigin = (input.origin || '').trim().replace(/\/+$/, '')
|
const normalizedOrigin = (input.origin || '').trim().replace(/\/+$/, '')
|
||||||
|
// When forceQRCode is enabled for alipay, always tell the backend this is not a mobile
|
||||||
|
// request so it generates a QR code instead of a mobile-redirect URL.
|
||||||
|
const effectiveMobile = (input.forceQRCode && visibleMethod === 'alipay')
|
||||||
|
? false
|
||||||
|
: input.isMobile
|
||||||
const payload: CreateOrderRequest = {
|
const payload: CreateOrderRequest = {
|
||||||
amount: input.amount,
|
amount: input.amount,
|
||||||
payment_type: visibleMethod,
|
payment_type: visibleMethod,
|
||||||
order_type: input.orderType,
|
order_type: input.orderType,
|
||||||
is_mobile: input.isMobile,
|
is_mobile: effectiveMobile,
|
||||||
payment_source: visibleMethod === 'wxpay' && input.isWechatBrowser
|
payment_source: visibleMethod === 'wxpay' && input.isWechatBrowser
|
||||||
? 'wechat_in_app_resume'
|
? 'wechat_in_app_resume'
|
||||||
: 'hosted_redirect',
|
: 'hosted_redirect',
|
||||||
@ -190,9 +199,14 @@ export function decidePaymentLaunch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizedPaymentMode = baseState.paymentMode.trim().toLowerCase()
|
const normalizedPaymentMode = baseState.paymentMode.trim().toLowerCase()
|
||||||
|
// When forceQRCode is on for alipay, treat the device as desktop so the mobile-redirect
|
||||||
|
// branch is bypassed and we fall through to qr_waiting.
|
||||||
|
const effectiveMobile = (context.forceQRCode && visibleMethod === 'alipay')
|
||||||
|
? false
|
||||||
|
: context.isMobile
|
||||||
const prefersRedirect = normalizedPaymentMode === 'redirect'
|
const prefersRedirect = normalizedPaymentMode === 'redirect'
|
||||||
|| normalizedPaymentMode === 'popup'
|
|| normalizedPaymentMode === 'popup'
|
||||||
|| (context.isMobile && !!baseState.payUrl)
|
|| (effectiveMobile && !!baseState.payUrl)
|
||||||
const prefersQr = normalizedPaymentMode === 'qrcode'
|
const prefersQr = normalizedPaymentMode === 'qrcode'
|
||||||
|| normalizedPaymentMode === 'native'
|
|| normalizedPaymentMode === 'native'
|
||||||
|| (!prefersRedirect && !!baseState.qrCode)
|
|| (!prefersRedirect && !!baseState.qrCode)
|
||||||
|
|||||||
@ -5635,6 +5635,8 @@ export default {
|
|||||||
cancelRateLimitWindowMode: 'Window Mode',
|
cancelRateLimitWindowMode: 'Window Mode',
|
||||||
cancelRateLimitWindowModeRolling: 'Rolling',
|
cancelRateLimitWindowModeRolling: 'Rolling',
|
||||||
cancelRateLimitWindowModeFixed: 'Fixed',
|
cancelRateLimitWindowModeFixed: 'Fixed',
|
||||||
|
alipayForceQRCode: 'Force Alipay QR Code',
|
||||||
|
alipayForceQRCodeHint: 'When enabled, mobile Alipay users always see a QR code instead of being redirected to the mobile payment page',
|
||||||
helpText: 'Help Text',
|
helpText: 'Help Text',
|
||||||
helpImageUrl: 'Help Image URL',
|
helpImageUrl: 'Help Image URL',
|
||||||
manageProviders: 'Manage Providers',
|
manageProviders: 'Manage Providers',
|
||||||
|
|||||||
@ -5795,6 +5795,8 @@ export default {
|
|||||||
cancelRateLimitWindowMode: '窗口模式',
|
cancelRateLimitWindowMode: '窗口模式',
|
||||||
cancelRateLimitWindowModeRolling: '滚动',
|
cancelRateLimitWindowModeRolling: '滚动',
|
||||||
cancelRateLimitWindowModeFixed: '固定',
|
cancelRateLimitWindowModeFixed: '固定',
|
||||||
|
alipayForceQRCode: '支付宝强制二维码支付',
|
||||||
|
alipayForceQRCodeHint: '启用后,移动端支付宝用户将统一使用二维码扫码支付,不再跳转至手机网站支付',
|
||||||
helpText: '帮助文本',
|
helpText: '帮助文本',
|
||||||
helpImageUrl: '帮助图片链接',
|
helpImageUrl: '帮助图片链接',
|
||||||
manageProviders: '管理服务商',
|
manageProviders: '管理服务商',
|
||||||
|
|||||||
@ -69,6 +69,8 @@ export interface CheckoutInfoResponse {
|
|||||||
help_text: string
|
help_text: string
|
||||||
help_image_url: string
|
help_image_url: string
|
||||||
stripe_publishable_key: string
|
stripe_publishable_key: string
|
||||||
|
/** When true, Alipay payments on mobile always show the QR code instead of redirecting */
|
||||||
|
alipay_force_qrcode?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== Orders ====================
|
// ==================== Orders ====================
|
||||||
|
|||||||
@ -5843,6 +5843,38 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="input-label">{{
|
||||||
|
t("admin.settings.payment.alipayForceQRCode")
|
||||||
|
}}</label>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||||
|
form.payment_alipay_force_qrcode
|
||||||
|
? 'bg-primary-500'
|
||||||
|
: 'bg-gray-300 dark:bg-dark-600',
|
||||||
|
]"
|
||||||
|
@click="
|
||||||
|
form.payment_alipay_force_qrcode =
|
||||||
|
!form.payment_alipay_force_qrcode
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||||
|
form.payment_alipay_force_qrcode
|
||||||
|
? 'translate-x-5'
|
||||||
|
: 'translate-x-0',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400">{{
|
||||||
|
t("admin.settings.payment.alipayForceQRCodeHint")
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Row 4: Enabled payment types (provider badges like sub2apipay) -->
|
<!-- Row 4: Enabled payment types (provider badges like sub2apipay) -->
|
||||||
<div>
|
<div>
|
||||||
@ -6772,6 +6804,7 @@ const form = reactive<SettingsForm>({
|
|||||||
payment_cancel_rate_limit_window: 1,
|
payment_cancel_rate_limit_window: 1,
|
||||||
payment_cancel_rate_limit_unit: "day",
|
payment_cancel_rate_limit_unit: "day",
|
||||||
payment_cancel_rate_limit_window_mode: "rolling",
|
payment_cancel_rate_limit_window_mode: "rolling",
|
||||||
|
payment_alipay_force_qrcode: false,
|
||||||
table_default_page_size: tablePageSizeDefault,
|
table_default_page_size: tablePageSizeDefault,
|
||||||
table_page_size_options: [10, 20, 50, 100],
|
table_page_size_options: [10, 20, 50, 100],
|
||||||
custom_menu_items: [] as Array<{
|
custom_menu_items: [] as Array<{
|
||||||
@ -8036,6 +8069,7 @@ async function saveSettings() {
|
|||||||
payment_cancel_rate_limit_unit: form.payment_cancel_rate_limit_unit,
|
payment_cancel_rate_limit_unit: form.payment_cancel_rate_limit_unit,
|
||||||
payment_cancel_rate_limit_window_mode:
|
payment_cancel_rate_limit_window_mode:
|
||||||
form.payment_cancel_rate_limit_window_mode,
|
form.payment_cancel_rate_limit_window_mode,
|
||||||
|
payment_alipay_force_qrcode: form.payment_alipay_force_qrcode,
|
||||||
openai_advanced_scheduler_enabled: form.openai_advanced_scheduler_enabled,
|
openai_advanced_scheduler_enabled: form.openai_advanced_scheduler_enabled,
|
||||||
// Balance & quota notification
|
// Balance & quota notification
|
||||||
balance_low_notify_enabled: form.balance_low_notify_enabled,
|
balance_low_notify_enabled: form.balance_low_notify_enabled,
|
||||||
|
|||||||
@ -698,6 +698,7 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
|
|||||||
origin: typeof window !== 'undefined' ? window.location.origin : '',
|
origin: typeof window !== 'undefined' ? window.location.origin : '',
|
||||||
isMobile: isMobileDevice(),
|
isMobile: isMobileDevice(),
|
||||||
isWechatBrowser: typeof window !== 'undefined' && /MicroMessenger/i.test(window.navigator.userAgent),
|
isWechatBrowser: typeof window !== 'undefined' && /MicroMessenger/i.test(window.navigator.userAgent),
|
||||||
|
forceQRCode: !!(checkout.value.alipay_force_qrcode && normalizeVisibleMethod(requestType) === 'alipay'),
|
||||||
})
|
})
|
||||||
if (options.openid) {
|
if (options.openid) {
|
||||||
payload.openid = options.openid
|
payload.openid = options.openid
|
||||||
@ -745,6 +746,7 @@ async function createOrder(orderAmount: number, orderType: OrderType, planId?: n
|
|||||||
orderType,
|
orderType,
|
||||||
isMobile: isMobileDevice(),
|
isMobile: isMobileDevice(),
|
||||||
isWechatBrowser: typeof window !== 'undefined' && /MicroMessenger/i.test(window.navigator.userAgent),
|
isWechatBrowser: typeof window !== 'undefined' && /MicroMessenger/i.test(window.navigator.userAgent),
|
||||||
|
forceQRCode: !!(checkout.value.alipay_force_qrcode && visibleMethod === 'alipay'),
|
||||||
stripePopupUrl: stripeRouteUrl,
|
stripePopupUrl: stripeRouteUrl,
|
||||||
stripeRouteUrl,
|
stripeRouteUrl,
|
||||||
airwallexRouteUrl,
|
airwallexRouteUrl,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user