@softure-ai/billing 0.1.7 → 0.1.8

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +75 -16
  3. package/dist/contract.d.ts +19 -0
  4. package/dist/contract.d.ts.map +1 -1
  5. package/dist/contract.js +1 -0
  6. package/dist/contract.js.map +1 -1
  7. package/dist/fields.d.ts +2 -0
  8. package/dist/fields.d.ts.map +1 -1
  9. package/dist/fields.js +2 -0
  10. package/dist/fields.js.map +1 -1
  11. package/dist/index.d.ts +19 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +4 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/messages/en.d.ts +17 -0
  16. package/dist/messages/en.d.ts.map +1 -1
  17. package/dist/messages/en.js +17 -0
  18. package/dist/messages/en.js.map +1 -1
  19. package/dist/messages/index.d.ts +36 -2
  20. package/dist/messages/index.d.ts.map +1 -1
  21. package/dist/messages/index.js.map +1 -1
  22. package/dist/messages/pl.d.ts.map +1 -1
  23. package/dist/messages/pl.js +17 -0
  24. package/dist/messages/pl.js.map +1 -1
  25. package/dist/next/actions.d.ts +8 -1
  26. package/dist/next/actions.d.ts.map +1 -1
  27. package/dist/next/actions.js +37 -1
  28. package/dist/next/actions.js.map +1 -1
  29. package/dist/next/index.d.ts +1 -1
  30. package/dist/next/index.d.ts.map +1 -1
  31. package/dist/next/index.js +1 -1
  32. package/dist/next/index.js.map +1 -1
  33. package/dist/next/pages.d.ts +13 -3
  34. package/dist/next/pages.d.ts.map +1 -1
  35. package/dist/next/pages.js +25 -8
  36. package/dist/next/pages.js.map +1 -1
  37. package/dist/schema.d.ts +109 -0
  38. package/dist/schema.d.ts.map +1 -1
  39. package/dist/schema.js +10 -2
  40. package/dist/schema.js.map +1 -1
  41. package/dist/scripts/entitlement-scripts.d.ts +2 -0
  42. package/dist/scripts/entitlement-scripts.d.ts.map +1 -1
  43. package/dist/scripts/entitlement-scripts.js +17 -5
  44. package/dist/scripts/entitlement-scripts.js.map +1 -1
  45. package/dist/server/entitlements.d.ts +23 -6
  46. package/dist/server/entitlements.d.ts.map +1 -1
  47. package/dist/server/entitlements.js +37 -6
  48. package/dist/server/entitlements.js.map +1 -1
  49. package/dist/server/grants.d.ts +13 -2
  50. package/dist/server/grants.d.ts.map +1 -1
  51. package/dist/server/grants.js +11 -2
  52. package/dist/server/grants.js.map +1 -1
  53. package/dist/server/health.d.ts.map +1 -1
  54. package/dist/server/health.js +2 -1
  55. package/dist/server/health.js.map +1 -1
  56. package/dist/server/index.d.ts +3 -2
  57. package/dist/server/index.d.ts.map +1 -1
  58. package/dist/server/index.js +1 -0
  59. package/dist/server/index.js.map +1 -1
  60. package/dist/server/privacy.d.ts +9 -0
  61. package/dist/server/privacy.d.ts.map +1 -1
  62. package/dist/server/privacy.js +19 -6
  63. package/dist/server/privacy.js.map +1 -1
  64. package/dist/server/trials.d.ts +22 -0
  65. package/dist/server/trials.d.ts.map +1 -0
  66. package/dist/server/trials.js +60 -0
  67. package/dist/server/trials.js.map +1 -0
  68. package/dist/ui/index.d.ts +1 -0
  69. package/dist/ui/index.d.ts.map +1 -1
  70. package/dist/ui/index.js +1 -0
  71. package/dist/ui/index.js.map +1 -1
  72. package/dist/ui/trial-form.d.ts +16 -0
  73. package/dist/ui/trial-form.d.ts.map +1 -0
  74. package/dist/ui/trial-form.js +25 -0
  75. package/dist/ui/trial-form.js.map +1 -0
  76. package/migrations/0010_record_trial_extensions_and_index_foreign_keys.sql +27 -0
  77. package/module.json +2 -2
  78. package/package.json +1 -1
  79. package/src/contract.ts +28 -0
  80. package/src/fields.ts +2 -0
  81. package/src/index.ts +7 -3
  82. package/src/messages/en.ts +17 -0
  83. package/src/messages/index.ts +2 -2
  84. package/src/messages/pl.ts +17 -0
  85. package/src/next/actions.ts +35 -2
  86. package/src/next/index.ts +9 -1
  87. package/src/next/pages.tsx +41 -6
  88. package/src/schema.ts +11 -2
  89. package/src/scripts/entitlement-scripts.ts +18 -5
  90. package/src/server/entitlements.ts +60 -6
  91. package/src/server/grants.ts +24 -3
  92. package/src/server/health.ts +2 -1
  93. package/src/server/index.ts +3 -0
  94. package/src/server/privacy.ts +29 -6
  95. package/src/server/trials.ts +70 -0
  96. package/src/ui/index.ts +1 -0
  97. package/src/ui/trial-form.tsx +80 -0
@@ -1,12 +1,13 @@
1
- export { changeEntitlement, checkWriteAccess, findEntitlementRecord, getDefaultRecord, getEntitlement, importEntitlement, pinDerivedTrials, type BillingContext, type EntitlementEventResolver, type ImportEntitlementInput, } from "./entitlements.js";
1
+ export { changeEntitlement, checkWriteAccess, findEntitlementRecord, getDefaultRecord, getEntitlement, importEntitlement, pinDerivedTrials, type BillingContext, type EntitlementEventResolver, type ImportEntitlementError, type ImportEntitlementInput, } from "./entitlements.js";
2
2
  export { ACCOUNT_HISTORY_LIMIT, getAccountHistory, grantPaymentRequest, grantPlanManually, revokeManualGrant, type AccountHistoryEntry, type GrantPaymentRequestInput, type GrantPlanManuallyError, type GrantPlanManuallyInput, type ManualGrantResult, type RevokeManualGrantInput, } from "./grants.js";
