@saasicat/core 1.0.0-rc.16 → 1.0.0-rc.18

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/dist/index.d.cts CHANGED
@@ -473,6 +473,275 @@ interface UpdateMarketingProjectionData {
473
473
  highlight?: boolean;
474
474
  }
475
475
 
476
+ /** The address lines a party is named with. Every one of them may be unknown. */
477
+ interface PartyAddress {
478
+ /** Street and number. */
479
+ addressLine1: string | null;
480
+ /** A second line, such as a building or a c/o. */
481
+ addressLine2: string | null;
482
+ postalCode: string | null;
483
+ city: string | null;
484
+ /** ISO 3166-1 alpha-2, upper case. */
485
+ country: string | null;
486
+ }
487
+ /**
488
+ * What names a legal entity: the name it is registered under and the
489
+ * identifiers its tax office gave it.
490
+ *
491
+ * For a subscriber these are the party a contract was concluded with. Under a
492
+ * running contract they change only as a correction of that same entity,
493
+ * recorded with the values it replaced and the reason; contact details change
494
+ * freely.
495
+ */
496
+ interface LegalIdentity {
497
+ /** The registered name, legal form included. */
498
+ legalName: string;
499
+ /** VAT identification number. */
500
+ vatId: string | null;
501
+ /** The national tax number, where one is stated beside or instead of the VAT id. */
502
+ taxNumber: string | null;
503
+ }
504
+ /** The fields of the legal identity, the ones a correction may change. */
505
+ type SubscriberIdentityField = keyof LegalIdentity;
506
+ /** How a subscriber is reached, which may change at any time. */
507
+ interface SubscriberContact extends PartyAddress {
508
+ /** Where invoices will be sent. */
509
+ invoiceEmail: string | null;
510
+ }
511
+ /** A subscriber's master data, as it stands. */
512
+ type SubscriberDetails = LegalIdentity & SubscriberContact;
513
+ /**
514
+ * What a subscriber is created with.
515
+ *
516
+ * Only the legal name is required. The address, the tax identifiers and the
517
+ * invoice email stay optional until sign-up asks for them and invoicing
518
+ * requires them; an absent or blank value is recorded as unknown.
519
+ */
520
+ type NewSubscriberDetails = Pick<SubscriberDetails, 'legalName'> & Partial<Omit<SubscriberDetails, 'legalName'>>;
521
+ interface SubscriberRecord extends SubscriberDetails {
522
+ id: string;
523
+ /**
524
+ * Assigned when the subscriber is created and never changed: the number,
525
+ * counted per installation, behind the prefix configured at that moment.
526
+ */
527
+ customerNumber: string;
528
+ /** The tenant this subscriber is live for, or `null` once it has none. */
529
+ tenantId: string | null;
530
+ /**
531
+ * Created by the migration that gave every existing tenant its subscriber,
532
+ * from the application's own tenant record rather than from what a customer
533
+ * entered.
534
+ */
535
+ migrated: boolean;
536
+ createdAt: Date;
537
+ updatedAt: Date;
538
+ }
539
+ /** What a repository writes for a new subscriber, every detail already settled. */
540
+ interface CreateSubscriberData extends SubscriberDetails {
541
+ /** The tenant the subscriber is created for and live with. */
542
+ tenantId: string;
543
+ /** Put in front of the assigned number; empty for the number alone. */
544
+ customerNumberPrefix: string;
545
+ }
546
+ /** Contact details to change; a member left out keeps its value. */
547
+ type SubscriberContactChange = Partial<SubscriberContact>;
548
+ /**
549
+ * A change to a subscriber's legal identity, and what the operator declares it
550
+ * to be.
551
+ *
552
+ * SaaSiCat cannot tell a misspelt name from another company taking over, so the
553
+ * operator says which: `correction` for the same legal entity — a typo, a wrong
554
+ * tax identifier, a change of name that entity went through — and `takeover`
555
+ * for another one, which is a transfer rather than an edit and is refused.
556
+ */
557
+ interface SubscriberIdentityCorrection {
558
+ kind: 'correction' | 'takeover';
559
+ legalName?: string;
560
+ vatId?: string | null;
561
+ taxNumber?: string | null;
562
+ /** Why the identity is corrected. Part of the record. */
563
+ reason: string;
564
+ /** Who corrects it, as an actor tag the audit log would write. */
565
+ correctedBy: string;
566
+ }
567
+ /** Identity values by field, holding only the fields a correction changed. */
568
+ type SubscriberIdentityValues = Partial<LegalIdentity>;
569
+ /** What a correction replaces and what it writes, holding only the fields that move. */
570
+ interface SubscriberIdentityDelta {
571
+ previous: SubscriberIdentityValues;
572
+ corrected: SubscriberIdentityValues;
573
+ }
574
+ /** What a repository writes for a correction the service has accepted. */
575
+ interface SubscriberCorrectionData {
576
+ /** The new values; a field equal to what is stored is not recorded. */
577
+ corrected: SubscriberIdentityValues;
578
+ reason: string;
579
+ correctedBy: string;
580
+ correctedAt: Date;
581
+ }
582
+ /** One correction of a subscriber's legal identity, as it was recorded. */
583
+ interface SubscriberCorrectionRecord {
584
+ id: string;
585
+ subscriberId: string;
586
+ /** The values the correction replaced. */
587
+ previous: SubscriberIdentityValues;
588
+ /** The values it wrote. */
589
+ corrected: SubscriberIdentityValues;
590
+ reason: string;
591
+ correctedBy: string;
592
+ correctedAt: Date;
593
+ }
594
+ /** The outcome of writing a correction: nothing is recorded when no value moved. */
595
+ interface SubscriberCorrectionResult {
596
+ subscriber: SubscriberRecord;
597
+ correction: SubscriberCorrectionRecord | null;
598
+ }
599
+
600
+ /** The payment methods SaaSiCat takes, by the names `config/saas.yaml` uses. */
601
+ type PaymentMethodType = 'card' | 'sepa_debit';
602
+ /**
603
+ * Whom a payment method is set up for. The gateway hands it back unchanged
604
+ * with its confirmation, which is how the confirmation finds its way to the
605
+ * sign-up or the subscriber that asked for it.
606
+ */
607
+ type PaymentMethodSetupSubject = {
608
+ kind: 'registration';
609
+ pendingRegistrationId: string;
610
+ } | {
611
+ kind: 'subscriber';
612
+ subscriberId: string;
613
+ };
614
+ /** The party the gateway keeps the payment method for: its customer there. */
615
+ interface PaymentMethodHolder {
616
+ /** The legal name, which a direct debit mandate names. */
617
+ name: string;
618
+ /** Where the gateway sends what it sends; `null` lets its form ask. */
619
+ email: string | null;
620
+ address: PartyAddress;
621
+ /**
622
+ * The customer this party already has at the account, from an earlier
623
+ * payment method. `null` asks the gateway for a new one.
624
+ */
625
+ customerRef: string | null;
626
+ }
627
+ interface StartPaymentMethodSetupInput {
628
+ subject: PaymentMethodSetupSubject;
629
+ holder: PaymentMethodHolder;
630
+ /** What the form offers, from the account's `methods`. */
631
+ methods: readonly PaymentMethodType[];
632
+ /** Where the gateway sends the person once the form is done. */
633
+ successUrl: string;
634
+ /** Where the gateway sends the person who leaves the form. */
635
+ cancelUrl: string;
636
+ }
637
+ /** A body and headers exactly as they arrived, before anything parsed them. */
638
+ interface PaymentGatewayCallback {
639
+ body: string | Uint8Array;
640
+ headers: Readonly<Record<string, string | readonly string[] | undefined>>;
641
+ }
642
+ interface PaymentMethodSetupSession {
643
+ /**
644
+ * The gateway's identifier of the session, unique within its account — and
645
+ * an adapter that has no session of its own makes one per start rather than
646
+ * a constant. A session is confirmed once, which the event log holds, so a
647
+ * value two starts share lets the second confirmation through as the
648
+ * duplicate it is not.
649
+ */
650
+ sessionRef: string;
651
+ /** The gateway's form, where the person is sent next. */
652
+ redirectUrl: string;
653
+ /** The customer the payment method is set up for, created if none was given. */
654
+ customerRef: string;
655
+ /**
656
+ * A confirmation the gateway already holds when the session starts, to be
657
+ * handled like any callback. Only a gateway without a form of its own has
658
+ * one — the development gateway; a real gateway confirms through its
659
+ * webhook.
660
+ */
661
+ immediateCallback?: PaymentGatewayCallback;
662
+ }
663
+ /**
664
+ * What SaaSiCat keeps about a payment method: enough to show which one it is,
665
+ * never enough to pay with it (`SC-PRIV-005`).
666
+ */
667
+ interface MaskedPaymentMethod {
668
+ type: PaymentMethodType;
669
+ /** The card network, such as `visa`; `null` for a direct debit. */
670
+ brand: string | null;
671
+ /** The last four digits of the card number or the IBAN. */
672
+ last4: string;
673
+ /** 1–12; `null` for a direct debit. */
674
+ expiryMonth: number | null;
675
+ /** Four digits; `null` for a direct debit. */
676
+ expiryYear: number | null;
677
+ /** ISO 3166-1 alpha-2 of the card's issuer or the bank account, where the gateway says. */
678
+ country: string | null;
679
+ /** The bank code of a direct debit account, where the gateway says. */
680
+ bankCode: string | null;
681
+ /** The reference of a direct debit mandate, which a debit announcement quotes. */
682
+ mandateReference: string | null;
683
+ }
684
+ /** A payment method the gateway confirmed, with the references that reach it there. */
685
+ interface ConfirmedPaymentMethod extends MaskedPaymentMethod {
686
+ customerRef: string;
687
+ paymentMethodRef: string;
688
+ }
689
+ /** A gateway callback, verified and translated. */
690
+ type PaymentGatewayEvent = {
691
+ kind: 'payment-method-confirmed';
692
+ /** The gateway's identifier of the event, unique within its account. */
693
+ eventId: string;
694
+ /** When the gateway says it happened. */
695
+ occurredAt: Date;
696
+ sessionRef: string;
697
+ subject: PaymentMethodSetupSubject;
698
+ paymentMethod: ConfirmedPaymentMethod;
699
+ } | {
700
+ kind: 'payment-method-setup-failed';
701
+ eventId: string;
702
+ occurredAt: Date;
703
+ sessionRef: string;
704
+ subject: PaymentMethodSetupSubject;
705
+ } | {
706
+ /** Genuine, and nothing SaaSiCat acts on. */
707
+ kind: 'unhandled';
708
+ eventId: string;
709
+ occurredAt: Date;
710
+ /** The gateway's own name for the event, for the log. */
711
+ type: string;
712
+ };
713
+ /**
714
+ * One account at a payment gateway: its keys, its form and its callbacks.
715
+ *
716
+ * An adapter is bound to one account, and `config/saas.yaml#payments.accounts`
717
+ * names it. Mollie or any other provider is another adapter behind this port.
718
+ */
719
+ interface PaymentGateway {
720
+ /** The provider, as `config/saas.yaml` names it, e.g. `stripe`. */
721
+ readonly provider: string;
722
+ /**
723
+ * Opens the gateway's form for a payment method. Nothing is confirmed until
724
+ * the gateway says so through `readCallback`.
725
+ */
726
+ startPaymentMethodSetup(input: StartPaymentMethodSetupInput): Promise<PaymentMethodSetupSession>;
727
+ /**
728
+ * Verifies a callback with the account's secret and translates it.
729
+ * Throws `PaymentCallbackRejectedError` for anything the gateway did not
730
+ * send, before a single field of it is trusted.
731
+ */
732
+ readCallback(callback: PaymentGatewayCallback): Promise<PaymentGatewayEvent>;
733
+ }
734
+ /**
735
+ * A callback that is not the gateway's: a missing or wrong signature, a stale
736
+ * timestamp, a body that was altered. Nothing is read from it.
737
+ */
738
+ declare class PaymentCallbackRejectedError extends Error {
739
+ readonly code = "PAYMENT_CALLBACK_REJECTED";
740
+ constructor(reason: string);
741
+ }
742
+ /** Realm-safe type guard, like `isPlatformUserExistsError`. */
743
+ declare function isPaymentCallbackRejectedError(err: unknown): err is PaymentCallbackRejectedError;
744
+
476
745
  type FeatureKey = string;
