3 Commits

Author SHA1 Message Date
erio
2590145a2c fix: 易支付移动端传 device=jump 以支持微信H5支付唤起 2026-03-16 13:47:56 +08:00
erio
e2018cbcf9 fix: 渠道 PUT schema 兼容字符串类型的 models/features 字段
前端 linesToJsonString 传的是 JSON 字符串,而 .strict() schema
只接受数组/对象,导致所有渠道编辑保存失败"参数校验失败"。
移除 .strict(),models/features 改为 union 接受 string | array/record。
2026-03-16 05:33:24 +08:00
erio
a1d3f3b639 chore: 从 git 中移除 CLAUDE.md 并加入 gitignore 2026-03-15 19:23:23 +08:00
5 changed files with 23 additions and 18 deletions

3
.gitignore vendored
View File

@@ -42,3 +42,6 @@ next-env.d.ts
# third-party source code (local reference only) # third-party source code (local reference only)
/third-party /third-party
# Claude Code project instructions (contains sensitive deployment info)
CLAUDE.md

View File

@@ -193,7 +193,7 @@ describe('Payment Flow - PC/Mobile, QR/Redirect', () => {
).toBe(true); ).toBe(true);
}); });
it('EasyPay does not use isMobile flag itself (delegates to frontend)', async () => { it('EasyPay forwards isMobile to client for device=jump on mobile', async () => {
mockEasyPayCreatePayment.mockResolvedValue({ mockEasyPayCreatePayment.mockResolvedValue({
code: 1, code: 1,
trade_no: 'EP-003', trade_no: 'EP-003',
@@ -212,16 +212,14 @@ describe('Payment Flow - PC/Mobile, QR/Redirect', () => {
await provider.createPayment(request); await provider.createPayment(request);
// EasyPay client is called the same way regardless of isMobile // EasyPay client receives isMobile so it can set device=jump
expect(mockEasyPayCreatePayment).toHaveBeenCalledWith( expect(mockEasyPayCreatePayment).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
outTradeNo: 'order-ep-003', outTradeNo: 'order-ep-003',
paymentType: 'alipay', paymentType: 'alipay',
isMobile: true,
}), }),
); );
// No isMobile parameter forwarded to the underlying client
const callArgs = mockEasyPayCreatePayment.mock.calls[0][0];
expect(callArgs).not.toHaveProperty('isMobile');
}); });
}); });

View File

@@ -3,19 +3,17 @@ import { z } from 'zod';
import { verifyAdminToken, unauthorizedResponse } from '@/lib/admin-auth'; import { verifyAdminToken, unauthorizedResponse } from '@/lib/admin-auth';
import { prisma } from '@/lib/db'; import { prisma } from '@/lib/db';
const updateChannelSchema = z const updateChannelSchema = z.object({
.object({ group_id: z.number().int().positive().optional(),
group_id: z.number().int().positive().optional(), name: z.string().min(1).max(100).optional(),
name: z.string().min(1).max(100).optional(), platform: z.string().min(1).max(50).optional(),
platform: z.string().min(1).max(50).optional(), rate_multiplier: z.number().positive().optional(),
rate_multiplier: z.number().positive().optional(), description: z.string().max(500).nullable().optional(),
description: z.string().max(500).nullable().optional(), models: z.union([z.array(z.string()), z.string()]).nullable().optional(),
models: z.array(z.string()).nullable().optional(), features: z.union([z.record(z.string(), z.unknown()), z.string()]).nullable().optional(),
features: z.record(z.string(), z.unknown()).nullable().optional(), sort_order: z.number().int().min(0).optional(),
sort_order: z.number().int().min(0).optional(), enabled: z.boolean().optional(),
enabled: z.boolean().optional(), });
})
.strict();
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
if (!(await verifyAdminToken(request))) return unauthorizedResponse(request); if (!(await verifyAdminToken(request))) return unauthorizedResponse(request);

View File

@@ -9,6 +9,7 @@ export interface CreatePaymentOptions {
clientIp: string; clientIp: string;
productName: string; productName: string;
returnUrl?: string; returnUrl?: string;
isMobile?: boolean;
} }
function normalizeCidList(cid?: string): string | undefined { function normalizeCidList(cid?: string): string | undefined {
@@ -68,6 +69,10 @@ export async function createPayment(opts: CreatePaymentOptions): Promise<EasyPay
params.cid = cid; params.cid = cid;
} }
if (opts.isMobile) {
params.device = 'jump';
}
const sign = generateSign(params, env.EASY_PAY_PKEY); const sign = generateSign(params, env.EASY_PAY_PKEY);
params.sign = sign; params.sign = sign;
params.sign_type = 'MD5'; params.sign_type = 'MD5';

View File

@@ -29,6 +29,7 @@ export class EasyPayProvider implements PaymentProvider {
clientIp: request.clientIp || '127.0.0.1', clientIp: request.clientIp || '127.0.0.1',
productName: request.subject, productName: request.subject,
returnUrl: request.returnUrl, returnUrl: request.returnUrl,
isMobile: request.isMobile,
}); });
return { return {