3
3
  export { checkBillingTables } from "./health.js";
4
+ export { extendTrialManually, type ExtendTrialManuallyInput, type TrialExtensionResult } from "./trials.js";
4
5
  export { findAccessReminders, type AccessReminderDue, type FindAccessRemindersOptions } from "./reminders.js";
5
6
  export { failRefund, receiveStripeWebhook, recordPayment, refundPayment, STRIPE_PROVIDER, type FailRefundInput, type PaymentOutcome, type ReceiveStripeWebhookInput, type RecordPaymentError, type RecordPaymentInput, type RefundPaymentInput, type StripeWebhookReceipt, } from "./payments.js";
6
7
  export { getBillingMessages, getBillingModule, getBillingOptions, getBillingRoutes, getEntitlementPolicy, type BillingRoutes, } from "./options.js";
7
8
  export { findAccountByEmail, findAccountById, getBillingPlans, getPaymentProvider, grantPlan, startPayment, type StartPaymentInput, type StartPaymentResult, } from "./plans.js";
8
9
  export { parseInvoiceDetails, type InvoiceDetailsError, type InvoiceInput } from "../invoice.js";
9
- export { billingPrivacyContributor, deleteBillingUserData, exportBillingUserData, type BillingManualGrantData, type BillingPaymentData, type BillingPaymentRequestData, type BillingRefundFailureData, type BillingUserData, } from "./privacy.js";
10
+ export { billingPrivacyContributor, deleteBillingUserData, exportBillingUserData, type BillingManualGrantData, type BillingPaymentData, type BillingPaymentRequestData, type BillingRefundFailureData, type BillingTrialExtensionData, type BillingUserData, } from "./privacy.js";
10
11
  export { dismissPaymentRequest, expireStaleRequests, listOpenRequests, OPEN_REQUESTS_LIMIT, recordPaymentRequest, type ExpiredRequestsSummary, type OpenPaymentRequest, type RecordPaymentRequestInput, } from "./requests.js";
11
12
  export { assertAdminRoleDeclared, assertPaymentSetup, PAYMENT_BUCKET } from "./setup.js";
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,KAAK,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC9G,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,GAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC5G,OAAO,EAAE,mBAAmB,EAAE,KAAK,iBAAiB,EAAE,KAAK,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC9G,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,yBAAyB,EAC9B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,GAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
@@ -3,6 +3,7 @@
3
3
  export { changeEntitlement, checkWriteAccess, findEntitlementRecord, getDefaultRecord, getEntitlement, importEntitlement, pinDerivedTrials, } from "./entitlements.js";
4
4
  export { ACCOUNT_HISTORY_LIMIT, getAccountHistory, grantPaymentRequest, grantPlanManually, revokeManualGrant, } from "./grants.js";
5
5
  export { checkBillingTables } from "./health.js";
6
+ export { extendTrialManually } from "./trials.js";
6
7
  export { findAccessReminders } from "./reminders.js";
7
8
  export { failRefund, receiveStripeWebhook, recordPayment, refundPayment, STRIPE_PROVIDER, } from "./payments.js";
8
9
  export { getBillingMessages, getBillingModule, getBillingOptions, getBillingRoutes, getEntitlementPolicy, } from "./options.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,kGAAkG;AAClG,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GAIjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,GAOlB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAA2D,MAAM,gBAAgB,CAAC;AAC9G,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,GAQhB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GAErB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,YAAY,GAGb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAA+C,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,GAMtB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,GAIrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,kGAAkG;AAClG,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GAKjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,GAOlB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAA4D,MAAM,aAAa,CAAC;AAC5G,OAAO,EAAE,mBAAmB,EAA2D,MAAM,gBAAgB,CAAC;AAC9G,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,GAQhB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,oBAAoB,GAErB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,SAAS,EACT,YAAY,GAGb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,mBAAmB,EAA+C,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,GAOtB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,GAIrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
@@ -53,6 +53,13 @@ export interface BillingManualGrantData {
53
53
  readonly amount: number | null;
54
54
  readonly currency: string | null;
55
55
  }
56
+ /** One trial extended by hand, as it appears in an export. */
57
+ export interface BillingTrialExtensionData {
58
+ readonly extendedAt: Date;
59
+ /** The trial end before and after the extension (first instants no longer covered). */
60
+ readonly previousEndsAt: Date;
61
+ readonly endsAt: Date;
62
+ }
56
63
  /** What billing holds about one user, as it appears in their export. */
