Skip to content

Commit f28403d

Browse files
feat: Implement subscription group plans with member management (#931)
1 parent 8cf8209 commit f28403d

1 file changed

Lines changed: 38 additions & 2 deletions

File tree

backend/services/billing/alignmentService.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@ export interface AlignmentConfirmation {
1111
appliedAt: Date;
1212
}
1313

14+
export interface GroupAlignmentConfirmation {
15+
previews: AlignmentPlanPreview[];
16+
appliedAt: Date;
17+
}
18+
1419
/**
1520
* Server-side counterpart to the mobile billing-alignment store: tracks the
16-
* 90-day re-alignment lockout per merchant/subscriber and produces alignment
17-
* previews/confirmations from the same pure domain logic.
21+
* 90-day re-alignment lockout per merchant/subscriber/group and produces
22+
* alignment previews/confirmations from the same pure domain logic.
1823
*/
1924
export class AlignmentService {
2025
private lastAlignedAt = new Map<string, Date>();
26+
private lastGroupAlignedAt = new Map<string, Date>();
2127

2228
previewAlignment(
2329
userId: string,
@@ -48,6 +54,36 @@ export class AlignmentService {
4854
this.lastAlignedAt.set(userId, now);
4955
return { preview, appliedAt: now };
5056
}
57+
58+
previewGroupAlignment(
59+
groupId: string,
60+
memberSubscriptions: Subscription[][],
61+
targetDay: AlignmentTargetDay
62+
): AlignmentPlanPreview[] {
63+
return memberSubscriptions.map(subs => buildAlignmentPlanPreview(subs, targetDay));
64+
}
65+
66+
canRealignGroup(groupId: string, now: Date = new Date()): boolean {
67+
return canRealign(this.lastGroupAlignedAt.get(groupId) ?? null, now);
68+
}
69+
70+
daysUntilNextGroupRealignment(groupId: string, now: Date = new Date()): number {
71+
return daysUntilNextRealignment(this.lastGroupAlignedAt.get(groupId) ?? null, now);
72+
}
73+
74+
confirmGroupAlignment(
75+
groupId: string,
76+
memberSubscriptions: Subscription[][],
77+
targetDay: AlignmentTargetDay,
78+
now: Date = new Date()
79+
): GroupAlignmentConfirmation {
80+
if (!this.canRealignGroup(groupId, now)) {
81+
throw new Error(`Re-alignment for group ${groupId} is locked until the 90-day cooldown elapses`);
82+
}
83+
const previews = memberSubscriptions.map(subs => buildAlignmentPlanPreview(subs, targetDay));
84+
this.lastGroupAlignedAt.set(groupId, now);
85+
return { previews, appliedAt: now };
86+
}
5187
}
5288

5389
export const alignmentService = new AlignmentService();

0 commit comments

Comments
 (0)