@softure-ai/billing 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ Newest first. Each version lists what changed for an app that uses `@softure-ai/
4
4
  production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
5
5
  "Definition of done"). Versions before the first one below are described in their GitHub Releases (`billing@x.y.z`).
6
6
 
7
+ ## 0.1.7
8
+
9
+ - `sendAccessReminders` stops at a refused mail account or a spent quota (mailing's `halted`, counted in
10
+ `retryLater`) and skips a reminder whose send was interrupted long ago (mailing's `uncertain`, counted in `skipped`).
11
+
7
12
  ## 0.1.6
8
13
 
9
14
  - `module.json` names `mailing` and `ops` as optional dependencies.
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ export const BILLING_RATE_LIMIT_BUCKETS = {
25
25
  export const billing = defineModule({
26
26
  manifest: {
27
27
  id: MODULE_ID,
28
- version: "0.1.6",
28
+ version: "0.1.7",
29
29
  dependsOn: { security: "^0.1.0", auth: "^0.1.0", mailing: "^0.1.0?", ops: "^0.1.0?" },
30
30
  dbSchema: "billing",
31
31
  tables: ["entitlements", "payments", "payment_requests", "manual_grants"],
@@ -28,11 +28,17 @@ export interface AccessReminderSummary {
28
28
  readonly due: number;
29
29
  /** Mails sent now. */
30
30
  readonly sent: number;
31
- /** Mails an earlier run sent (or refused), or another run is sending right now. */
31
+ /**
32
+ * Mails an earlier run sent (or refused), another run is sending right now, or whose send was interrupted
33
+ * so long ago that it may have gone out (mailing's `uncertain`).
34
+ */
32
35
  readonly skipped: number;
33
36
  /** Mails refused for good (an invalid address, the provider rejected it). */
34
37
  readonly rejected: number;
35
- /** Mails the provider could not take now; the next run sends them. */
38
+ /**
39
+ * Mails the provider could not take now; the next run sends them. A refused account or a spent quota stops the
40
+ * run there: the mails after it are not counted and go out on the next run.
41
+ */
36
42
  readonly retryLater: number;
37
43
  }
38
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"reminder-mail.d.ts","sourceRoot":"","sources":["../../src/mailing/reminder-mail.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAA0B,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAuB,KAAK,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAG9F,gGAAgG;AAChG,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wEAAwE;AACxE,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,MAAM,CAAC,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpG,MAAM,WAAW,0BAA2B,SAAQ,0BAA0B;IAC5E,kGAAkG;IAClG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACpC,wCAAwC;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAeD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,uBAAuB,GAAG,kBAAkB,CAStH;AAMD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,EAAE,OAAO,GAAE,0BAA+B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAqBjJ"}
1
+ {"version":3,"file":"reminder-mail.d.ts","sourceRoot":"","sources":["../../src/mailing/reminder-mail.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAA0B,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEjF,OAAO,EAAuB,KAAK,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAG9F,gGAAgG;AAChG,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wEAAwE;AACxE,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,MAAM,CAAC,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpG,MAAM,WAAW,0BAA2B,SAAQ,0BAA0B;IAC5E,kGAAkG;IAClG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACpC,wCAAwC;IACxC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sBAAsB;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAeD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,uBAAuB,GAAG,kBAAkB,CAStH;AAMD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,aAAa,CAAC,SAAS,CAAC,EAAE,OAAO,GAAE,0BAA+B,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA0BjJ"}
@@ -53,10 +53,15 @@ export async function sendAccessReminders(ctx, options = {}) {
53
53
  const lastDay = formatLastDay(reminder.endsAt, ctx.config.locale, ctx.config.timezone);
54
54
  const mail = renderAccessReminderMail(messages, { kind: reminder.kind, lastDay, link });
55
55
  const outcome = await deliverOnce(ctx, { scope: getAccessReminderScope(reminder.kind, reminder.userId, reminder.endsAt), mail: { to: reminder.email, ...mail } });
56
- if (outcome.status === "done" || outcome.status === "in-flight") {
56
+ if (outcome.status === "done" || outcome.status === "in-flight" || outcome.status === "uncertain") {
57
57
  counts.skipped += 1;
58
58
  continue;
59
59
  }
60
+ if (outcome.status === "halted") {
61
+ // The account cannot send (refused key, spent quota): every other mail would fail the same way.
62
+ counts.retryLater += 1;
63
+ break;
64
+ }
60
65
  if (outcome.status === "sent")
61
66
  counts.sent += 1;
62
67
  else if (outcome.status === "rejected")
@@ -1 +1 @@
1
- {"version":3,"file":"reminder-mail.js","sourceRoot":"","sources":["../../src/mailing/reminder-mail.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reminder-mail.js","sourceRoot":"","sources":["../../src/mailing/reminder-mail.ts"],"names":[],"mappings":"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"}
package/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "billing",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "dependsOn": { "security": "^0.1.0", "auth": "^0.1.0", "mailing": "^0.1.0?", "ops": "^0.1.0?" },
5
5
  "dbSchema": "billing",
6
6
  "tables": ["entitlements", "payments", "payment_requests", "manual_grants"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softure-ai/billing",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "SOFTURE AI billing module: trial, paid and read-only entitlements from a pure state machine, billing.entitlements apart from auth.users, a write guard for server actions, access badge and notice components, a privacy contributor, reminder mail before access ends over @softure-ai/mailing, manual invoice requests handed to the owner once and expired after a configurable age, grant-plan and revoke-grant ops scripts, and import-entitlements and pin-trials ops scripts for accounts that exist before billing is turned on.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -28,7 +28,7 @@ export const BILLING_RATE_LIMIT_BUCKETS = {
28
28
  export const billing = defineModule({
29
29
  manifest: {
30
30
  id: MODULE_ID,
31
- version: "0.1.6",
31
+ version: "0.1.7",
32
32
  dependsOn: { security: "^0.1.0", auth: "^0.1.0", mailing: "^0.1.0?", ops: "^0.1.0?" },
33
33
  dbSchema: "billing",
34
34
  tables: ["entitlements", "payments", "payment_requests", "manual_grants"],
@@ -38,11 +38,17 @@ export interface AccessReminderSummary {
38
38
  readonly due: number;
39
39
  /** Mails sent now. */
40
40
  readonly sent: number;
41
- /** Mails an earlier run sent (or refused), or another run is sending right now. */
41
+ /**
42
+ * Mails an earlier run sent (or refused), another run is sending right now, or whose send was interrupted
43
+ * so long ago that it may have gone out (mailing's `uncertain`).
44
+ */
42
45
  readonly skipped: number;
43
46
  /** Mails refused for good (an invalid address, the provider rejected it). */
44
47
  readonly rejected: number;
45
- /** Mails the provider could not take now; the next run sends them. */
48
+ /**
49
+ * Mails the provider could not take now; the next run sends them. A refused account or a spent quota stops the
50
+ * run there: the mails after it are not counted and go out on the next run.
51
+ */
46
52
  readonly retryLater: number;
47
53
  }
48
54
 
@@ -95,10 +101,15 @@ export async function sendAccessReminders(ctx: ModuleContext<Queryable>, options
95
101
  const lastDay = formatLastDay(reminder.endsAt, ctx.config.locale, ctx.config.timezone);
96
102
  const mail = renderAccessReminderMail(messages, { kind: reminder.kind, lastDay, link });
97
103
  const outcome = await deliverOnce(ctx, { scope: getAccessReminderScope(reminder.kind, reminder.userId, reminder.endsAt), mail: { to: reminder.email, ...mail } });
98
- if (outcome.status === "done" || outcome.status === "in-flight") {
104
+ if (outcome.status === "done" || outcome.status === "in-flight" || outcome.status === "uncertain") {
99
105
  counts.skipped += 1;
100
106
  continue;
101
107
  }
108
+ if (outcome.status === "halted") {
109
+ // The account cannot send (refused key, spent quota): every other mail would fail the same way.
110
+ counts.retryLater += 1;
111
+ break;
112
+ }
102
113
  if (outcome.status === "sent") counts.sent += 1;
103
114
  else if (outcome.status === "rejected") counts.rejected += 1;
104
115
  else counts.retryLater += 1;