feat(M08-B): 接入保洁微信转账适配器
This commit is contained in:
@@ -10,6 +10,11 @@ import {
|
||||
type CleaningActor,
|
||||
type CleaningTaskRepository
|
||||
} from '../cleaning/cleaning-task-repository.js';
|
||||
import {
|
||||
CleaningPayoutError,
|
||||
type CleaningPayoutService
|
||||
} from '../cleaning/cleaning-payout-service.js';
|
||||
import { WechatPayError } from '../payments/wechat-pay-client.js';
|
||||
|
||||
const listSchema = z.object({
|
||||
page: z.coerce.number().int().min(1).default(1),
|
||||
@@ -65,6 +70,10 @@ const settlementPayoutFailureSchema = z.object({
|
||||
error: z.string().trim().min(1).max(512),
|
||||
note: z.string().trim().max(512).optional()
|
||||
}).strict();
|
||||
const settlementWechatTransferSchema = z.object({
|
||||
mode: z.enum(['API', 'MOCK']).default('API'),
|
||||
note: z.string().trim().max(512).optional()
|
||||
}).strict();
|
||||
const reclaimSchema = z.object({
|
||||
olderThanMinutes: z.coerce.number().int().min(5).max(1440).default(60),
|
||||
limit: z.coerce.number().int().min(1).max(100).default(20)
|
||||
@@ -78,6 +87,7 @@ export interface CleaningRouteOptions {
|
||||
| 'recordSettlementPayoutFailure' | 'reclaimTimeouts'
|
||||
| 'assertCanUploadPhoto' | 'stats'>;
|
||||
mediaStorage?: MediaStorage;
|
||||
payoutService?: Pick<CleaningPayoutService, 'executeWechatTransfer'>;
|
||||
authRepository: Pick<AuthRepository, 'validateSession'>;
|
||||
accessControl: { getAccessProfile(tenantId: string, userId: string): Promise<AccessProfile> };
|
||||
jwtSecret: string;
|
||||
@@ -424,6 +434,29 @@ export async function registerCleaningRoutes(
|
||||
traceId: request.traceId
|
||||
}));
|
||||
});
|
||||
|
||||
app.post('/admin-api/cleaning/settlements/:settlementId/wechat-transfer', async (request, reply) => {
|
||||
if (!options.payoutService) return reply.status(501).send({
|
||||
code: 'CLEANING_PAYOUT_UNAVAILABLE',
|
||||
message: 'Cleaning payout service is not configured.',
|
||||
traceId: request.traceId
|
||||
});
|
||||
const actor = await requireActor(request, reply, options, 'write');
|
||||
if (!actor) return;
|
||||
const params = settlementParamsSchema.safeParse(request.params);
|
||||
const body = settlementWechatTransferSchema.safeParse(request.body ?? {});
|
||||
if (!params.success || !body.success) return invalid(reply, request.traceId);
|
||||
return handle(reply, request.traceId, async () => ({
|
||||
code: 0,
|
||||
data: await options.payoutService!.executeWechatTransfer({
|
||||
...actor,
|
||||
settlementId: params.data.settlementId,
|
||||
mode: body.data.mode,
|
||||
note: body.data.note
|
||||
}),
|
||||
traceId: request.traceId
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
async function requireActor(
|
||||
@@ -474,11 +507,14 @@ async function handle(reply: FastifyReply, traceId: string, work: () => Promise<
|
||||
traceId
|
||||
});
|
||||
}
|
||||
if (!(error instanceof CleaningTaskError)) throw error;
|
||||
const statusCode = error.code === 'CLEANING_TASK_FORBIDDEN'
|
||||
|| error.code === 'CLEANING_SETTLEMENT_FORBIDDEN' ? 403 : 409;
|
||||
if (!(error instanceof CleaningTaskError)
|
||||
&& !(error instanceof CleaningPayoutError)
|
||||
&& !(error instanceof WechatPayError)) throw error;
|
||||
const code = error.code;
|
||||
const statusCode = code === 'CLEANING_TASK_FORBIDDEN'
|
||||
|| code === 'CLEANING_SETTLEMENT_FORBIDDEN' ? 403 : 409;
|
||||
return reply.status(statusCode).send({
|
||||
code: error.code,
|
||||
code,
|
||||
message: 'The cleaning task request cannot be completed.',
|
||||
traceId
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user