57
64
  export interface BillingUserData {
58
65
  readonly entitlement: {
@@ -70,6 +77,8 @@ export interface BillingUserData {
70
77
  readonly paymentRequests: readonly BillingPaymentRequestData[];
71
78
  /** Oldest first. */
72
79
  readonly manualGrants: readonly BillingManualGrantData[];
80
+ /** Oldest first. */
81
+ readonly trialExtensions: readonly BillingTrialExtensionData[];
73
82
  }
74
83
  export declare function exportBillingUserData(context: ModuleContext, userId: string): Promise<Ok<BillingUserData>>;
75
84
  export declare function deleteBillingUserData(context: ModuleContext, userId: string): Promise<Ok<undefined>>;
@@ -1 +1 @@
1
- {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"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"}
@@ -1,15 +1,15 @@
1
1
  // The billing part of a GDPR export and deletion (`@softure-ai/privacy`): the account's entitlement
2
2
  // row, its provider payments and their failed refunds, its invoice requests and the plans granted
3
- // to it by hand. An account
3
+ // to it by hand, and the trials extended for it by hand. An account
4
4
  // without an entitlement row has no stored entitlement (its trial is derived from the account). The
5
- // provider keeps its own records of the payments. Which admin granted or revoked a plan is the
6
- // admin's data, not the account's, and is left out of the export.
5
+ // provider keeps its own records of the payments. Which admin granted or revoked a plan, or
6
+ // extended a trial, is the admin's data, not the account's, and is left out of the export.
7
7
  import { users } from "@softure-ai/auth";
8
8
  import { ok } from "@softure-ai/core";
9
9
  import { asc, eq } from "drizzle-orm";
10
- import { entitlements, manualGrants, paymentRequests, payments, refundFailures } from "../schema.js";
10
+ import { entitlements, manualGrants, paymentRequests, payments, refundFailures, trialExtensions } from "../schema.js";
11
11
  import { isUserId } from "./user-id.js";
12
- const EMPTY_USER_DATA = { entitlement: null, payments: [], refundFailures: [], paymentRequests: [], manualGrants: [] };
12
+ const EMPTY_USER_DATA = { entitlement: null, payments: [], refundFailures: [], paymentRequests: [], manualGrants: [], trialExtensions: [] };
13
13
  export async function exportBillingUserData(context, userId) {
14
14
  if (!isUserId(userId))
15
15
  return ok(EMPTY_USER_DATA);
@@ -86,7 +86,19 @@ export async function exportBillingUserData(context, userId) {
86
86
  .from(manualGrants)
87
87
  .where(eq(manualGrants.userId, userId))
88
88
  .orderBy(asc(manualGrants.grantedAt), asc(manualGrants.id));
89
- return ok({ entitlement: row ?? null, payments: paymentRows, refundFailures: failureRows, paymentRequests: requestRows, manualGrants: grantRows });
89
+ const extensionRows = await db
90
+ .select({ extendedAt: trialExtensions.extendedAt, previousEndsAt: trialExtensions.previousEndsAt, endsAt: trialExtensions.endsAt })
91
+ .from(trialExtensions)
92
+ .where(eq(trialExtensions.userId, userId))
93
+ .orderBy(asc(trialExtensions.extendedAt), asc(trialExtensions.id));
94
+ return ok({
95
+ entitlement: row ?? null,
96
+ payments: paymentRows,
97
+ refundFailures: failureRows,
98
+ paymentRequests: requestRows,
99
+ manualGrants: grantRows,
100
+ trialExtensions: extensionRows,
101
+ });
90
102
  }
91
103
  export async function deleteBillingUserData(context, userId) {
92
104
  if (!isUserId(userId))
@@ -101,6 +113,7 @@ export async function deleteBillingUserData(context, userId) {
101
113
  // Grants first: they reference the requests they answered.
102
114
  await db.delete(manualGrants).where(eq(manualGrants.userId, userId));
103
115
  await db.delete(paymentRequests).where(eq(paymentRequests.userId, userId));
116
+ await db.delete(trialExtensions).where(eq(trialExtensions.userId, userId));
104
117
  return ok();
105
118
  }
106
119
  export const billingPrivacyContributor = {
@@ -1 +1 @@
1
- {"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,kGAAkG;AAClG,4BAA4B;AAC5B,oGAAoG;AACpG,+FAA+F;AAC/F,kEAAkE;AAClE,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,EAAE,EAAwD,MAAM,kBAAkB,CAAC;AAE5F,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACrG,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AA+ExC,MAAM,eAAe,GAAoB,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC;AAExI,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAAsB,EAAE,MAAc;IAChF,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC,eAAe,CAAC,CAAC;IAClD,2FAA2F;IAC3F,MAAM,EAAE,GAAG,OAAO,CAAC,EAAe,CAAC;IACnC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE;SACnB,MAAM,CAAC;QACN,WAAW,EAAE,YAAY,CAAC,WAAW;QACrC,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,UAAU,EAAE,YAAY,CAAC,UAAU;QACnC,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,SAAS,EAAE,YAAY,CAAC,SAAS;KAClC,CAAC;SACD,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,MAAM,EAAE;SACzB,MAAM,CAAC;QACN,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,cAAc,EAAE,QAAQ,CAAC,cAAc;QACvC,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,WAAW,EAAE,QAAQ,CAAC,WAAW;QACjC,YAAY,EAAE,QAAQ,CAAC,YAAY;KACpC,CAAC;SACD,IAAI,CAAC,QAAQ,CAAC;SACd,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SAClC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,MAAM,EAAE;SACzB,MAAM,CAAC;QACN,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,QAAQ,EAAE,cAAc,CAAC,QAAQ;QACjC,MAAM,EAAE,cAAc,CAAC,MAAM;QAC7B,eAAe,EAAE,cAAc,CAAC,eAAe;QAC/C,QAAQ,EAAE,cAAc,CAAC,QAAQ;KAClC,CAAC;SACD,IAAI,CAAC,cAAc,CAAC;SACpB,SAAS,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;SAC9D,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SAClC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;IACvE,MAAM,WAAW,GAAG,MAAM,EAAE;SACzB,MAAM,CAAC;QACN,MAAM,EAAE,eAAe,CAAC,MAAM;QAC9B,WAAW,EAAE,eAAe,CAAC,WAAW;QACxC,YAAY,EAAE,eAAe,CAAC,YAAY;QAC1C,cAAc,EAAE,eAAe,CAAC,cAAc;QAC9C,MAAM,EAAE,eAAe,CAAC,MAAM;QAC9B,QAAQ,EAAE,eAAe,CAAC,QAAQ;QAClC,MAAM,EAAE,eAAe,CAAC,MAAM;QAC9B,WAAW,EAAE,eAAe,CAAC,WAAW;QACxC,QAAQ,EAAE,eAAe,CAAC,QAAQ;KACnC,CAAC;SACD,IAAI,CAAC,eAAe,CAAC;SACrB,KAAK,CAAC,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SACzC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,SAAS,GAAG,MAAM,EAAE;SACvB,MAAM,CAAC;QACN,MAAM,EAAE,YAAY,CAAC,MAAM;QAC3B,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,WAAW,EAAE,YAAY,CAAC,WAAW;QACrC,YAAY,EAAE,YAAY,CAAC,YAAY;QACvC,MAAM,EAAE,YAAY,CAAC,MAAM;QAC3B,SAAS,EAAE,YAAY,CAAC,SAAS;QACjC,MAAM,EAAE,YAAY,CAAC,MAAM;QAC3B,QAAQ,EAAE,YAAY,CAAC,QAAQ;KAChC,CAAC;SACD,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;SACtC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9D,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,GAAG,IAAI,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,CAAC;AACrJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,OAAsB,EAAE,MAAc;IAChF,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,EAAE,CAAC;IACnC,MAAM,EAAE,GAAG,OAAO,CAAC,EAAe,CAAC;IACnC,0FAA0F;IAC1F,+FAA+F;IAC/F,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxF,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE,yDAAyD;IACzD,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,2DAA2D;IAC3D,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrE,MAAM,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3E,OAAO,EAAE,EAAE,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,yBAAyB,GAAuB;IAC3D,cAAc,EAAE,qBAAqB;IACrC,cAAc,EAAE,qBAAqB;CACtC,CAAC"}
1
+ {"version":3,"file":"privacy.js","sourceRoot":"","sources":["../../src/server/privacy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type Err, type Ok } from "@softure-ai/core";
2
+ import type { Entitlement, TrialExtensionErrorCode } from "../contract.js";
3
+ import { type BillingContext } from "./entitlements.js";
4
+ export interface ExtendTrialManuallyInput {
5
+ readonly userId: string;
6
+ /** The trial's new end: the first instant it no longer covers. */
7
+ readonly until: Date;
8
+ /** The admin who extends it; null for an extension without one (a script). */
9
+ readonly adminId: string | null;
10
+ }
11
+ export interface TrialExtensionResult {
12
+ readonly extensionId: string;
13
+ /** Where the account stands after the extension (paid access still wins over the trial). */
14
+ readonly entitlement: Entitlement;
15
+ }
16
+ /**
17
+ * Moves the account's trial end to `until` and records it, in one transaction. Refuses an end at
18
+ * or before now (`billing.end_not_in_future`) and one at or before the current trial end
19
+ * (`billing.trial_not_extended`); every refusal writes nothing. Database errors propagate.
20
+ */
21
+ export declare function extendTrialManually(ctx: BillingContext, input: ExtendTrialManuallyInput): Promise<Ok<TrialExtensionResult> | Err<TrialExtensionErrorCode>>;
22
+ //# sourceMappingURL=trials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trials.d.ts","sourceRoot":"","sources":["../../src/server/trials.ts"],"names":[],"mappings":"AAMA,OAAO,EAAW,KAAK,GAAG,EAAE,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAG3E,OAAO,EAA4C,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKlG,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,4FAA4F;IAC5F,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,EAAE,CAAC,oBAAoB,CAAC,GAAG,GAAG,CAAC,uBAAuB,CAAC,CAAC,CAkChK"}
@@ -0,0 +1,60 @@
1
+ // Trials an admin extends by hand (the admin page's "Extend a trial" form): the trial end moves
2
+ // later and the change is recorded in `billing.trial_extensions` with the end before and after, so
3
+ // the account's history lists it beside the manual grants. Unlike a granted plan it never writes
4
+ // `paid_until`, so an app that counts paying accounts by it does not count a free extension. Locks
5
+ // follow every change's order (account, then the entitlement row, then the new row).
6
+ import { users } from "@softure-ai/auth";
7
+ import { err, ok } from "@softure-ai/core";
8
+ import { eq } from "drizzle-orm";
9
+ import { applyEntitlementEvent, resolveEntitlement } from "../entitlement.js";
10
+ import { entitlements, trialExtensions } from "../schema.js";
11
+ import { findEntitlementRecord, pinEntitlementRow } from "./entitlements.js";
12
+ import { getEntitlementPolicy } from "./options.js";
13
+ import { lockEntitlementRow } from "./take-back.js";
14
+ import { isUserId } from "./user-id.js";
15
+ /**
16
+ * Moves the account's trial end to `until` and records it, in one transaction. Refuses an end at
17
+ * or before now (`billing.end_not_in_future`) and one at or before the current trial end
18
+ * (`billing.trial_not_extended`); every refusal writes nothing. Database errors propagate.
19
+ */
20
+ export async function extendTrialManually(ctx, input) {
21
+ if (!isUserId(input.userId))
22
+ return err("billing.account_unknown");
23
+ return ctx.db.transaction(async (tx) => {
24
+ const now = ctx.clock.now();
25
+ // A shared lock: the account cannot be deleted before the extension below.
26
+ const [account] = await tx.select({ createdAt: users.createdAt }).from(users).where(eq(users.id, input.userId)).for("key share");
27
+ if (account === undefined)
28
+ return err("billing.account_unknown");
29
+ // The derived trial pinned and locked before the check, so a concurrent change is seen here.
30
+ const isPinned = await pinEntitlementRow({ ...ctx, db: tx }, { userId: input.userId, accountCreatedAt: account.createdAt });
31
+ await lockEntitlementRow(tx, input.userId);
32
+ const record = await findEntitlementRecord({ ...ctx, db: tx }, input.userId);
33
+ // The account was locked above and its row pinned.
34
+ if (record === null)
35
+ throw new Error("@softure-ai/billing: an account vanished while its trial was being extended");
36
+ const refusal = input.until <= now ? "billing.end_not_in_future" : input.until <= record.trialEndsAt ? "billing.trial_not_extended" : null;
37
+ if (refusal !== null) {
38
+ // The pin is this transaction's own row, nobody else has seen it: undo it, so the refusal writes nothing.
39
+ if (isPinned)
40
+ await tx.delete(entitlements).where(eq(entitlements.userId, input.userId));
41
+ return err(refusal);
42
+ }
43
+ const next = applyEntitlementEvent(record, { type: "extend_trial", until: input.until }, now);
44
+ // Both of the event's refusals were checked above.
45
+ if (!next.ok)
46
+ throw new Error(`@softure-ai/billing: extending a trial failed with ${next.error}`);
47
+ await tx
48
+ .update(entitlements)
49
+ .set({ trialEndsAt: next.value.trialEndsAt, updatedAt: now })
50
+ .where(eq(entitlements.userId, input.userId));
51
+ const [row] = await tx
52
+ .insert(trialExtensions)
53
+ .values({ userId: input.userId, extendedBy: input.adminId, extendedAt: now, previousEndsAt: record.trialEndsAt, endsAt: next.value.trialEndsAt })
54
+ .returning();
55
+ if (row === undefined)
56
+ throw new Error("@softure-ai/billing: recording a trial extension returned no row");
57
+ return ok({ extensionId: row.id, entitlement: resolveEntitlement(next.value, now, getEntitlementPolicy(ctx.config)) });
58
+ });
59
+ }
60
+ //# sourceMappingURL=trials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trials.js","sourceRoot":"","sources":["../../src/server/trials.ts"],"names":[],"mappings":"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"}
@@ -6,4 +6,5 @@ export { AccountLookup, type AccountLookupProps, type AccountLookupSlot, GrantHi
6
6
  export { PaymentForm, type PaymentFormAction, type PaymentFormProps, type PaymentFormSlot } from "./payment-form.js";
7
7
  export { type AdminAction, PaymentRequestList, type PaymentRequestListProps, type PaymentRequestListSlot, type PaymentRequestRow, } from "./payment-requests.js";
8
8
  export { PricingTiles, type PricingTilesProps, type PricingTilesSlot } from "./pricing-tiles.js";
9
+ export { TrialForm, type TrialFormAction, type TrialFormProps, type TrialFormSlot } from "./trial-form.js";
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrH,OAAO,EACL,KAAK,WAAW,EAChB,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EACL,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACrH,OAAO,EACL,KAAK,WAAW,EAChB,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/ui/index.js CHANGED
@@ -9,4 +9,5 @@ export { AccountLookup, GrantHistory, } from "./grant-history.js";
9
9
  export { PaymentForm } from "./payment-form.js";
10
10
  export { PaymentRequestList, } from "./payment-requests.js";
11
11
  export { PricingTiles } from "./pricing-tiles.js";
12
+ export { TrialForm } from "./trial-form.js";
12
13
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,gFAAgF;AAChF,+FAA+F;AAC/F,OAAO,EAAE,WAAW,EAA+C,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAiD,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,SAAS,EAAiE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EACL,aAAa,EAGb,YAAY,GAIb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAuE,MAAM,mBAAmB,CAAC;AACrH,OAAO,EAEL,kBAAkB,GAInB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAiD,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,gFAAgF;AAChF,+FAA+F;AAC/F,OAAO,EAAE,WAAW,EAA+C,MAAM,mBAAmB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAiD,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,SAAS,EAAiE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EACL,aAAa,EAGb,YAAY,GAIb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAuE,MAAM,mBAAmB,CAAC;AACrH,OAAO,EAEL,kBAAkB,GAInB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,YAAY,EAAiD,MAAM,oBAAoB,CAAC;AACjG,OAAO,EAAE,SAAS,EAAiE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { Locale } from "@softure-ai/core";
2
+ import { type ClassNames } from "@softure-ai/ui";
3
+ import { type TrialFormState } from "../contract.js";
4
+ import { type BillingMessages } from "../messages/index.js";
5
+ export type TrialFormAction = (previous: TrialFormState, formData: FormData) => Promise<TrialFormState>;
6
+ export type TrialFormSlot = "root" | "form" | "notice";
7
+ export interface TrialFormProps {
8
+ readonly action: TrialFormAction;
9
+ readonly messages: BillingMessages;
10
+ /** Locale of the built-in copy of the ui primitives. */
11
+ readonly locale?: Locale;
12
+ readonly classNames?: ClassNames<TrialFormSlot>;
13
+ readonly unstyled?: boolean;
14
+ }
15
+ export declare function TrialForm({ action, messages, locale, classNames, unstyled }: TrialFormProps): import("react").JSX.Element;
16
+ //# sourceMappingURL=trial-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trial-form.d.ts","sourceRoot":"","sources":["../../src/ui/trial-form.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAU,KAAK,UAAU,EAA+C,MAAM,gBAAgB,CAAC;AAEtG,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAMpF,MAAM,MAAM,eAAe,GAAG,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAExG,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,aAAa,CAAC,CAAC;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAQD,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,cAAc,+BA+C3F"}
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button, createSlotClassGetter, FormError, TextField } from "@softure-ai/ui";
4
+ import { useActionState } from "react";
5
+ import { INITIAL_TRIAL_FORM_STATE } from "../contract.js";
6
+ import { EMAIL_FIELD, TRIAL_LAST_DAY_FIELD } from "../fields.js";
7
+ import { getBillingErrorMessage } from "../messages/index.js";
8
+ const DEFAULT_CLASSES = {
9
+ root: "sft:flex sft:flex-col sft:gap-3 sft:font-sans",
10
+ form: "sft:flex sft:flex-col sft:gap-3",
11
+ notice: "sft:m-0 sft:text-sm sft:text-success",
12
+ };
13
+ export function TrialForm({ action, messages, locale, classNames, unstyled }) {
14
+ const [state, formAction, isPending] = useActionState(action, INITIAL_TRIAL_FORM_STATE);
15
+ const slot = createSlotClassGetter({ defaults: DEFAULT_CLASSES, classNames, unstyled });
16
+ const copy = messages.admin.trial;
17
+ const error = state.error === undefined ? undefined : getBillingErrorMessage(messages, state.error);
18
+ const isEmailError = state.error === "billing.account_unknown";
19
+ const isDayError = state.error === "billing.day_invalid" || state.error === "billing.end_not_in_future" || state.error === "billing.trial_not_extended";
20
+ const isError = state.status === "error";
21
+ // A fresh, empty form after each extension; the typed values after an error.
22
+ const key = `${state.status}:${state.email ?? ""}:${state.lastDay ?? ""}`;
23
+ return (_jsxs("div", { className: slot("root"), children: [_jsxs("form", { action: formAction, className: slot("form"), children: [_jsx(TextField, { name: EMAIL_FIELD, type: "email", label: copy.email, required: true, defaultValue: isError ? (state.email ?? "") : "", error: isEmailError ? error : undefined, locale: locale, unstyled: unstyled }, `email:${key}`), _jsx(TextField, { name: TRIAL_LAST_DAY_FIELD, type: "date", label: copy.lastDay, required: true, defaultValue: isError ? (state.lastDay ?? "") : "", error: isDayError ? error : undefined, locale: locale, unstyled: unstyled }, `day:${key}`), _jsx(FormError, { message: isEmailError || isDayError ? undefined : error, unstyled: unstyled }), _jsx(Button, { type: "submit", variant: "primary", pending: isPending, unstyled: unstyled, children: isPending ? copy.pending : copy.submit })] }), state.status === "extended" ? (_jsx("p", { role: "status", className: slot("notice"), children: state.notice })) : null] }));
24
+ }
25
+ //# sourceMappingURL=trial-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trial-form.js","sourceRoot":"","sources":["../../src/ui/trial-form.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ -- Trial extensions an admin makes by hand (extendTrialManually, the admin page's "Extend a trial"
2
+ -- form), each with the trial end before and after, so the account's history lists it beside the
3
+ -- manual grants. And the indexes behind three foreign keys from auth.users that had none starting
4
+ -- with their column, so deleting an account (or an admin's account) does not scan these tables
5
+ -- under the delete's lock.
6
+ -- Rollback: DROP TABLE billing.trial_extensions; DROP INDEX billing.payment_requests_user_id,
7
+ -- billing.manual_grants_granted_by, billing.manual_grants_revoked_by; then
8
+ -- DELETE FROM softure.migrations WHERE module = 'billing' AND version = 10;
9
+ CREATE TABLE trial_extensions (
10
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
11
+ user_id uuid NOT NULL REFERENCES auth.users (id) ON DELETE CASCADE,
12
+ -- The admin who extended it; NULL for one without an admin (a script) or once their account is gone.
13
+ extended_by uuid REFERENCES auth.users (id) ON DELETE SET NULL,
14
+ extended_at timestamptz NOT NULL,
15
+ -- The first instant the trial no longer covered before, and after the extension.
16
+ previous_ends_at timestamptz NOT NULL,
17
+ ends_at timestamptz NOT NULL,
18
+ CONSTRAINT trial_extensions_moves_later CHECK (ends_at > previous_ends_at),
19
+ CONSTRAINT trial_extensions_ends_after_extended CHECK (ends_at > extended_at)
20
+ );
21
+
22
+ CREATE INDEX trial_extensions_user_id ON trial_extensions (user_id);
23
+ CREATE INDEX trial_extensions_extended_by ON trial_extensions (extended_by) WHERE extended_by IS NOT NULL;
24
+
25
+ CREATE INDEX payment_requests_user_id ON payment_requests (user_id);
26
+ CREATE INDEX manual_grants_granted_by ON manual_grants (granted_by) WHERE granted_by IS NOT NULL;
27
+ CREATE INDEX manual_grants_revoked_by ON manual_grants (revoked_by) WHERE revoked_by IS NOT NULL;
package/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "id": "billing",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "dependsOn": { "security": "^0.1.0", "auth": "^0.1.0", "mailing": "^0.1.0?", "ops": "^0.1.0?" },
5
5
  "dbSchema": "billing",
6
- "tables": ["entitlements", "payments", "payment_requests", "manual_grants"],
6
+ "tables": ["entitlements", "payments", "payment_requests", "manual_grants", "trial_extensions"],
7
7
  "env": [
8
8
  {
9
9
  "name": "STRIPE_SECRET_KEY",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softure-ai/billing",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
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/contract.ts CHANGED
@@ -11,6 +11,13 @@ export type BillingErrorCode =
11
11
  /** A grant or a trial extension that ends now or earlier. */
12
12
  | "billing.end_not_in_future";
13
13
 
14
+ /** Why an admin could not extend a trial (`extendTrialManually`). */
15
+ export type TrialExtensionErrorCode =
16
+ | "billing.account_unknown"
17
+ | "billing.end_not_in_future"
18
+ /** The trial already ends then or later: an extension would change nothing. */
19
+ | "billing.trial_not_extended";
20
+
14
21
  /** Every code a guarded write can show: the guard's own and the generic ones. */
15
22
  export type BillingFormErrorCode = BillingErrorCode | CoreErrorCode;
16
23
 
@@ -137,6 +144,14 @@ export type PaymentFormErrorCode = PaymentErrorCode | InvoiceFieldErrorCode | "s
137
144
  /** Every code the admin grant form can show. */
138
145
  export type GrantFormErrorCode = "billing.plan_unknown" | "billing.account_unknown" | "billing.lifetime_active" | "auth.forbidden" | CoreErrorCode;
139
146
 
147
+ /** Every code the admin trial form can show. */
148
+ export type TrialFormErrorCode =
149
+ | TrialExtensionErrorCode
150
+ /** The last day is not a calendar day (`YYYY-MM-DD`). */
151
+ | "billing.day_invalid"
152
+ | "auth.forbidden"
153
+ | CoreErrorCode;
154
+
140
155
  /** Why an admin could not act on a request or a manual grant. */
141
156
  export type AdminErrorCode =
142
157
  /** The request was granted or dismissed already, or never existed. */
@@ -172,6 +187,19 @@ export interface GrantFormState {
172
187
 
173
188
  export const INITIAL_GRANT_FORM_STATE: GrantFormState = { status: "idle" };
174
189
 
190
+ /** What the admin trial action returns to its form (`useActionState`). */
191
+ export interface TrialFormState {
192
+ readonly status: "idle" | "extended" | "error";
193
+ readonly error?: TrialFormErrorCode;
194
+ /** After an extension: the trial's new last day, in the app's copy. */
195
+ readonly notice?: string;
196
+ /** The email and last day as sent, to fill the form again after an error. */
197
+ readonly email?: string;
198
+ readonly lastDay?: string;
199
+ }
200
+
201
+ export const INITIAL_TRIAL_FORM_STATE: TrialFormState = { status: "idle" };
202
+
175
203
  /** What an admin button (grant or dismiss a request, revoke a grant, find an account) returns to its form. */
176
204
  export type AdminActionState =
177
205
  | { readonly status: "idle" }
package/src/fields.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // Form field names shared by the forms (`/ui`) and the actions that read them (`/next`).
2
2
  export const PLAN_FIELD = "plan";
3
3
  export const EMAIL_FIELD = "email";
4
+ /** The admin trial form's field: the trial's new last day, `YYYY-MM-DD`. */
5
+ export const TRIAL_LAST_DAY_FIELD = "trialLastDay";
4
6
 
5
7
  export const INVOICE_FIELDS = {
6
8
  name: "invoiceName",
package/src/index.ts CHANGED
@@ -28,10 +28,10 @@ export const BILLING_RATE_LIMIT_BUCKETS = {
28
28
  export const billing = defineModule({
29
29
  manifest: {
30
30
  id: MODULE_ID,
31
- version: "0.1.7",
31
+ version: "0.1.8",
32
32
  dependsOn: { security: "^0.1.0", auth: "^0.1.0", mailing: "^0.1.0?", ops: "^0.1.0?" },
33
33
  dbSchema: "billing",
34
- tables: ["entitlements", "payments", "payment_requests", "manual_grants"],
34
+ tables: ["entitlements", "payments", "payment_requests", "manual_grants", "trial_extensions"],
35
35
  env: [
36
36
  {
37
37
  name: "STRIPE_SECRET_KEY",
@@ -62,11 +62,15 @@ export {
62
62
  INITIAL_ADMIN_ACTION_STATE,
63
63
  INITIAL_GRANT_FORM_STATE,
64
64
  INITIAL_PAYMENT_FORM_STATE,
65
+ INITIAL_TRIAL_FORM_STATE,
65
66
  PERIOD_UNITS,
66
67
  type AdminActionErrorCode,
67
68
  type AdminActionState,
68
69
  type AdminErrorCode,
69
70
  type BillingErrorCode,
71
+ type TrialExtensionErrorCode,
72
+ type TrialFormErrorCode,
73
+ type TrialFormState,
70
74
  type BillingFormErrorCode,
71
75
  type Entitlement,
72
76
  type EntitlementEvent,
@@ -136,7 +140,7 @@ export {
136
140
  } from "./reminder.js";
137
141
  export { CURRENCY_MINOR_UNIT_DIGITS } from "./currency-digits.js";
138
142
  export { formatPrice, getMinorUnitDigits, isSupportedCurrency } from "./price.js";
139
- export { billingSchema, entitlements, manualGrants, paymentRequests, payments } from "./schema.js";
143
+ export { billingSchema, entitlements, manualGrants, paymentRequests, payments, trialExtensions } from "./schema.js";
140
144
  export { getCheckoutSessionParams, stripe, STRIPE_API_BASE, STRIPE_SECRET_KEY_ENV, STRIPE_TIMEOUT_MS, type StripeOptions } from "./stripe.js";
141
145
  export {
142
146
  describeStripePriceProblem,
@@ -46,6 +46,7 @@ export const en = {
46
46
  empty: "No plans are available yet.",
47
47
  },
48
48
  payment: {
49
+ heading: "Payment",
49
50
  title: "Choose a plan",
50
51
  lead: "Pick the plan that suits you. Paid access starts when your current access ends.",
51
52
  plansLabel: "Plans",
@@ -67,6 +68,7 @@ export const en = {
67
68
  lifetime: "You have lifetime access: there is nothing left to pay for.",
68
69
  },
69
70
  admin: {
71
+ heading: "Billing",
70
72
  title: "Grant access",
71
73
  lead: "Grant a plan to an account once its payment has arrived. A paid period starts when the account's current access ends.",
72
74
  email: "Account email",
@@ -76,6 +78,15 @@ export const en = {
76
78
  granted: "{email} now has {plan}.",
77
79
  grantedUntil: "{email} now has {plan}, with access until {date}.",
78
80
  noPlans: "There are no plans to grant: add them to billing({ plans }).",
81
+ trial: {
82
+ title: "Extend a trial",
83
+ lead: "Give an account a longer trial for free, e.g. an invited user. It is not paid access: the account keeps its paid periods, and the trial lasts through the day you choose.",
84
+ email: "Email of the account to extend",
85
+ lastDay: "Last day of the trial",
86
+ submit: "Extend trial",
87
+ pending: "Extending…",
88
+ extended: "{email} now has a trial until {date}.",
89
+ },
79
90
  requests: {
80
91
  title: "Invoice requests",
81
92
  lead: "Requests sent from the payment page. Grant the plan once the invoice is paid, or dismiss the request.",
@@ -119,6 +130,10 @@ export const en = {
119
130
  revoke: "Revoke",
120
131
  revoking: "Revoking…",
121
132
  revokeLabel: "Revoke {plan} granted on {date}",
133
+ trialExtended: "Trial extended",
134
+ trialUntil: "Trial until {date}",
135
+ extendedOn: "Extended on {date}",
136
+ trialWasUntil: "The trial was until {date}",
122
137
  },
123
138
  },
124
139
  errors: {
@@ -135,6 +150,8 @@ export const en = {
135
150
  lifetime_active: "This account already has lifetime access: there is nothing to pay for or grant.",
136
151
  request_closed: "This request was granted or dismissed already.",
137
152
  grant_revoked: "This grant was revoked already.",
153
+ trial_not_extended: "The trial already lasts through this day or longer.",
154
+ day_invalid: "Enter a date, e.g. 2026-11-30.",
138
155
  },
139
156
  security: {
140
157
  rate_limited: "Too many attempts. Try again later.",
@@ -1,4 +1,4 @@
1
- import type { AdminActionErrorCode, BillingFormErrorCode, GrantFormErrorCode, PaymentFormErrorCode } from "../contract.js";
1
+ import type { AdminActionErrorCode, BillingFormErrorCode, GrantFormErrorCode, PaymentFormErrorCode, TrialFormErrorCode } from "../contract.js";
2
2
  import { en } from "./en.js";
3
3
  import { pl } from "./pl.js";
4
4
 
@@ -8,7 +8,7 @@ export type BillingMessages = typeof en;
8
8
  export const billingMessages = { en, pl };
9
9
 
10
10
  /** The copy for an error code; an unknown code gets the generic failure. */
11
- export function getBillingErrorMessage(messages: BillingMessages, code: BillingFormErrorCode | PaymentFormErrorCode | GrantFormErrorCode | AdminActionErrorCode): string {
11
+ export function getBillingErrorMessage(messages: BillingMessages, code: BillingFormErrorCode | PaymentFormErrorCode | GrantFormErrorCode | TrialFormErrorCode | AdminActionErrorCode): string {
12
12
  const separator = code.lastIndexOf(".");
13
13
  const namespace = code.slice(0, separator);
14
14
  const name = code.slice(separator + 1);
@@ -48,6 +48,7 @@ export const pl: typeof en = {
48
48
  empty: "Nie ma jeszcze dostępnych planów.",
49
49
  },
50
50
  payment: {
51
+ heading: "Płatność",
51
52
  title: "Wybierz plan",
52
53
  lead: "Wybierz plan dla siebie. Płatny dostęp zaczyna się, gdy skończy się obecny.",
53
54
  plansLabel: "Plany",
@@ -69,6 +70,7 @@ export const pl: typeof en = {
69
70
  lifetime: "Masz dostęp dożywotni: nie ma już za co płacić.",
70
71
  },
71
72
  admin: {
73
+ heading: "Rozliczenia",
72
74
  title: "Nadaj dostęp",
73
75
  lead: "Nadaj plan kontu, gdy wpłynie płatność. Płatny okres zaczyna się, gdy skończy się obecny dostęp konta.",
74
76
  email: "E-mail konta",
@@ -78,6 +80,15 @@ export const pl: typeof en = {
78
80
  granted: "{email} ma teraz {plan}.",
79
81
  grantedUntil: "{email} ma teraz {plan}, z dostępem do {date}.",
80
82
  noPlans: "Nie ma planów do nadania: dodaj je w billing({ plans }).",
83
+ trial: {
84
+ title: "Przedłuż okres próbny",
85
+ lead: "Daj kontu dłuższy okres próbny za darmo, np. zaproszonej osobie. To nie jest płatny dostęp: konto zachowuje opłacone okresy, a okres próbny trwa do końca wybranego dnia.",
86
+ email: "E-mail konta do przedłużenia",
87
+ lastDay: "Ostatni dzień okresu próbnego",
88
+ submit: "Przedłuż okres próbny",
89
+ pending: "Przedłużanie…",
90
+ extended: "{email} ma teraz okres próbny do {date}.",
91
+ },
81
92
  requests: {
82
93
  title: "Prośby o fakturę",
83
94
  lead: "Prośby wysłane ze strony płatności. Nadaj plan, gdy faktura zostanie opłacona, albo odrzuć prośbę.",
@@ -121,6 +132,10 @@ export const pl: typeof en = {
121
132
  revoke: "Cofnij",
122
133
  revoking: "Cofanie…",
123
134
  revokeLabel: "Cofnij {plan} nadany {date}",
135
+ trialExtended: "Przedłużony okres próbny",
136
+ trialUntil: "Okres próbny do {date}",
137
+ extendedOn: "Przedłużono {date}",
138
+ trialWasUntil: "Wcześniej okres próbny do {date}",
124
139
  },
125
140
  },
126
141
  errors: {
@@ -137,6 +152,8 @@ export const pl: typeof en = {
137
152
  lifetime_active: "To konto ma już dostęp dożywotni: nie ma za co płacić ani czego nadawać.",
138
153
  request_closed: "Ta prośba została już obsłużona lub odrzucona.",
139
154
  grant_revoked: "Ten dostęp został już cofnięty.",
155
+ trial_not_extended: "Okres próbny trwa już do tego dnia albo dłużej.",
156
+ day_invalid: "Wpisz datę, np. 2026-11-30.",
140
157
  },
141
158
  security: {
142
159
  rate_limited: "Zbyt wiele prób. Spróbuj później.",