You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`Service.SynchronizeSubscription(...)` in `service/sync.go` is the main entrypoint.
58
+
The public sync entrypoints are defined by `subscriptionsync.SyncService`:
59
+
60
+
-`SyncByID(...)` and `SyncByIDAndInvoiceCustomer(...)` fetch subscription state internally.
61
+
-`SyncByView(...)` and `SyncByViewAndInvoiceCustomer(...)` use an already-expanded subscription view.
62
+
- The `AndInvoiceCustomer` variants run the regular sync and then invoice pending lines for the subscription customer.
63
+
64
+
The internal orchestration lives in `service/sync.go` (`synchronizeSubscription`, `synchronizeSubscriptionAndInvoiceCustomer`) and normalizes the public input through `subscriptionReferenceOrView` from `service/ref.go`.
58
65
59
66
High-level flow:
60
-
1. Load persisted sync state and persisted billing artifacts.
61
-
2. Build target state from the subscription view.
62
-
3. Reconcile target vs persisted.
63
-
4. Apply billing patches.
64
-
5. Persist sync state.
67
+
1. Load the subscription entity, including deleted subscriptions when the ID path is used.
68
+
2. Load persisted sync state and persisted billing artifacts.
69
+
3. Build target state from the subscription view, or an empty target state when the subscription is deleted.
70
+
4. Reconcile target vs persisted.
71
+
5. Apply billing patches.
72
+
6. Persist sync state.
65
73
66
74
The important bridge boundaries are:
67
75
-`persistedstate`: current billing-side reality relevant to this subscription
68
76
-`targetstate`: expected billing-side reality derived from the subscription view
69
77
-`reconciler`: diff between the two, expressed as backend-specific patches
70
78
79
+
Deleted-subscription cleanup uses the ID path. `subscription.Service.GetView` follows the normal non-deleted read path, so code that needs to reconcile deleted subscriptions must use `List(... IncludeDeleted: true)` or the sync service's ID entrypoints rather than calling `GetView` first.
80
+
71
81
## Persisted State
72
82
73
83
`service/persistedstate` owns the billing-side read model used by sync.
@@ -100,6 +110,7 @@ Invoice loading notes:
100
110
`service/targetstate` converts a subscription view into expected billing/charge items.
101
111
102
112
Useful points:
113
+
-`BuildInput.SubscriptionView` is a pointer. A nil view represents a deleted subscription and builds an empty target state so persisted billing artifacts are removed.
103
114
-`StateItem.IsBillable()` is the first gate
104
115
-`StateItem.GetServicePeriod()` is the diff-level period source
105
116
-`StateItem.GetExpectedLine()` is invoice-specific rendering; keep direct billing assumptions isolated to places that really need invoice lines
@@ -191,6 +202,8 @@ Test suite hierarchy:
191
202
192
203
Use `setupChargesService(config)` on `SuiteBase` to rebuild the sync service with a charge-capable stack (replaces the default no-charges service).
193
204
205
+
When a billing sync test needs to exercise deleted-subscription cleanup, prefer the public ID entrypoint (`SyncByID` / `SyncByIDAndInvoiceCustomer`) so the service owns the `IncludeDeleted` lookup. Subscription-domain coverage for deleted scheduled replacements belongs under `openmeter/subscription/service/sync_test.go`; keep billing-package tests focused on billing artifacts.
- these mocks are intentionally minimal but valid enough for charge creation/advancement
@@ -216,7 +229,7 @@ Pattern for credit-only tests:
216
229
217
230
Key methods:
218
231
-`ListSubscriptions(...)` — pages through active subscriptions with their sync states
219
-
-`ReconcileSubscription(...)` — fetches subscription view and calls `SynchronizeSubscription`
232
+
-`ReconcileSubscription(...)` — calls `SyncByID` for the subscription under reconciliation
220
233
-`All(...)` — reconciles all eligible subscriptions, skipping those with no billables or whose `NextSyncAfter` is in the future (unless `Force` is set)
0 commit comments