feat(round-10): 新增出海潜客多时区最佳联系窗口算法与Stripe回调幂等性防重处理 (79套件957测试全绿)

这个提交包含在:
wikiboss
2026-09-21 01:59:11 +08:00
父节点 9f8e309a4a
当前提交 f46b24a177
共有 6 个文件被更改,包括 643 次插入 和 1 次删除

查看文件

@@ -42,6 +42,9 @@ export class TeamStripeService extends BaseService {
webhookSecret: string =
process.env.STRIPE_WEBHOOK_SECRET || 'whsec_default_team_secret';
/** Webhook 事件幂等去重表 */
processedEventIds: Set<string> = new Set();
/**
* 创建 Stripe Checkout 结算会话
* @param tenantIdOrParams 租户 ID 或完整请求参数
@@ -199,6 +202,18 @@ export class TeamStripeService extends BaseService {
async handleWebhookEvent(
event: StripeWebhookEvent
): Promise<StripeWebhookHandleResult> {
if (event.id && this.processedEventIds.has(event.id)) {
return {
success: true,
eventType: event.type,
action: 'idempotent_ignored',
ignored: true,
};
}
if (event.id) {
this.processedEventIds.add(event.id);
}
const { type, data } = event;
const object = (data?.object || {}) as Record<string, unknown>;
@@ -340,6 +355,43 @@ export class TeamStripeService extends BaseService {
};
}
case 'invoice.payment_failed': {
// 扣款失败,优雅流转为 past_due 宽限期
const metadata = (object.metadata || {}) as Record<string, string>;
const stripeSubId = (object.subscription as string) || '';
const rawTenantId = String(metadata.tenantId || '');
let targetSub: TeamSubscriptionEntity | null = null;
if (this.teamSubscriptionEntity) {
if (rawTenantId) {
targetSub = await this.teamSubscriptionEntity.findOne({
where: { tenantId: Number(rawTenantId) || 0 },
});
}
if (!targetSub && stripeSubId) {
targetSub = await this.teamSubscriptionEntity.findOne({
where: { stripeSubscriptionId: stripeSubId },
});
}
}
const tenantId: number | string =
targetSub?.tenantId ?? (Number(rawTenantId) || rawTenantId);
if (targetSub && this.teamSubscriptionEntity) {
targetSub.status = 'past_due';
await this.teamSubscriptionEntity.save(targetSub);
}
return {
success: true,
eventType: type,
tenantId,
status: 'past_due',
action: 'payment_failed',
};
}
default: {
return {
success: true,