import assert from 'node:assert/strict'; import { loadConfig } from '../dist/config.js'; import { CleaningPayoutError, CleaningPayoutService } from '../dist/cleaning/cleaning-payout-service.js'; assert.equal(loadConfig({ NODE_ENV: 'production', QIPAI_MQTT_USERNAME: 'backend-test', QIPAI_MQTT_PASSWORD: 'not-a-real-secret', QIPAI_JWT_SECRET: 'production-cleaning-payout-secret-long-enough', QIPAI_CLEANING_PAYOUT_MOCK_ENABLED: 'true' }).payment.cleaningPayoutMockEnabled, false); assert.equal(loadConfig({ NODE_ENV: 'test', QIPAI_CLEANING_PAYOUT_MOCK_ENABLED: 'true' }).payment.cleaningPayoutMockEnabled, true); const actor = { tenantId: '7', userId: '21', access: { roles: ['TENANT_ADMIN'], capabilities: ['tenant.manage'], storeIds: [] }, traceId: 'cleaning-payout-test' }; { const harness = createHarness({ transferState: 'SUCCESS' }); const result = await harness.service.executeWechatTransfer({ ...actor, settlementId: '501', mode: 'API' }); assert.equal(result.transferState, 'SUCCESS'); assert.equal(harness.repository.paid[0].payoutChannel, 'WECHAT_TRANSFER'); assert.equal(harness.state.transferInput.amountCents, 1200); assert.equal(harness.state.transferInput.openid, 'openid-cleaner'); } { const harness = createHarness({ transferState: 'WAIT_USER_CONFIRM', packageInfo: 'package-info' }); const result = await harness.service.executeWechatTransfer({ ...actor, settlementId: '501', mode: 'API', note: 'need user confirm' }); assert.equal(result.transferState, 'WAIT_USER_CONFIRM'); assert.equal(harness.repository.pending[0].payoutState, 'WAIT_USER_CONFIRM'); assert.equal(harness.repository.pending[0].payoutPackageInfo, 'package-info'); assert.equal(harness.repository.paid.length, 0); } { const harness = createHarness({ transferState: 'FAIL', failReason: 'REAL_NAME_CHECK_FAILED' }); const result = await harness.service.executeWechatTransfer({ ...actor, settlementId: '501', mode: 'API' }); assert.equal(result.transferState, 'FAIL'); assert.equal(harness.repository.failures[0].error, 'REAL_NAME_CHECK_FAILED'); } { const harness = createHarness({ mockEnabled: true }); const result = await harness.service.executeWechatTransfer({ ...actor, settlementId: '501', mode: 'MOCK' }); assert.equal(result.transferState, 'SUCCESS'); assert.equal(harness.repository.paid[0].payoutChannel, 'WECHAT_TRANSFER_MOCK'); } { const harness = createHarness({ mockEnabled: false }); await assert.rejects( () => harness.service.executeWechatTransfer({ ...actor, settlementId: '501', mode: 'MOCK' }), (error) => error instanceof CleaningPayoutError && error.code === 'CLEANING_PAYOUT_MOCK_DISABLED' ); } function createHarness(options = {}) { const state = { transferInput: null, settlement: { id: '501', settlementNo: 'CLS-20260627-501', cleanerUserId: '31', storeId: '11', status: options.status ?? 'CONFIRMED', totalRewardCents: 1200, payoutReference: '', payoutState: '' }, account: { id: '41', platformAppId: '9', storeId: '11', merchantId: '1900000109', credentialRef: 'wechat-cleaning', authorizationStatus: options.authorizationStatus ?? 'AUTHORIZED' } }; const pool = { async execute(sql) { if (sql.includes('FROM qipai_cleaning_settlements')) { return [[state.settlement], []]; } if (sql.includes('FROM qipai_collection_accounts')) { return [[state.account], []]; } if (sql.includes('FROM qipai_user_identities')) { return [[{ openid: 'openid-cleaner' }], []]; } throw new Error(`Unexpected SQL: ${sql}`); } }; const repository = { paid: [], failures: [], pending: [], async markSettlementPaid(input) { this.paid.push(input); return { ...state.settlement, status: 'PAID', payoutReference: input.payoutReference }; }, async recordSettlementPayoutFailure(input) { this.failures.push(input); return { ...state.settlement, payoutError: input.error }; }, async recordSettlementPayoutPending(input) { this.pending.push(input); return { ...state.settlement, payoutState: input.payoutState }; } }; const client = { async createMerchantTransfer(_credential, input) { state.transferInput = input; return { outBillNo: input.outBillNo, transferBillNo: 'wx-transfer-501', state: options.transferState ?? 'SUCCESS', failReason: options.failReason ?? '', packageInfo: options.packageInfo ?? '' }; } }; const credential = { appId: 'wx-test', merchantId: '1900000109', serialNo: 'serial', privateKeyPem: 'private-key', apiV3Key: '0123456789abcdef0123456789abcdef', platformCertificates: {}, transferSceneId: '1000', transferSceneReportInfos: [{ infoType: '岗位类型', infoContent: '保洁员' }] }; return { state, repository, service: new CleaningPayoutService( pool, repository, client, new Map([['wechat-cleaning', credential]]), options.mockEnabled ?? false ) }; } console.log('PASS: M08-B cleaning payout service handles WeChat transfer states and mock gate.');