477
746
  type PlanId = string;
478
747
  type QuotaKey = string;
@@ -613,6 +882,25 @@ interface PlanCatalogSubscribers {
613
882
  */
614
883
  customerNumberPrefix?: string;
615
884
  }
885
+ /** One account at a payment gateway, by the name `PlanCatalogPayments.accounts` gives it. */
886
+ interface PlanCatalogPaymentAccount {
887
+ /** The provider its bound adapter names itself as, e.g. `stripe`. */
888
+ provider: string;
889
+ /** What a new payment method may be at this account. Read for `newPaymentMethods` only. */
890
+ methods?: PaymentMethodType[];
891
+ }
892
+ /**
893
+ * The gateway accounts payment methods are taken through. Their keys are bound
894
+ * in code from the environment, never written into the file.
895
+ */
896
+ interface PlanCatalogPayments {
897
+ /** The account a new payment method is taken at. Omitted, none is taken. */
898
+ newPaymentMethods?: string;
899
+ /** The origins a gateway's form may send a person back to, such as `https://app.example.com`. */
900
+ returnUrlOrigins: string[];
901
+ /** Every account taking new payment methods or holding a reference in use. */
902
+ accounts: Record<string, PlanCatalogPaymentAccount>;
903
+ }
616
904
  /**
617
905
  * The part of `config/saas.yaml` that is configuration rather than catalogue.
618
906
  *
@@ -637,6 +925,8 @@ interface PlanCatalogSettings {
637
925
  issuer?: PlanCatalogIssuer;
638
926
  /** How subscribers are numbered. Optional. */
639
927
  subscribers?: PlanCatalogSubscribers;
928
+ /** The payment gateway accounts. Optional until payment methods are taken. */
929
+ payments?: PlanCatalogPayments;
640
930
  }
641
931
  interface PlanCatalog extends PlanCatalogSettings {
642
932
  schemaVersion: 1;
@@ -2803,130 +3093,6 @@ interface PromoRevenueDeductionAggregator {
2803
3093
  sumGrossForPromoCode(promoCodeId: string): Promise<string>;
2804
3094
  }
2805
3095
 
2806
- /** The address lines a party is named with. Every one of them may be unknown. */
2807
- interface PartyAddress {
2808
- /** Street and number. */
2809
- addressLine1: string | null;
2810
- /** A second line, such as a building or a c/o. */
2811
- addressLine2: string | null;
2812
- postalCode: string | null;
2813
- city: string | null;
2814
- /** ISO 3166-1 alpha-2, upper case. */
2815
- country: string | null;
2816
- }
2817
- /**
2818
- * What names a legal entity: the name it is registered under and the
2819
- * identifiers its tax office gave it.
2820
- *
2821
- * For a subscriber these are the party a contract was concluded with. Under a
2822
- * running contract they change only as a correction of that same entity,
2823
- * recorded with the values it replaced and the reason; contact details change
2824
- * freely.
2825
- */
2826
- interface LegalIdentity {
2827
- /** The registered name, legal form included. */
2828
- legalName: string;
2829
- /** VAT identification number. */
2830
- vatId: string | null;
2831
- /** The national tax number, where one is stated beside or instead of the VAT id. */
2832
- taxNumber: string | null;
2833
- }
2834
- /** The fields of the legal identity, the ones a correction may change. */
2835
- type SubscriberIdentityField = keyof LegalIdentity;
2836
- /** How a subscriber is reached, which may change at any time. */
2837
- interface SubscriberContact extends PartyAddress {
2838
- /** Where invoices will be sent. */
2839
- invoiceEmail: string | null;
2840
- }
2841
- /** A subscriber's master data, as it stands. */
2842
- type SubscriberDetails = LegalIdentity & SubscriberContact;
2843
- /**
2844
- * What a subscriber is created with.
2845
- *
2846
- * Only the legal name is required. The address, the tax identifiers and the
2847
- * invoice email stay optional until sign-up asks for them and invoicing
2848
- * requires them; an absent or blank value is recorded as unknown.
2849
- */
2850
- type NewSubscriberDetails = Pick<SubscriberDetails, 'legalName'> & Partial<Omit<SubscriberDetails, 'legalName'>>;
2851
- interface SubscriberRecord extends SubscriberDetails {
2852
- id: string;
2853
- /**
2854
- * Assigned when the subscriber is created and never changed: the number,
2855
- * counted per installation, behind the prefix configured at that moment.
2856
- */
2857
- customerNumber: string;
2858
- /** The tenant this subscriber is live for, or `null` once it has none. */
2859
- tenantId: string | null;
2860
- /**
2861
- * Created by the migration that gave every existing tenant its subscriber,
2862
- * from the application's own tenant record rather than from what a customer
2863
- * entered.
2864
- */
2865
- migrated: boolean;
2866
- createdAt: Date;
2867
- updatedAt: Date;
2868
- }
2869
- /** What a repository writes for a new subscriber, every detail already settled. */
2870
- interface CreateSubscriberData extends SubscriberDetails {
2871
- /** The tenant the subscriber is created for and live with. */
2872
- tenantId: string;
2873
- /** Put in front of the assigned number; empty for the number alone. */
2874
- customerNumberPrefix: string;
2875
- }
2876
- /** Contact details to change; a member left out keeps its value. */
2877
- type SubscriberContactChange = Partial<SubscriberContact>;
2878
- /**
2879
- * A change to a subscriber's legal identity, and what the operator declares it
2880
- * to be.
2881
- *
2882
- * SaaSiCat cannot tell a misspelt name from another company taking over, so the
2883
- * operator says which: `correction` for the same legal entity — a typo, a wrong
2884
- * tax identifier, a change of name that entity went through — and `takeover`
2885
- * for another one, which is a transfer rather than an edit and is refused.
2886
- */
2887
- interface SubscriberIdentityCorrection {
2888
- kind: 'correction' | 'takeover';
2889
- legalName?: string;
2890
- vatId?: string | null;
2891
- taxNumber?: string | null;
2892
- /** Why the identity is corrected. Part of the record. */
2893
- reason: string;
2894
- /** Who corrects it, as an actor tag the audit log would write. */
2895
- correctedBy: string;
2896
- }
2897
- /** Identity values by field, holding only the fields a correction changed. */
2898
- type SubscriberIdentityValues = Partial<LegalIdentity>;
2899
- /** What a correction replaces and what it writes, holding only the fields that move. */
2900
- interface SubscriberIdentityDelta {
2901
- previous: SubscriberIdentityValues;
2902
- corrected: SubscriberIdentityValues;
2903
- }
2904
- /** What a repository writes for a correction the service has accepted. */
2905
- interface SubscriberCorrectionData {
2906
- /** The new values; a field equal to what is stored is not recorded. */
2907
- corrected: SubscriberIdentityValues;
2908
- reason: string;
2909
- correctedBy: string;
2910
- correctedAt: Date;
2911
- }
2912
- /** One correction of a subscriber's legal identity, as it was recorded. */
2913
- interface SubscriberCorrectionRecord {
2914
- id: string;
2915
- subscriberId: string;
2916
- /** The values the correction replaced. */
2917
- previous: SubscriberIdentityValues;
2918
- /** The values it wrote. */
2919
- corrected: SubscriberIdentityValues;
2920
- reason: string;
2921
- correctedBy: string;
2922
- correctedAt: Date;
2923
- }
2924
- /** The outcome of writing a correction: nothing is recorded when no value moved. */
2925
- interface SubscriberCorrectionResult {
2926
- subscriber: SubscriberRecord;
2927
- correction: SubscriberCorrectionRecord | null;
2928
- }
2929
-
2930
3096
  type ContractLineItemKind = 'plan' | 'bundle' | 'discount';
2931
3097
  type SubscriptionContractStatus = 'active' | 'scheduled' | 'terminated' | 'superseded';
2932
3098
  /**
@@ -4298,6 +4464,160 @@ interface CheckoutOfferRepository {
4298
4464
  consume(id: string, tx?: TransactionContext): Promise<CheckoutOfferRow>;
4299
4465
  }
4300
4466
 
4467
+ /** `ACTIVE` is the one in use; `REPLACED` is one a later payment method took over from. */
4468
+ type SubscriberPaymentMethodStatus = 'ACTIVE' | 'REPLACED';
4469
+ interface SubscriberPaymentMethodRecord extends ConfirmedPaymentMethod {
4470
+ id: string;
4471
+ subscriberId: string;
4472
+ /** The account in `config/saas.yaml#payments.accounts` the references belong to. */
4473
+ gatewayAccount: string;
4474
+ /** The provider of that account when the payment method was confirmed. */
4475
+ provider: string;
4476
+ status: SubscriberPaymentMethodStatus;
4477
+ /** When the gateway confirmed the payment method. */
4478
+ confirmedAt: Date;
4479
+ /** When a later payment method took over; `null` while this one is in use. */
4480
+ replacedAt: Date | null;
4481
+ createdAt: Date;
4482
+ }
4483
+ /** What a repository records for a payment method the gateway confirmed. */
4484
+ interface RecordSubscriberPaymentMethodData extends ConfirmedPaymentMethod {
4485
+ subscriberId: string;
4486
+ gatewayAccount: string;
4487
+ provider: string;
4488
+ confirmedAt: Date;
4489
+ }
4490
+ /**
4491
+ * - `activated`: it is the subscriber's payment method now, and the one it
4492
+ * replaced, if any, is `REPLACED`.
4493
+ * - `already-recorded`: the account's reference was recorded before; nothing
4494
+ * was written, and `method` is the row that holds it.
4495
+ * - `superseded`: the subscriber's payment method in use was confirmed after
4496
+ * this one, so this one is recorded as already replaced — confirmations can
4497
+ * arrive in another order than the forms were filled in.
4498
+ */
4499
+ type RecordSubscriberPaymentMethodOutcome = 'activated' | 'already-recorded' | 'superseded';
4500
+ /**
4501
+ * A change of payment method a tenant started: the gateway session it opened
4502
+ * for the subscriber. A confirmation is recorded only against the setup it
4503
+ * belongs to, so a callback that names another subscriber than the session was
4504
+ * opened for changes nobody's payment method.
4505
+ */
4506
+ interface SubscriberPaymentMethodSetupData {
4507
+ subscriberId: string;
4508
+ gatewayAccount: string;
4509
+ /** The gateway's session, unique within the account. */
4510
+ sessionRef: string;
4511
+ /** The customer the gateway keeps the payment method under. */
4512
+ customerRef: string;
4513
+ startedAt: Date;
4514
+ }
4515
+ /** Which setup a confirmation claims to complete. */
4516
+ interface SubscriberPaymentMethodSetupMatch {
4517
+ gatewayAccount: string;
4518
+ sessionRef: string;
4519
+ subscriberId: string;
4520
+ }
4521
+ interface RecordSubscriberPaymentMethodResult {
4522
+ method: SubscriberPaymentMethodRecord;
4523
+ outcome: RecordSubscriberPaymentMethodOutcome;
4524
+ }
4525
+
4526
+ /** A gateway event, as the log records it. */
4527
+ interface PaymentEventClaim {
4528
+ /** The account the event came from; an event identifier is unique only within it. */
4529
+ gatewayAccount: string;
4530
+ eventId: string;
4531
+ provider: string;
4532
+ /** The gateway session the event is about, where it is about one. */
4533
+ sessionId: string | null;
4534
+ kind: PaymentGatewayEvent['kind'];
4535
+ /** What the event said, without anything that names or reaches a person. */
4536
+ summary: Record<string, unknown>;
4537
+ }
4538
+ /**
4539
+ * The events a gateway sent, each handled once.
4540
+ *
4541
+ * Gateways deliver at least once, so the same event arrives again after a
4542
+ * timeout or a retry. An event is claimed on the transaction that writes what
4543
+ * it changes: when that transaction rolls back, the claim goes with it and the
4544
+ * gateway's retry is handled rather than discarded as a duplicate.
4545
+ */
4546
+ interface PaymentEventLog {
4547
+ /**
4548
+ * Records the event and returns `true`, or returns `false` when this account
4549
+ * already recorded it, writing nothing.
4550
+ *
4551
+ * The duplicate must not raise: on PostgreSQL an error aborts the
4552
+ * transaction it happened on, and the caller's transaction has to stay
4553
+ * usable. An `INSERT … ON CONFLICT DO NOTHING` does both — and when another
4554
+ * transaction holds the same claim uncommitted, it waits for that one and
4555
+ * answers by its outcome.
4556
+ *
4557
+ * A confirmation of a session this account already confirmed is a duplicate
4558
+ * as well, whatever its `eventId`: one session is set up once, and a gateway
4559
+ * may report it through more than one event. `sql/constraints.postgres.sql`
4560
+ * holds that as a second unique index, and the untargeted `DO NOTHING`
4561
+ * answers for it too.
4562
+ */
4563
+ claim(claim: PaymentEventClaim, tx: TransactionContext): Promise<boolean>;
4564
+ /**
4565
+ * Takes the session off a claim whose event changed nothing, so the next
4566
+ * event about that session is handled instead of taken for the duplicate it
4567
+ * is not. The claim itself stays: that one event is never handled twice.
4568
+ *
4569
+ * The session is held from the claim onwards, which is what keeps two
4570
+ * confirmations delivered at once from both taking effect; an event that
4571
+ * turned out to have nothing to do gives it back on the same transaction.
4572
+ */
4573
+ releaseSession(gatewayAccount: string, eventId: string, tx: TransactionContext): Promise<void>;
4574
+ }
4575
+ /**
4576
+ * The payment methods of subscribers, as references at the gateway accounts
4577
+ * that confirmed them.
4578
+ *
4579
+ * A subscriber has at most one `ACTIVE` payment method; the database holds
4580
+ * that, so two confirmations recorded at once for one subscriber end with one.
4581
+ */
4582
+ interface SubscriberPaymentMethodRepository {
4583
+ /**
4584
+ * Records a confirmed payment method for its subscriber, under a lock on the
4585
+ * subscriber so two confirmations take turns. See
4586
+ * `RecordSubscriberPaymentMethodOutcome` for the three outcomes.
4587
+ */
4588
+ recordConfirmed(data: RecordSubscriberPaymentMethodData, tx?: TransactionContext): Promise<RecordSubscriberPaymentMethodResult>;
4589
+ /** The subscriber's payment method in use, or `null` when it has none. */
4590
+ findActive(subscriberId: string, tx?: TransactionContext): Promise<SubscriberPaymentMethodRecord | null>;
4591
+ /**
4592
+ * The payment method an account's reference names, whatever its status.
4593
+ *
4594
+ * The one read that reaches a payment method a newer one replaced, which is
4595
+ * how `@saasicat/persistence-testing` verifies that an implementation keeps
4596
+ * the history rather than overwriting the row (`SC-PRIC-030`).
4597
+ */
4598
+ findByReference(gatewayAccount: string, paymentMethodRef: string, tx?: TransactionContext): Promise<SubscriberPaymentMethodRecord | null>;
4599
+ /** Every account that holds a payment method in use, each once. */
4600
+ accountsInUse(): Promise<string[]>;
4601
+ /**
4602
+ * Records a change of payment method a tenant started, open until its
4603
+ * confirmation completes it.
4604
+ *
4605
+ * One session is one setup: the account and the session are unique together,
4606
+ * and a second setup for a session already recorded raises. A gateway hands
4607
+ * out a session per start, so an adapter that returns one twice is the
4608
+ * defect this refuses to write over.
4609
+ */
4610
+ recordSetup(data: SubscriberPaymentMethodSetupData, tx?: TransactionContext): Promise<void>;
4611
+ /**
4612
+ * Completes the open setup the account, the session and the subscriber all
4613
+ * name, and returns `true` — or returns `false`, writing nothing, when no
4614
+ * open setup matches all three: none was started, it was started for another
4615
+ * subscriber, or it is complete already. A single conditional write, so two
4616
+ * confirmations for one setup complete it once.
4617
+ */
4618
+ completeSetup(match: SubscriberPaymentMethodSetupMatch, completedAt: Date, tx?: TransactionContext): Promise<boolean>;
4619
+ }
4620
+
4301
4621
  /** Which changes to list. */
4302
4622
  interface SettingsChangeFilter {
4303
4623
  /** Only changes an operator has, or has not, acknowledged. Omitted: both. */
@@ -4451,6 +4771,15 @@ interface SaaSiCatPersistenceTenantBilling {
4451
4771
  subscriptionWritePort: PersistenceProvider<TenantSubscriptionWritePort>;
4452
4772
  usageSnapshotPort?: PersistenceProvider<UsageSnapshotPort>;
4453
4773
  }
4774
+ /**
4775
+ * The record of payment gateway callbacks and the payment methods they confirm
4776
+ * (`payments` in `SaaSiCatModule.forRoot`). Both are written on the transaction
4777
+ * a callback is handled on, so they come from the adapter that runs it.
4778
+ */
4779
+ interface SaaSiCatPersistencePayments {
4780
+ paymentEventLog: PersistenceProvider<PaymentEventLog>;
4781
+ subscriberPaymentMethodRepository: PersistenceProvider<SubscriberPaymentMethodRepository>;
4782
+ }
4454
4783
  /** Read/write backing for the standard SuperAdmin resource pages. */
4455
4784
  interface SaaSiCatPersistenceAdminResources {
4456
4785
  resources: PersistenceProvider<AdminResourcesPort>;
@@ -4485,6 +4814,8 @@ interface SaaSiCatPersistenceAdapter {
4485
4814
  /** Tenant, user, audit and subscription resources for the SuperAdmin UI. */
4486
4815
  adminResources?: SaaSiCatPersistenceAdminResources;
4487
4816
  promo?: SaaSiCatPersistencePromo;
4817
+ /** Payment gateway callbacks and subscriber payment methods. */
4818
+ payments?: SaaSiCatPersistencePayments;
4488
4819
  /** DB hydration of the plan catalog at boot (`PlanCatalogModule`). */
4489
4820
  planCatalogReadSink?: PersistenceProvider<PlanCatalogReadSink>;
4490
4821
  /** One-shot `saas.yaml → DB` import. */
@@ -4750,7 +5081,10 @@ declare const SUBSCRIBER_ERROR_CODES: {
4750
5081
  readonly SUBSCRIBER_ALREADY_EXISTS: "SUBSCRIBER_ALREADY_EXISTS";
4751
5082
  readonly SUBSCRIBER_NOT_FOUND: "SUBSCRIBER_NOT_FOUND";
4752
5083
  readonly SUBSCRIBER_LEGAL_NAME_REQUIRED: "SUBSCRIBER_LEGAL_NAME_REQUIRED";
4753
- /** A detail that has a form — the country, the invoice email — is not in it. Carries `field`. */
5084
+ /**
5085
+ * A detail that has a form — the country, the invoice email — is not in it,
5086
+ * or one sign-up requires — the billing address — is missing. Carries `field`.
5087
+ */
4754
5088
  readonly SUBSCRIBER_DETAIL_INVALID: "SUBSCRIBER_DETAIL_INVALID";
4755
5089
  /** A contact change named a field of the legal identity. Carries `field`. */
4756
5090
  readonly SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "SUBSCRIBER_IDENTITY_NOT_A_CONTACT";
@@ -4789,6 +5123,12 @@ declare const AUTH_ERROR_CODES: {
4789
5123
  /** Neither `tenantId` nor `userId` could be resolved from the request. */
4790
5124
  readonly TENANT_CONTEXT_MISSING: "TENANT_CONTEXT_MISSING";
4791
5125
  readonly TENANT_ADMIN_REQUIRED: "TENANT_ADMIN_REQUIRED";
5126
+ /**
5127
+ * The tenant's billing area — its payment method, and later its invoices
5128
+ * and account — needs the billing permission, which the application maps
5129
+ * to its roles and the tenant's administrator holds by default.
5130
+ */
5131
+ readonly BILLING_PERMISSION_REQUIRED: "BILLING_PERMISSION_REQUIRED";
4792
5132
  readonly SUPER_ADMIN_REQUIRED: "SUPER_ADMIN_REQUIRED";
4793
5133
  /** TOTP MFA has never been set up for this user. */
4794
5134
  readonly MFA_NOT_SET_UP: "MFA_NOT_SET_UP";
@@ -4819,6 +5159,24 @@ declare const PROMO_ERROR_CODES: {
4819
5159
  readonly PROMO_MAX_REDEMPTIONS_LOWERED: "PROMO_MAX_REDEMPTIONS_LOWERED";
4820
5160
  };
4821
5161
  type PromoErrorCode = (typeof PROMO_ERROR_CODES)[keyof typeof PROMO_ERROR_CODES];
5162
+ /** Payment methods and the gateways that confirm them. */
5163
+ declare const PAYMENT_ERROR_CODES: {
5164
+ /**
5165
+ * No gateway account takes new payment methods:
5166
+ * `config/saas.yaml#payments.newPaymentMethods` names none.
5167
+ */
5168
+ readonly PAYMENTS_NOT_CONFIGURED: "PAYMENTS_NOT_CONFIGURED";
5169
+ /** A callback arrived for an account `config/saas.yaml#payments.accounts` does not name. Carries `account`. */
5170
+ readonly PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "PAYMENT_GATEWAY_ACCOUNT_UNKNOWN";
5171
+ /** A callback the gateway did not send: its signature does not verify. */
5172
+ readonly PAYMENT_CALLBACK_REJECTED: "PAYMENT_CALLBACK_REJECTED";
5173
+ /**
5174
+ * A success or cancel URL at an origin `config/saas.yaml#payments.returnUrlOrigins`
5175
+ * does not name. Carries `field`.
5176
+ */
5177
+ readonly PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED";
5178
+ };
5179
+ type PaymentErrorCode = (typeof PAYMENT_ERROR_CODES)[keyof typeof PAYMENT_ERROR_CODES];
4822
5180
  /** Codes of the settings record (`GET /admin/settings`, the acknowledgement). */
4823
5181
  declare const SETTINGS_ERROR_CODES: {
4824
5182
  /** No recorded change has this id, or the installation keeps no record at all. */
@@ -4835,6 +5193,20 @@ type SettingsErrorCode = (typeof SETTINGS_ERROR_CODES)[keyof typeof SETTINGS_ERR
4835
5193
  declare const PLATFORM_ERROR_CODES: {
4836
5194
  /** No recorded change has this id, or the installation keeps no record at all. */
4837
5195
  readonly SETTINGS_CHANGE_NOT_FOUND: "SETTINGS_CHANGE_NOT_FOUND";
5196
+ /**
5197
+ * No gateway account takes new payment methods:
5198
+ * `config/saas.yaml#payments.newPaymentMethods` names none.
5199
+ */
5200
+ readonly PAYMENTS_NOT_CONFIGURED: "PAYMENTS_NOT_CONFIGURED";
5201
+ /** A callback arrived for an account `config/saas.yaml#payments.accounts` does not name. Carries `account`. */
5202
+ readonly PAYMENT_GATEWAY_ACCOUNT_UNKNOWN: "PAYMENT_GATEWAY_ACCOUNT_UNKNOWN";
5203
+ /** A callback the gateway did not send: its signature does not verify. */
5204
+ readonly PAYMENT_CALLBACK_REJECTED: "PAYMENT_CALLBACK_REJECTED";
5205
+ /**
5206
+ * A success or cancel URL at an origin `config/saas.yaml#payments.returnUrlOrigins`
5207
+ * does not name. Carries `field`.
5208
+ */
5209
+ readonly PAYMENT_RETURN_URL_NOT_ALLOWED: "PAYMENT_RETURN_URL_NOT_ALLOWED";
4838
5210
  readonly PENDING_REGISTRATION_NOT_FOUND: "PENDING_REGISTRATION_NOT_FOUND";
4839
5211
  readonly PENDING_REGISTRATION_EXPIRED: "PENDING_REGISTRATION_EXPIRED";
4840
5212
  readonly INVALID_REGISTRATION_STATE: "INVALID_REGISTRATION_STATE";
@@ -4860,7 +5232,10 @@ declare const PLATFORM_ERROR_CODES: {
4860
5232
  readonly SUBSCRIBER_ALREADY_EXISTS: "SUBSCRIBER_ALREADY_EXISTS";
4861
5233
  readonly SUBSCRIBER_NOT_FOUND: "SUBSCRIBER_NOT_FOUND";
4862
5234
  readonly SUBSCRIBER_LEGAL_NAME_REQUIRED: "SUBSCRIBER_LEGAL_NAME_REQUIRED";
4863
- /** A detail that has a form — the country, the invoice email — is not in it. Carries `field`. */
5235
+ /**
5236
+ * A detail that has a form — the country, the invoice email — is not in it,
5237
+ * or one sign-up requires — the billing address — is missing. Carries `field`.
5238
+ */
4864
5239
  readonly SUBSCRIBER_DETAIL_INVALID: "SUBSCRIBER_DETAIL_INVALID";
4865
5240
  /** A contact change named a field of the legal identity. Carries `field`. */
4866
5241
  readonly SUBSCRIBER_IDENTITY_NOT_A_CONTACT: "SUBSCRIBER_IDENTITY_NOT_A_CONTACT";
@@ -5089,6 +5464,12 @@ declare const PLATFORM_ERROR_CODES: {
5089
5464
  /** Neither `tenantId` nor `userId` could be resolved from the request. */
5090
5465
  readonly TENANT_CONTEXT_MISSING: "TENANT_CONTEXT_MISSING";
5091
5466
  readonly TENANT_ADMIN_REQUIRED: "TENANT_ADMIN_REQUIRED";
5467
+ /**
5468
+ * The tenant's billing area — its payment method, and later its invoices
5469
+ * and account — needs the billing permission, which the application maps
5470
+ * to its roles and the tenant's administrator holds by default.
5471
+ */
5472
+ readonly BILLING_PERMISSION_REQUIRED: "BILLING_PERMISSION_REQUIRED";
5092
5473
  readonly SUPER_ADMIN_REQUIRED: "SUPER_ADMIN_REQUIRED";
5093
5474
  /** TOTP MFA has never been set up for this user. */
5094
5475
  readonly MFA_NOT_SET_UP: "MFA_NOT_SET_UP";
@@ -5109,7 +5490,7 @@ declare const PLATFORM_ERROR_CODES: {
5109
5490
  /** Email already taken (mapped from `PlatformUserExistsError`). */
5110
5491
  readonly EMAIL_EXISTS: "EMAIL_EXISTS";
5111
5492
  };
5112
- type PlatformErrorCode = SetupErrorCode | AuthErrorCode | PromoErrorCode | CatalogErrorCode | BillingErrorCode | ContractErrorCode | SubscriberErrorCode | RegistrationErrorCode | SettingsErrorCode;
5493
+ type PlatformErrorCode = SetupErrorCode | AuthErrorCode | PromoErrorCode | CatalogErrorCode | BillingErrorCode | ContractErrorCode | SubscriberErrorCode | RegistrationErrorCode | PaymentErrorCode | SettingsErrorCode;
5113
5494
  /**
5114
5495
  * Shape of a coded error response.
5115
5496
  *
@@ -5330,7 +5711,25 @@ interface PendingRegistration {
5330
5711
  billingCycle: 'MONTHLY' | 'YEARLY' | null;
5331
5712
  /** Plaintext code (UI display). Validation runs fresh every time. */
5332
5713
  appliedPromoCode: string | null;
5714
+ /**
5715
+ * The billing address and tax identifiers step 4 asks for, which the
5716
+ * subscriber is created with. The address is required before a payment
5717
+ * method is set up; the tax identifiers stay optional.
5718
+ */
5719
+ addressLine1: string | null;
5720
+ addressLine2: string | null;
5721
+ postalCode: string | null;
5722
+ city: string | null;
5723
+ /** ISO 3166-1 alpha-2, upper case. */
5724
+ country: string | null;
5725
+ vatId: string | null;
5726
+ taxNumber: string | null;
5727
+ /** The gateway's session for the payment method, unique within `checkoutGatewayAccount`. */
5333
5728
  checkoutSessionId: string | null;
5729
+ /** The account in `config/saas.yaml#payments.accounts` the session was opened at. */
5730
+ checkoutGatewayAccount: string | null;
5731
+ /** The customer the gateway created for the sign-up, reused when step 4 is repeated. */
5732
+ gatewayCustomerRef: string | null;
5334
5733
  checkoutStartedAt: Date | null;
5335
5734
  expiresAt: Date;
5336
5735
  createdAt: Date;
@@ -5362,7 +5761,16 @@ interface PendingRegistrationUpdateInput {
5362
5761
  configJson?: RegistrationConfigSelection | null;
5363
5762
  billingCycle?: 'MONTHLY' | 'YEARLY' | null;
5364
5763
  appliedPromoCode?: string | null;
5764
+ addressLine1?: string | null;
5765
+ addressLine2?: string | null;
5766
+ postalCode?: string | null;
5767
+ city?: string | null;
5768
+ country?: string | null;
5769
+ vatId?: string | null;
5770
+ taxNumber?: string | null;
5365
5771
  checkoutSessionId?: string | null;
5772
+ checkoutGatewayAccount?: string | null;
5773
+ gatewayCustomerRef?: string | null;
5366
5774
  checkoutStartedAt?: Date | null;
5367
5775
  expiresAt?: Date;
5368
5776
  }
@@ -5370,14 +5778,24 @@ interface PendingRegistrationUpdateInput {
5370
5778
  interface PendingRegistrationRepository {
5371
5779
  findById(id: string): Promise<PendingRegistration | null>;
5372
5780
  findByEmail(email: string): Promise<PendingRegistration | null>;
5373
- /** Webhook lookup: finds the pending record for the provider session. */
5374
- findByCheckoutSession(sessionId: string): Promise<PendingRegistration | null>;
5781
+ /**
5782
+ * Finds the pending record a gateway session belongs to. A session
5783
+ * identifier is unique only within its account, so both are matched.
5784
+ */
5785
+ findByCheckoutSession(gatewayAccount: string, sessionId: string): Promise<PendingRegistration | null>;
5375
5786
  /**
5376
5787
  * Cleanup lookup: all pending records with `expiresAt < now`, max
5377
5788
  * `limit` entries per call (batch protection). Ordering irrelevant, the
5378
5789
  * cron service iterates sequentially.
5379
5790
  */
5380
5791
  findExpired(now: Date, limit: number): Promise<PendingRegistration[]>;
5792
+ /**
5793
+ * Every gateway account a checkout session is still open at: the distinct
5794
+ * `checkoutGatewayAccount` of records in `CHECKOUT_STARTED` whose
5795
+ * `expiresAt` is after `now`. The start refuses when one of them is no
5796
+ * longer configured, because that sign-up's confirmation could not arrive.
5797
+ */
5798
+ findOpenCheckoutAccounts(now: Date): Promise<string[]>;
5381
5799
  create(input: PendingRegistrationCreateInput): Promise<PendingRegistration>;
5382
5800
  update(id: string, input: PendingRegistrationUpdateInput): Promise<PendingRegistration>;
5383
5801
  /**
@@ -5387,7 +5805,13 @@ interface PendingRegistrationRepository {
5387
5805
  * value is the authoritative threshold for the lockout check.
5388
5806
  */
5389
5807
  incrementOtpAttemptCount(id: string): Promise<number>;
5390
- delete(id: string): Promise<void>;
5808
+ /**
5809
+ * Removes the record. With `tx` it is removed on that transaction and comes
5810
+ * back with its rollback — an activation deletes the sign-up on the
5811
+ * transaction that creates the tenant, so a sign-up is either still waiting
5812
+ * or activated, never both.
5813
+ */
5814
+ delete(id: string, tx?: TransactionContext): Promise<void>;
5391
5815
  }
5392
5816
  /** Adapter port: detects whether a full user account (verified) exists for this email. */
5393
5817
  interface UserAccountLookup {
@@ -5397,38 +5821,6 @@ interface UserAccountLookup {
5397
5821
  interface SlugAvailabilityCheck {
5398
5822
  isSlugAvailable(slug: string): Promise<boolean>;
5399
5823
  }
5400
- /** Wire format of a created checkout session (provider-agnostic). */
5401
- interface CheckoutSession {
5402
- /** Provider-specific session ID (e.g. Stripe `cs_…`). */
5403
- sessionId: string;
5404
- /** Payment URL to be opened by the frontend. */
5405
- checkoutUrl: string;
5406
- /** Optional: provider name (`stripe`, `dev-stub`) for logging/audit. */
5407
- provider?: string;
5408
- }
5409
- type PaymentEventStatus = 'SUCCEEDED' | 'FAILED';
5410
- /**
5411
- * Adapter port: idempotency log for payment webhooks. Stripe (and most
5412
- * other providers) deliver events at-least-once — the service calls
5413
- * `tryClaim` as an atomic race guard BEFORE it triggers the final
5414
- * activation.
5415
- */
5416
- interface PaymentEventLog {
5417
- /**
5418
- * Tries to insert an event record via `@unique` INSERT. Returns
5419
- * `true` if it was newly created (webhook seen for the first time),
5420
- * `false` if it already exists (duplicate → silently drop).
5421
- *
5422
- * Implementations must return a DB unique-constraint-violation error
5423
- * (Prisma P2002) as `false`.
5424
- */
5425
- tryClaim(eventId: string, payload: {
5426
- provider: string;
5427
- sessionId: string | null;
5428
- status: PaymentEventStatus;
5429
- rawPayload?: unknown;
5430
- }): Promise<boolean>;
5431
- }
5432
5824
  interface FinalActivationResult {
5433
5825
  userId: string;
5434
5826
  tenantId: string;
@@ -5440,33 +5832,31 @@ interface FinalActivationResult {
5440
5832
  */
5441
5833
  subscriberId: string;
5442
5834
  }
5835
+ /** The transaction a sign-up is activated on. */
5836
+ interface RegistrationActivation {
5837
+ /**
5838
+ * Opened by the platform, which has already claimed the gateway's
5839
+ * confirmation on it and records the confirmed payment method on it once
5840
+ * `activate` returns. Every row the activation writes goes through it, so
5841
+ * a failure anywhere rolls back all of it — the claim included, and the
5842
+ * gateway's retry is handled rather than discarded as a duplicate.
5843
+ */
5844
+ tx: TransactionContext;
5845
+ }
5443
5846
  /**
5444
- * Adapter port: orchestrates the final creation of User + Tenant +
5445
- * Subscriber + Subscription after successful payment. App-specific — each app
5446
- * has its own schema (e.g. Tenant + TenantUser + Role + UserRole +
5447
- * Subscription).
5847
+ * Adapter port: creates User + Tenant + Subscriber + Subscription once the
5848
+ * gateway confirmed the sign-up's payment method. App-specific — each app has
5849
+ * its own schema (e.g. Tenant + TenantUser + Role + UserRole + Subscription).
5448
5850
  *
5449
- * Implementations MUST perform the creation in a DB transaction so that
5450
- * partial creations are fully rolled back on errors. The subscriber is created
5451
- * there too, before any contract: `SubscriberService.createForTenant(tenantId,
5452
- * details, tx)`, or `CheckoutOfferService.conclude` with `subscriber`, which
5453
- * creates it on the transaction it concludes the offer on.
5851
+ * Implementations write on `activation.tx` and open no transaction of their
5852
+ * own: a write beside it would survive the rollback that undoes the rest. The
5853
+ * subscriber is created there too, before any contract:
5854
+ * `SubscriberService.createForTenant(tenantId, subscriberFromRegistration(pending),
5855
+ * activation.tx)`, or `CheckoutOfferService.conclude` with `subscriber` and
5856
+ * that transaction.
5454
5857
  */
5455
5858
  interface ActivationOrchestrator {
5456
- activate(pending: PendingRegistration): Promise<FinalActivationResult>;
5457
- }
5458
- interface HandlePaymentEventInput {
5459
- eventId: string;
5460
- sessionId: string | null;
5461
- provider: string;
5462
- status: PaymentEventStatus;
5463
- rawPayload?: unknown;
5464
- }
5465
- type HandlePaymentEventReason = 'ALREADY_PROCESSED' | 'PAYMENT_NOT_SUCCEEDED' | 'MISSING_SESSION_ID' | 'PENDING_REGISTRATION_NOT_FOUND' | 'INVALID_STATE';
5466
- interface HandlePaymentEventResult {
5467
- activated: boolean;
5468
- reason?: HandlePaymentEventReason;
5469
- result?: FinalActivationResult;
5859
+ activate(pending: PendingRegistration, activation: RegistrationActivation): Promise<FinalActivationResult>;
5470
5860
  }
5471
5861
  interface CleanupResult {
5472
5862
  /** Number of deleted PendingRegistration records. */
@@ -5477,7 +5867,7 @@ interface CleanupResult {
5477
5867
  */
5478
5868
  moreAvailable: boolean;
5479
5869
  }
5480
- type RegistrationAuditEventType = 'REGISTRATION_STARTED' | 'REGISTRATION_NEUTRAL_ACTIVE_USER' | 'REGISTRATION_NEUTRAL_REPLAY' | 'REGISTRATION_NEUTRAL_EXPIRED' | 'OTP_VERIFIED' | 'OTP_VERIFY_FAILED' | 'OTP_RESEND_REQUESTED' | 'OTP_RATE_LIMIT_HIT' | 'PLAN_SELECTED' | 'CHECKOUT_STARTED' | 'PAYMENT_RECEIVED' | 'PAYMENT_DUPLICATE_IGNORED' | 'PAYMENT_FAILED' | 'ACTIVATION_COMPLETED' | 'LOGIN_SUCCEEDED' | 'LOGIN_INVALID_CREDENTIALS' | 'LOGIN_ONBOARDING_REQUIRED';
5870
+ type RegistrationAuditEventType = 'REGISTRATION_STARTED' | 'REGISTRATION_NEUTRAL_ACTIVE_USER' | 'REGISTRATION_NEUTRAL_REPLAY' | 'REGISTRATION_NEUTRAL_EXPIRED' | 'OTP_VERIFIED' | 'OTP_VERIFY_FAILED' | 'OTP_RESEND_REQUESTED' | 'OTP_RATE_LIMIT_HIT' | 'PLAN_SELECTED' | 'CHECKOUT_STARTED' | 'PAYMENT_RECEIVED' | 'PAYMENT_FAILED' | 'ACTIVATION_COMPLETED' | 'LOGIN_SUCCEEDED' | 'LOGIN_INVALID_CREDENTIALS' | 'LOGIN_ONBOARDING_REQUIRED';
5481
5871
  /**
5482
5872
  * Context information that the audit layer records per event.
5483
5873
  * IP is expected as a hashed fingerprint — no plaintext IPs in the
@@ -5724,6 +6114,8 @@ interface PendingRegistrationSnapshot {
5724
6114
  config: RegistrationConfigSelection | null;
5725
6115
  billingCycle: 'MONTHLY' | 'YEARLY' | null;
5726
6116
  appliedPromoCode: string | null;
6117
+ /** The billing details step 4 already took, to fill its form again. */
6118
+ billingDetails: RegistrationBillingDetails | null;
5727
6119
  checkoutSessionId: string | null;
5728
6120
  }
5729
6121
  interface ResumeRegistrationResult {
@@ -5732,27 +6124,6 @@ interface ResumeRegistrationResult {
5732
6124
  nextStep: RegistrationStep;
5733
6125
  snapshot: PendingRegistrationSnapshot;
5734
6126
  }
5735
- /** Adapter port: payment provider (Stripe, Dev-Stub, Mollie, ...). */
5736
- interface PaymentProvider {
5737
- /**
5738
- * Creates a checkout session at the payment provider and returns the URL
5739
- * that the frontend should redirect to.
5740
- *
5741
- * @param params.pendingRegistrationId Stored as `client_reference_id` (or similar)
5742
- * in the provider — the webhook needs it to link back.
5743
- * @param params.planId The chosen plan (Stripe price/product mapping lives
5744
- * in the adapter).
5745
- * @param params.successUrl Where to go after successful payment.
5746
- * @param params.cancelUrl Where to go on cancellation.
5747
- */
5748
- createCheckoutSession(params: {
5749
- pendingRegistrationId: string;
5750
- planId: string;
5751
- email: string;
5752
- successUrl: string;
5753
- cancelUrl: string;
5754
- }): Promise<CheckoutSession>;
5755
- }
5756
6127
  /** Adapter port: OTP delivery via email (or another channel). */
5757
6128
  interface RegistrationOtpDelivery {
5758
6129
  sendVerificationOtp(params: {
@@ -5818,9 +6189,27 @@ interface SelectPlanResult {
5818
6189
  nextStep: RegistrationStep;
5819
6190
  selectedPlanId: string;
5820
6191
  }
6192
+ /**
6193
+ * The billing address and tax identifiers a sign-up gives in step 4, which its
6194
+ * subscriber is created with. The address is required; the tax identifiers
6195
+ * stay optional until the tax adapter says when one is needed.
6196
+ */
6197
+ interface RegistrationBillingDetails {
6198
+ addressLine1: string;
6199
+ addressLine2?: string | null;
6200
+ postalCode: string;
6201
+ city: string;
6202
+ /** ISO 3166-1 alpha-2, upper case. */
6203
+ country: string;
6204
+ vatId?: string | null;
6205
+ taxNumber?: string | null;
6206
+ }
5821
6207
  interface StartCheckoutInput {
5822
6208
  pendingRegistrationId: string;
6209
+ billingDetails: RegistrationBillingDetails;
6210
+ /** Where the gateway's form sends the person once the payment method is set up. */
5823
6211
  successUrl: string;
6212
+ /** Where the gateway's form sends the person who leaves it. */
5824
6213
  cancelUrl: string;
5825
6214
  }
5826
6215
  interface StartCheckoutResult {
@@ -5967,10 +6356,45 @@ declare function identityCorrectionDelta(current: Pick<SubscriberRecord, Subscri
5967
6356
  declare function contractPartiesOf(subscriber: SubscriberRecord, issuer: PlanCatalog['issuer']): SubscriptionContractParties;
5968
6357
  /**
5969
6358
  * The subscriber a completed sign-up is created with: the name the tenant was
5970
- * registered under as its legal name, and the address the registration was
5971
- * verified with as its invoice email. Sign-up collects nothing more yet.
6359
+ * registered under as its legal name, the address the registration was
6360
+ * verified with as its invoice email, and the billing address and tax
6361
+ * identifiers step 4 took.
6362
+ */
6363
+ declare function subscriberFromRegistration(pending: Pick<PendingRegistration, 'tenantName' | 'email' | 'addressLine1' | 'addressLine2' | 'postalCode' | 'city' | 'country' | 'vatId' | 'taxNumber'>): NewSubscriberDetails;
6364
+
6365
+ /** The payment method types, in the order a form offers them. */
6366
+ declare const PAYMENT_METHOD_TYPES: readonly PaymentMethodType[];
6367
+ /** A `subscriber_payment_methods` row as either adapter reads it back. */
6368
+ interface CanonicalSubscriberPaymentMethodRow {
6369
+ id: string;
6370
+ subscriberId: string;
6371
+ gatewayAccount: string;
6372
+ provider: string;
6373
+ customerRef: string;
6374
+ paymentMethodRef: string;
6375
+ type: string;
6376
+ brand: string | null;
6377
+ last4: string;
6378
+ expiryMonth: number | null;
6379
+ expiryYear: number | null;
6380
+ country: string | null;
6381
+ bankCode: string | null;
6382
+ mandateReference: string | null;
6383
+ status: string;
6384
+ confirmedAt: Date;
6385
+ replacedAt: Date | null;
6386
+ createdAt: Date;
6387
+ }
6388
+ /**
6389
+ * Reads a row back as a record.
6390
+ *
6391
+ * The two text columns with a closed set of values are checked rather than
6392
+ * cast: a value written by hand or by an older release would otherwise reach a
6393
+ * screen as a type nobody handles.
5972
6394
  */
5973
- declare function subscriberFromRegistration(pending: Pick<PendingRegistration, 'tenantName' | 'email'>): NewSubscriberDetails;
6395
+ declare function toSubscriberPaymentMethodRecord(row: CanonicalSubscriberPaymentMethodRow): SubscriberPaymentMethodRecord;
6396
+ /** The columns a confirmed payment method is written with, and nothing a caller added beside them. */
6397
+ declare function subscriberPaymentMethodColumns(data: RecordSubscriberPaymentMethodData): Omit<CanonicalSubscriberPaymentMethodRow, 'id' | 'status' | 'replacedAt' | 'createdAt'>;
5974
6398
 
5975
6399
  /** A `subscription_contracts` row as either adapter reads it back. */
5976
6400
  interface CanonicalContractRow {
@@ -6101,4 +6525,4 @@ declare function resolveErrorMessage(body: ResolvableErrorBody, overrides?: Part
6101
6525
 
6102
6526
  declare const ERROR_MESSAGES_DE: Record<PlatformErrorCode, string>;
6103
6527
 
6104
- export { ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES, AUTH_ERROR_CODES, type ActionKey, type ActivationOrchestrator, type ActivePlanVersionWhere, type ActivePlanVersionWhereWithEndsAt, type ActiveVersionWhere, type ActiveVersionWhereWithEndsAt, type ActorTag, type AdminActor, type AdminAuditListFilter, type AdminManifest, type AdminResourcesPort, type AdminSubscriptionListRow, type AdminTenantDetail, type AdminTenantListFilter, type AdminTenantListRow, type AdminTenantStateResult, type AdminUserListFilter, type AdminUserListRow, type AppliedSettingsPort, type AppliedSettingsRecord, type AppliedSettingsValues, type ApplyOnboardingSelectionInput, type ApplyOnboardingSelectionResult, type ApprovedCatalogKeys, type AuditActionDef, type AuditEntry, type AuditPort, type AuditQuery, type AuditQueryPort, type AuditStatsPort, type AuditStatsSnapshot, type AuthErrorCode, BILLING_ERROR_CODES, BUNDLE_PRICE_LOOKUP_LIMIT, type BillingCycle, type BillingErrorCode, type BundleAvailabilityState, type BundleCompatibility, type BundleFeatureShape, type BundleListFilter, type BundlePricingOverride, type BundleRepository, type BundleRow, type BundleVersionFields, type BundleVersionMutationResult, type BundleVersionRow, CATALOGUE_KEYS, CATALOG_ERROR_CODES, CONTRACT_ERROR_CODES, type CancelSubscriptionBundleData, type CancelSubscriptionInput, type CancelSubscriptionResult, type CancellationNoticePeriods, type CanonicalContractLineItemRow, type CanonicalContractRow, type CanonicalPlanRow, type CanonicalPlanVersionRow, type CanonicalSubscriberCorrectionRow, type CanonicalSubscriberRow, type CapabilityCatalogEntryRow, type CapabilityCodeStatus, type CapabilityKey, type CapabilityKind, type CatalogEntryFilter, type CatalogEntryI18n, type CatalogEntryI18nFields, type CatalogEntryRepository, type CatalogErrorCode, type ChangeDirection, type CheckoutOfferFilter, type CheckoutOfferLineItem, type CheckoutOfferLineItemKind, type CheckoutOfferPriceBreakdown, type CheckoutOfferPromoCodeSnapshot, type CheckoutOfferPromotionSnapshot, type CheckoutOfferRepository, type CheckoutOfferRow, type CheckoutOfferSelection, type CheckoutOfferSelectionUpdate, type CheckoutOfferStatus, type CheckoutSession, type CleanupResult, type CliUserRow, type ComponentKey, type ConfiguratorCatalog, type ConfiguratorMarketingProvider, type ConfiguratorModel, type ConfiguratorPlanMarketing, type ConfiguratorPlanVersionRow, type ConfiguratorPriceBreakdown, type ConfiguratorSourcesLookup, type ContractErrorCode, type ContractIssuerParty, type ContractLineItemKind, type ContractLineItemRecord, type ContractSubscriberParty, type CreateBundleData, type CreateBundleVersionDraftData, type CreateCheckoutOfferData, type CreateMarketingProjectionData, type CreatePlanData, type CreatePlanVersionDraftData, type CreatePromoCodeData, type CreatePromoCodeRequest, type CreatePromotionData, type CreateSubscriberData, type CreateSubscriptionBundleData, type CreateSubscriptionContractData, type CreateSuperAdminCliInput, type CreateTenantInput, type DiffResult, type DiscoveredCapability, type DiscoveredFeature, type DiscoveredQuota, type DiscoveredQuotaPolicy, type DiscoveryCodeStatus, type DiscoverySnapshot, type DiscoveryStatus, ERROR_MESSAGES_DE, ERROR_MESSAGES_EN, type EffectiveLimitsSnapshot, type EmailPort, type ErrorMessageParams, FEATURE_NOT_LICENSED, type FeatureCatalogEntryRow, type FeatureDef, type FeatureKey, type FeatureNotLicensedBody, type FeatureRequiresIndex, type FeatureTier, type FeatureUiMeta, type FeatureUiRegistry, type FinalActivationResult, type FirstTimeCustomerCheck, type HandlePaymentEventInput, type HandlePaymentEventReason, type HandlePaymentEventResult, type ImmediatePlanChangeInput, type InvoiceLineItemSnapshot, type KpiCardDef, type KpiDisplayHint, type LegalIdentity, MARKETING_PRIORITY_MAX, MARKETING_PRIORITY_MIN, type ManifestAccessPort, type ManifestContribution, type MarketingProjectionFilter, type MarketingProjectionRepository, type MarketingProjectionRow, type MarketingSettingsRepository, type MarketingSettingsRow, type MarketingTargetType, type MarketingTopFeature, type MfaPort, type NewContractLineItemData, type NewSettingsChange, type NewSubscriberDetails, type NewSubscriptionContractData, OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, type OnboardingPromoRedemption, type OnboardingSelectionRequest, type OnboardingSelectionResponse, PASSWORD_RESET_TTL_MINUTES, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, PLATFORM_ERROR_CODES, PROMO_ERROR_CODES, type Paginated, type PartyAddress, type PasswordHasher, type PasswordResetCliResult, type PaymentEventLog, type PaymentEventStatus, type PaymentProvider, type PendingRegistration, type PendingRegistrationCreateInput, type PendingRegistrationRepository, type PendingRegistrationSnapshot, type PendingRegistrationUpdateInput, type PersistenceCapabilities, PersistenceCapabilityError, type PersistenceClassRef, type PersistenceInjectionToken, type PersistenceProvider, type PlanCatalog, type PlanCatalogApp, type PlanCatalogImportReport, type PlanCatalogImportSink, type PlanCatalogIssuer, type PlanCatalogLookup, type PlanCatalogMarketing, type PlanCatalogNotifications, type PlanCatalogReadSink, type PlanCatalogReadSnapshot, type PlanCatalogSettings, type PlanCatalogSubscribers, type PlanCatalogTenantBilling, type PlanDef, type PlanId, type PlanListFilter, type PlanRepository, type PlanRow, type PlanVersion, type PlanVersionFields, type PlanVersionMappingFields, type PlanVersionMutationResult, type PlanVersionRecord, type PlanVersionRepository, type PlanVersionRow, type PlatformErrorBody, type PlatformErrorCode, type PlatformRole, type PlatformUserDto, PlatformUserExistsError, type ProjectPageDef, type PromoCode, type PromoCodeDurationType, type PromoCodeFilter, type PromoCodeRecord, type PromoCodeRedemption, type PromoCodeRedemptionListItem, type PromoCodeRedemptionRecord, type PromoCodeRedemptionRepository, type PromoCodeRedemptionStatus, type PromoCodeRepository, type PromoCodeStatsPort, type PromoCodeStatsSnapshot, type PromoCodeStatus, type PromoCodeValidationLog, type PromoCodeValidationLogRepository, type PromoCodeValidationResult, type PromoCodeValueType, type PromoErrorCode, type PromoPreviewInvalidReason, type PromoPreviewRequest, type PromoPreviewResponse, type PromoPreviewValidResponse, type PromoRevenueDeductionAggregator, type PromoSubscriptionLookup, type PromotionBillingCycle, type PromotionI18n, type PromotionI18nFields, type PromotionRepository, type PromotionResult, type PromotionRow, type PromotionStatus, type PromotionTargetType, type PromotionType, type PromotionValue, type PublicBootResponse, type PublicComparisonRow, type PublicMarketingBundle, type PublicMarketingCatalogResponse, type PublicMarketingPlan, type PublicMarketingPromo, type PublicSignupPlan, type PublishBundleVersionData, type PublishBundleVersionMeta, type PublishPlanVersionData, type QuotaCatalogEntryRow, type QuotaEnforcementMode, type QuotaKey, type QuotaProvider, REGISTRATION_ERROR_CODES, REGISTRATION_RESUME_TTL_MINUTES, REGISTRATION_STEP_BY_STATUS, type ReassignTenantAdminCliResult, type RecommendablePlan, type RedeemPromoInTransactionCallback, type RegistrationAuditContext, type RegistrationAuditEvent, type RegistrationAuditEventType, type RegistrationAuditLogger, type RegistrationConfigSelection, type RegistrationConfiguratorLookup, type RegistrationErrorCode, type RegistrationOtpDelivery, type RegistrationPromoPreview, type RegistrationResumeDelivery, type RegistrationResumeTokenSigner, type RegistrationStatus, type RegistrationStep, type RequiredCapabilities, type ResolvableErrorBody, type ResumeRegistrationInput, type ResumeRegistrationResult, type ReviewCatalogEntryData, type RlsBypassPort, SETTINGS_ERROR_CODES, SETUP_ERROR_CODES, SUBSCRIBER_ERROR_CODES, SUBSCRIBER_IDENTITY_FIELDS, type SaaSiCatPersistenceAdapter, type SaaSiCatPersistenceAdminResources, type SaaSiCatPersistenceCatalog, type SaaSiCatPersistenceCore, type SaaSiCatPersistenceEntitlement, type SaaSiCatPersistencePromo, type SaaSiCatPersistenceTenantBilling, type SaveRegistrationConfigInput, type SaveRegistrationConfigResult, type ScheduledPlanChangeInput, type SelectPlanInput, type SelectPlanResult, type SelectableBundleShape, type SelfServiceBlockedPlans, type SetCatalogEntryReviewData, type SettingsChangeFilter, type SettingsChangeRecord, type SettingsDifference, type SettingsErrorCode, type SetupConfirmMfaRequest, type SetupConfirmMfaResponse, type SetupErrorCode, type SetupRequest, type SetupResult, type SetupStatusResponse, type SlugAvailabilityCheck, type StandardPageDef, type StandardPageKey, type StartCheckoutInput, type StartCheckoutResult, type StartRegistrationInput, type StartRegistrationResult, type StoredBundleStem, type StrictModeWarning, type StrictModeWarningCode, type SubscriberContact, type SubscriberContactChange, type SubscriberCorrectionData, type SubscriberCorrectionRecord, type SubscriberCorrectionResult, type SubscriberDetails, type SubscriberErrorCode, type SubscriberIdentityCorrection, type SubscriberIdentityDelta, type SubscriberIdentityField, type SubscriberIdentityValues, type SubscriberRecord, type SubscriberRepository, type Subscription, type SubscriptionBundleRecord, type SubscriptionBundleRepository, type SubscriptionBundleView, type SubscriptionContractFilter, type SubscriptionContractInvoiceSnapshot, type SubscriptionContractParties, type SubscriptionContractPriceSnapshot, type SubscriptionContractRecord, type SubscriptionContractRepository, type SubscriptionContractStatus, type SubscriptionRecord, type SubscriptionRepository, type SubscriptionStatsPort, type SubscriptionStatsSnapshot, type SubscriptionStatus, type SubscriptionUsagePort, type SubscriptionUsageRecord, type SuperAdminProvisioningPort, type SyncDiscoveryResult, type TenantActionDef, type TenantColumnDef, type TenantDto, type TenantListFilter, type TenantPort, type TenantSubscriptionWritePort, type TerminateSubscriptionContractData, type TopPromoCode, type TransactionContext, type TransactionRunner, type UpdateBundleData, type UpdateBundleVersionDraftData, type UpdateCatalogEntryBaseData, type UpdateCatalogEntryI18nData, type UpdateCheckoutOfferData, type UpdateMarketingProjectionData, type UpdateMarketingSettingsData, type UpdatePlanData, type UpdatePlanVersionDraftData, type UpdatePromoCodeData, type UpdatePromoCodeRequest, type UpdatePromotionData, type UpsellOffer, type UpsellOfferResolver, type UpsertCapabilityEntryData, type UpsertFeatureCatalogEntryInput, type UpsertFeatureEntryData, type UpsertPlanInput, type UpsertPlanVersionInput, type UpsertQuotaEntryData, type UpsertResult, type UsageSnapshotPort, type UserAccountLookup, type UserListFilter, type UserManagementPort, type UserPort, type VerifyRegistrationOtpResult, type VersionChange, type VersionChangeDirection, type VersionEditability, type VersionEditableReason, type VersionedEntityBase, applyPromo, assertPersistenceCapabilities, buildActivePlanVersionWhere, buildActiveVersionWhere, buildFeatureRequiresIndex, bundleDraftDefaults, bundleStemDefaults, canonicalJson, classifyBundleVersionDiff, classifyPlanDiff, collectUnsatisfiedRequires, contractPartiesOf, coverageExcludingSelf, definedFields, diffSettings, formatCustomerNumber, formatErrorMessage, identityCorrectionDelta, isBundleRedundant, isPlatformUserExistsError, isVersionEditable, keepOneRecommended, missingRequiresFor, pickActivePromo, planCatalogSettingsOf, previousUtcDay, promoStatus, readQuotaRecord, readQuotaValue, resolveBundleAvailability, resolveErrorMessage, selectChargeableBundles, settingsSubtreeOf, startOfUtcDay, subscriberFromRegistration, toBundleStemRow, toContractLineItemRecord, toPlanRow, toPlanVersionRow, toSubscriberCorrectionRecord, toSubscriberRecord, toSubscriptionContractRecord };
6528
+ export { ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES, AUTH_ERROR_CODES, type ActionKey, type ActivationOrchestrator, type ActivePlanVersionWhere, type ActivePlanVersionWhereWithEndsAt, type ActiveVersionWhere, type ActiveVersionWhereWithEndsAt, type ActorTag, type AdminActor, type AdminAuditListFilter, type AdminManifest, type AdminResourcesPort, type AdminSubscriptionListRow, type AdminTenantDetail, type AdminTenantListFilter, type AdminTenantListRow, type AdminTenantStateResult, type AdminUserListFilter, type AdminUserListRow, type AppliedSettingsPort, type AppliedSettingsRecord, type AppliedSettingsValues, type ApplyOnboardingSelectionInput, type ApplyOnboardingSelectionResult, type ApprovedCatalogKeys, type AuditActionDef, type AuditEntry, type AuditPort, type AuditQuery, type AuditQueryPort, type AuditStatsPort, type AuditStatsSnapshot, type AuthErrorCode, BILLING_ERROR_CODES, BUNDLE_PRICE_LOOKUP_LIMIT, type BillingCycle, type BillingErrorCode, type BundleAvailabilityState, type BundleCompatibility, type BundleFeatureShape, type BundleListFilter, type BundlePricingOverride, type BundleRepository, type BundleRow, type BundleVersionFields, type BundleVersionMutationResult, type BundleVersionRow, CATALOGUE_KEYS, CATALOG_ERROR_CODES, CONTRACT_ERROR_CODES, type CancelSubscriptionBundleData, type CancelSubscriptionInput, type CancelSubscriptionResult, type CancellationNoticePeriods, type CanonicalContractLineItemRow, type CanonicalContractRow, type CanonicalPlanRow, type CanonicalPlanVersionRow, type CanonicalSubscriberCorrectionRow, type CanonicalSubscriberPaymentMethodRow, type CanonicalSubscriberRow, type CapabilityCatalogEntryRow, type CapabilityCodeStatus, type CapabilityKey, type CapabilityKind, type CatalogEntryFilter, type CatalogEntryI18n, type CatalogEntryI18nFields, type CatalogEntryRepository, type CatalogErrorCode, type ChangeDirection, type CheckoutOfferFilter, type CheckoutOfferLineItem, type CheckoutOfferLineItemKind, type CheckoutOfferPriceBreakdown, type CheckoutOfferPromoCodeSnapshot, type CheckoutOfferPromotionSnapshot, type CheckoutOfferRepository, type CheckoutOfferRow, type CheckoutOfferSelection, type CheckoutOfferSelectionUpdate, type CheckoutOfferStatus, type CleanupResult, type CliUserRow, type ComponentKey, type ConfiguratorCatalog, type ConfiguratorMarketingProvider, type ConfiguratorModel, type ConfiguratorPlanMarketing, type ConfiguratorPlanVersionRow, type ConfiguratorPriceBreakdown, type ConfiguratorSourcesLookup, type ConfirmedPaymentMethod, type ContractErrorCode, type ContractIssuerParty, type ContractLineItemKind, type ContractLineItemRecord, type ContractSubscriberParty, type CreateBundleData, type CreateBundleVersionDraftData, type CreateCheckoutOfferData, type CreateMarketingProjectionData, type CreatePlanData, type CreatePlanVersionDraftData, type CreatePromoCodeData, type CreatePromoCodeRequest, type CreatePromotionData, type CreateSubscriberData, type CreateSubscriptionBundleData, type CreateSubscriptionContractData, type CreateSuperAdminCliInput, type CreateTenantInput, type DiffResult, type DiscoveredCapability, type DiscoveredFeature, type DiscoveredQuota, type DiscoveredQuotaPolicy, type DiscoveryCodeStatus, type DiscoverySnapshot, type DiscoveryStatus, ERROR_MESSAGES_DE, ERROR_MESSAGES_EN, type EffectiveLimitsSnapshot, type EmailPort, type ErrorMessageParams, FEATURE_NOT_LICENSED, type FeatureCatalogEntryRow, type FeatureDef, type FeatureKey, type FeatureNotLicensedBody, type FeatureRequiresIndex, type FeatureTier, type FeatureUiMeta, type FeatureUiRegistry, type FinalActivationResult, type FirstTimeCustomerCheck, type ImmediatePlanChangeInput, type InvoiceLineItemSnapshot, type KpiCardDef, type KpiDisplayHint, type LegalIdentity, MARKETING_PRIORITY_MAX, MARKETING_PRIORITY_MIN, type ManifestAccessPort, type ManifestContribution, type MarketingProjectionFilter, type MarketingProjectionRepository, type MarketingProjectionRow, type MarketingSettingsRepository, type MarketingSettingsRow, type MarketingTargetType, type MarketingTopFeature, type MaskedPaymentMethod, type MfaPort, type NewContractLineItemData, type NewSettingsChange, type NewSubscriberDetails, type NewSubscriptionContractData, OTP_RATE_LIMIT_MAX_SENDS, OTP_RATE_LIMIT_WINDOW_MINUTES, OTP_TTL_MINUTES, OTP_VERIFY_MAX_ATTEMPTS, type OnboardingPromoRedemption, type OnboardingSelectionRequest, type OnboardingSelectionResponse, PASSWORD_RESET_TTL_MINUTES, PAYMENT_ERROR_CODES, PAYMENT_METHOD_TYPES, PENDING_CHECKOUT_TTL_DAYS, PENDING_EMAIL_TTL_HOURS, PENDING_ONBOARDING_TTL_DAYS, PLATFORM_ERROR_CODES, PROMO_ERROR_CODES, type Paginated, type PartyAddress, type PasswordHasher, type PasswordResetCliResult, PaymentCallbackRejectedError, type PaymentErrorCode, type PaymentEventClaim, type PaymentEventLog, type PaymentGateway, type PaymentGatewayCallback, type PaymentGatewayEvent, type PaymentMethodHolder, type PaymentMethodSetupSession, type PaymentMethodSetupSubject, type PaymentMethodType, type PendingRegistration, type PendingRegistrationCreateInput, type PendingRegistrationRepository, type PendingRegistrationSnapshot, type PendingRegistrationUpdateInput, type PersistenceCapabilities, PersistenceCapabilityError, type PersistenceClassRef, type PersistenceInjectionToken, type PersistenceProvider, type PlanCatalog, type PlanCatalogApp, type PlanCatalogImportReport, type PlanCatalogImportSink, type PlanCatalogIssuer, type PlanCatalogLookup, type PlanCatalogMarketing, type PlanCatalogNotifications, type PlanCatalogPaymentAccount, type PlanCatalogPayments, type PlanCatalogReadSink, type PlanCatalogReadSnapshot, type PlanCatalogSettings, type PlanCatalogSubscribers, type PlanCatalogTenantBilling, type PlanDef, type PlanId, type PlanListFilter, type PlanRepository, type PlanRow, type PlanVersion, type PlanVersionFields, type PlanVersionMappingFields, type PlanVersionMutationResult, type PlanVersionRecord, type PlanVersionRepository, type PlanVersionRow, type PlatformErrorBody, type PlatformErrorCode, type PlatformRole, type PlatformUserDto, PlatformUserExistsError, type ProjectPageDef, type PromoCode, type PromoCodeDurationType, type PromoCodeFilter, type PromoCodeRecord, type PromoCodeRedemption, type PromoCodeRedemptionListItem, type PromoCodeRedemptionRecord, type PromoCodeRedemptionRepository, type PromoCodeRedemptionStatus, type PromoCodeRepository, type PromoCodeStatsPort, type PromoCodeStatsSnapshot, type PromoCodeStatus, type PromoCodeValidationLog, type PromoCodeValidationLogRepository, type PromoCodeValidationResult, type PromoCodeValueType, type PromoErrorCode, type PromoPreviewInvalidReason, type PromoPreviewRequest, type PromoPreviewResponse, type PromoPreviewValidResponse, type PromoRevenueDeductionAggregator, type PromoSubscriptionLookup, type PromotionBillingCycle, type PromotionI18n, type PromotionI18nFields, type PromotionRepository, type PromotionResult, type PromotionRow, type PromotionStatus, type PromotionTargetType, type PromotionType, type PromotionValue, type PublicBootResponse, type PublicComparisonRow, type PublicMarketingBundle, type PublicMarketingCatalogResponse, type PublicMarketingPlan, type PublicMarketingPromo, type PublicSignupPlan, type PublishBundleVersionData, type PublishBundleVersionMeta, type PublishPlanVersionData, type QuotaCatalogEntryRow, type QuotaEnforcementMode, type QuotaKey, type QuotaProvider, REGISTRATION_ERROR_CODES, REGISTRATION_RESUME_TTL_MINUTES, REGISTRATION_STEP_BY_STATUS, type ReassignTenantAdminCliResult, type RecommendablePlan, type RecordSubscriberPaymentMethodData, type RecordSubscriberPaymentMethodOutcome, type RecordSubscriberPaymentMethodResult, type RedeemPromoInTransactionCallback, type RegistrationActivation, type RegistrationAuditContext, type RegistrationAuditEvent, type RegistrationAuditEventType, type RegistrationAuditLogger, type RegistrationBillingDetails, type RegistrationConfigSelection, type RegistrationConfiguratorLookup, type RegistrationErrorCode, type RegistrationOtpDelivery, type RegistrationPromoPreview, type RegistrationResumeDelivery, type RegistrationResumeTokenSigner, type RegistrationStatus, type RegistrationStep, type RequiredCapabilities, type ResolvableErrorBody, type ResumeRegistrationInput, type ResumeRegistrationResult, type ReviewCatalogEntryData, type RlsBypassPort, SETTINGS_ERROR_CODES, SETUP_ERROR_CODES, SUBSCRIBER_ERROR_CODES, SUBSCRIBER_IDENTITY_FIELDS, type SaaSiCatPersistenceAdapter, type SaaSiCatPersistenceAdminResources, type SaaSiCatPersistenceCatalog, type SaaSiCatPersistenceCore, type SaaSiCatPersistenceEntitlement, type SaaSiCatPersistencePayments, type SaaSiCatPersistencePromo, type SaaSiCatPersistenceTenantBilling, type SaveRegistrationConfigInput, type SaveRegistrationConfigResult, type ScheduledPlanChangeInput, type SelectPlanInput, type SelectPlanResult, type SelectableBundleShape, type SelfServiceBlockedPlans, type SetCatalogEntryReviewData, type SettingsChangeFilter, type SettingsChangeRecord, type SettingsDifference, type SettingsErrorCode, type SetupConfirmMfaRequest, type SetupConfirmMfaResponse, type SetupErrorCode, type SetupRequest, type SetupResult, type SetupStatusResponse, type SlugAvailabilityCheck, type StandardPageDef, type StandardPageKey, type StartCheckoutInput, type StartCheckoutResult, type StartPaymentMethodSetupInput, type StartRegistrationInput, type StartRegistrationResult, type StoredBundleStem, type StrictModeWarning, type StrictModeWarningCode, type SubscriberContact, type SubscriberContactChange, type SubscriberCorrectionData, type SubscriberCorrectionRecord, type SubscriberCorrectionResult, type SubscriberDetails, type SubscriberErrorCode, type SubscriberIdentityCorrection, type SubscriberIdentityDelta, type SubscriberIdentityField, type SubscriberIdentityValues, type SubscriberPaymentMethodRecord, type SubscriberPaymentMethodRepository, type SubscriberPaymentMethodSetupData, type SubscriberPaymentMethodSetupMatch, type SubscriberPaymentMethodStatus, type SubscriberRecord, type SubscriberRepository, type Subscription, type SubscriptionBundleRecord, type SubscriptionBundleRepository, type SubscriptionBundleView, type SubscriptionContractFilter, type SubscriptionContractInvoiceSnapshot, type SubscriptionContractParties, type SubscriptionContractPriceSnapshot, type SubscriptionContractRecord, type SubscriptionContractRepository, type SubscriptionContractStatus, type SubscriptionRecord, type SubscriptionRepository, type SubscriptionStatsPort, type SubscriptionStatsSnapshot, type SubscriptionStatus, type SubscriptionUsagePort, type SubscriptionUsageRecord, type SuperAdminProvisioningPort, type SyncDiscoveryResult, type TenantActionDef, type TenantColumnDef, type TenantDto, type TenantListFilter, type TenantPort, type TenantSubscriptionWritePort, type TerminateSubscriptionContractData, type TopPromoCode, type TransactionContext, type TransactionRunner, type UpdateBundleData, type UpdateBundleVersionDraftData, type UpdateCatalogEntryBaseData, type UpdateCatalogEntryI18nData, type UpdateCheckoutOfferData, type UpdateMarketingProjectionData, type UpdateMarketingSettingsData, type UpdatePlanData, type UpdatePlanVersionDraftData, type UpdatePromoCodeData, type UpdatePromoCodeRequest, type UpdatePromotionData, type UpsellOffer, type UpsellOfferResolver, type UpsertCapabilityEntryData, type UpsertFeatureCatalogEntryInput, type UpsertFeatureEntryData, type UpsertPlanInput, type UpsertPlanVersionInput, type UpsertQuotaEntryData, type UpsertResult, type UsageSnapshotPort, type UserAccountLookup, type UserListFilter, type UserManagementPort, type UserPort, type VerifyRegistrationOtpResult, type VersionChange, type VersionChangeDirection, type VersionEditability, type VersionEditableReason, type VersionedEntityBase, applyPromo, assertPersistenceCapabilities, buildActivePlanVersionWhere, buildActiveVersionWhere, buildFeatureRequiresIndex, bundleDraftDefaults, bundleStemDefaults, canonicalJson, classifyBundleVersionDiff, classifyPlanDiff, collectUnsatisfiedRequires, contractPartiesOf, coverageExcludingSelf, definedFields, diffSettings, formatCustomerNumber, formatErrorMessage, identityCorrectionDelta, isBundleRedundant, isPaymentCallbackRejectedError, isPlatformUserExistsError, isVersionEditable, keepOneRecommended, missingRequiresFor, pickActivePromo, planCatalogSettingsOf, previousUtcDay, promoStatus, readQuotaRecord, readQuotaValue, resolveBundleAvailability, resolveErrorMessage, selectChargeableBundles, settingsSubtreeOf, startOfUtcDay, subscriberFromRegistration, subscriberPaymentMethodColumns, toBundleStemRow, toContractLineItemRecord, toPlanRow, toPlanVersionRow, toSubscriberCorrectionRecord, toSubscriberPaymentMethodRecord, toSubscriberRecord, toSubscriptionContractRecord };