@openlup/platform 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +440 -158
- package/README.md +141 -589
- package/dist/fulfillment/adapterConformance.js +1 -1
- package/dist/fulfillment/bindings.js +1 -1
- package/dist/fulfillment/contracts/conformance.d.ts +3 -1
- package/dist/fulfillment/contracts/dispatch.d.ts +45 -3
- package/dist/fulfillment/contracts/index.d.ts +3 -3
- package/dist/fulfillment/contracts/observation.d.ts +21 -1
- package/dist/fulfillment/contracts/readback.d.ts +21 -0
- package/dist/fulfillment/contracts/store.d.ts +3 -0
- package/dist/fulfillment/contracts/sync.d.ts +58 -2
- package/dist/fulfillment/dispatchProjection.d.ts +20 -1
- package/dist/fulfillment/dispatchProjection.js +97 -6
- package/dist/fulfillment/index.d.ts +3 -2
- package/dist/fulfillment/index.js +2 -2
- package/dist/fulfillment/migrations.d.ts +5 -0
- package/dist/fulfillment/migrations.js +1 -0
- package/dist/fulfillment/observationProjection.d.ts +24 -3
- package/dist/fulfillment/observationProjection.js +65 -12
- package/dist/fulfillment/omnipack/client.d.ts +3 -1
- package/dist/fulfillment/omnipack/client.js +31 -13
- package/dist/fulfillment/omnipack/index.d.ts +4 -1
- package/dist/fulfillment/omnipack/index.js +107 -45
- package/dist/fulfillment/omnipack/mappers.d.ts +7 -1
- package/dist/fulfillment/omnipack/mappers.js +24 -2
- package/dist/fulfillment/omnipack/observation.d.ts +11 -4
- package/dist/fulfillment/omnipack/observation.js +44 -29
- package/dist/fulfillment/omnipack/reconciliation.d.ts +15 -0
- package/dist/fulfillment/omnipack/reconciliation.js +55 -0
- package/dist/fulfillment/postgresDispatchStore.js +1 -1
- package/dist/fulfillment/postgresShipmentSpine.d.ts +8 -1
- package/dist/fulfillment/postgresShipmentSpine.js +17 -3
- package/dist/fulfillment/postgresStockSyncPort.js +6 -3
- package/dist/fulfillment/providerDispatchAcceptance.d.ts +1 -0
- package/dist/fulfillment/providerDispatchAcceptance.js +8 -2
- package/dist/fulfillment/providerDispatchWorker.d.ts +51 -0
- package/dist/fulfillment/providerDispatchWorker.js +65 -20
- package/dist/fulfillment/providerReconciliationWorker.d.ts +27 -19
- package/dist/fulfillment/providerReconciliationWorker.js +75 -19
- package/dist/fulfillment/providerStockSyncWorker.d.ts +5 -1
- package/dist/fulfillment/providerStockSyncWorker.js +229 -120
- package/dist/fulfillment/providerTrackingReferences.d.ts +3 -2
- package/dist/fulfillment/verifiedIngress.d.ts +38 -11
- package/dist/fulfillment/verifiedIngress.js +36 -8
- package/dist/invoicing/accountingCanonicalProviderLines.d.ts +5 -0
- package/dist/invoicing/accountingCanonicalProviderLines.js +104 -0
- package/dist/invoicing/accountingLineAllocation.d.ts +2 -0
- package/dist/invoicing/accountingLineAllocation.js +69 -0
- package/dist/invoicing/adapterConformance.d.ts +42 -0
- package/dist/invoicing/adapterConformance.js +296 -0
- package/dist/invoicing/artifacts.d.ts +7 -0
- package/dist/invoicing/artifacts.js +20 -0
- package/dist/invoicing/bindings.d.ts +46 -0
- package/dist/invoicing/bindings.js +69 -0
- package/dist/invoicing/capabilities.d.ts +11 -0
- package/dist/invoicing/capabilities.js +10 -0
- package/dist/invoicing/channelPolicy.d.ts +28 -0
- package/dist/invoicing/channelPolicy.js +27 -0
- package/dist/invoicing/continuity.d.ts +12 -0
- package/dist/invoicing/continuity.js +18 -0
- package/dist/invoicing/contracts/capabilities.d.ts +40 -0
- package/dist/invoicing/contracts/capabilities.js +1 -0
- package/dist/invoicing/contracts/companyLookup.d.ts +49 -0
- package/dist/invoicing/contracts/companyLookup.js +1 -0
- package/dist/invoicing/contracts/delivery.d.ts +74 -0
- package/dist/invoicing/contracts/delivery.js +1 -0
- package/dist/invoicing/contracts/facts.d.ts +110 -0
- package/dist/invoicing/contracts/facts.js +1 -0
- package/dist/invoicing/contracts/host.d.ts +245 -0
- package/dist/invoicing/contracts/host.js +1 -0
- package/dist/invoicing/contracts/index.d.ts +16 -0
- package/dist/invoicing/contracts/index.js +1 -0
- package/dist/invoicing/contracts/provider.d.ts +126 -0
- package/dist/invoicing/contracts/provider.js +1 -0
- package/dist/invoicing/contracts/settlement.d.ts +66 -0
- package/dist/invoicing/contracts/settlement.js +1 -0
- package/dist/invoicing/contracts/store.d.ts +277 -0
- package/dist/invoicing/contracts/store.js +1 -0
- package/dist/invoicing/contribution.d.ts +21 -0
- package/dist/invoicing/contribution.js +19 -0
- package/dist/invoicing/correction.d.ts +42 -0
- package/dist/invoicing/correction.js +44 -0
- package/dist/invoicing/delivery.d.ts +85 -0
- package/dist/invoicing/delivery.js +79 -0
- package/dist/invoicing/documentHistory.d.ts +50 -0
- package/dist/invoicing/documentHistory.js +213 -0
- package/dist/invoicing/documentSync.d.ts +24 -0
- package/dist/invoicing/documentSync.js +26 -0
- package/dist/invoicing/dutyCollector.d.ts +15 -0
- package/dist/invoicing/dutyCollector.js +35 -0
- package/dist/invoicing/fulfillmentTrigger.d.ts +56 -0
- package/dist/invoicing/fulfillmentTrigger.js +90 -0
- package/dist/invoicing/handoff.d.ts +37 -0
- package/dist/invoicing/handoff.js +62 -0
- package/dist/invoicing/index.d.ts +39 -0
- package/dist/invoicing/index.js +37 -0
- package/dist/invoicing/issue.d.ts +39 -0
- package/dist/invoicing/issue.js +55 -0
- package/dist/invoicing/migrations.d.ts +48 -0
- package/dist/invoicing/migrations.js +25 -0
- package/dist/invoicing/nativeUnsupported.d.ts +9 -0
- package/dist/invoicing/nativeUnsupported.js +22 -0
- package/dist/invoicing/paymentReadback.d.ts +20 -0
- package/dist/invoicing/paymentReadback.js +47 -0
- package/dist/invoicing/postgresInvoiceStore.d.ts +55 -0
- package/dist/invoicing/postgresInvoiceStore.js +344 -0
- package/dist/invoicing/readModel.d.ts +74 -0
- package/dist/invoicing/readModel.js +90 -0
- package/dist/invoicing/readback.d.ts +4 -0
- package/dist/invoicing/readback.js +30 -0
- package/dist/invoicing/reversal.d.ts +37 -0
- package/dist/invoicing/reversal.js +28 -0
- package/dist/invoicing/sellerRevision.d.ts +19 -0
- package/dist/invoicing/sellerRevision.js +14 -0
- package/dist/invoicing/settlementSync.d.ts +18 -0
- package/dist/invoicing/settlementSync.js +32 -0
- package/dist/invoicing/snapshot.d.ts +52 -0
- package/dist/invoicing/snapshot.js +141 -0
- package/dist/subscriptions/automaticRenewal.d.ts +44 -0
- package/dist/subscriptions/automaticRenewal.js +81 -0
- package/dist/subscriptions/automaticRenewalExecution.d.ts +21 -0
- package/dist/subscriptions/automaticRenewalExecution.js +44 -0
- package/dist/subscriptions/contracts/commands.d.ts +115 -0
- package/dist/subscriptions/contracts/commands.js +1 -0
- package/dist/subscriptions/contracts/dunning.d.ts +133 -0
- package/dist/subscriptions/contracts/dunning.js +1 -0
- package/dist/subscriptions/contracts/index.d.ts +6 -0
- package/dist/subscriptions/contracts/index.js +1 -0
- package/dist/subscriptions/contracts/messages.d.ts +43 -0
- package/dist/subscriptions/contracts/messages.js +1 -0
- package/dist/subscriptions/contracts/renewal.d.ts +533 -0
- package/dist/subscriptions/contracts/renewal.js +1 -0
- package/dist/subscriptions/contracts/stockWait.d.ts +73 -0
- package/dist/subscriptions/contracts/stockWait.js +1 -0
- package/dist/subscriptions/index.d.ts +39 -0
- package/dist/subscriptions/index.js +17 -0
- package/dist/subscriptions/paymentOutcomes.d.ts +8 -0
- package/dist/subscriptions/paymentOutcomes.js +27 -0
- package/dist/subscriptions/postgresRenewalStore.d.ts +20 -0
- package/dist/subscriptions/postgresRenewalStore.js +61 -0
- package/dist/subscriptions/postgresRetryStore.d.ts +78 -0
- package/dist/subscriptions/postgresRetryStore.js +142 -0
- package/dist/subscriptions/renewalCycleFailureMechanism.d.ts +34 -0
- package/dist/subscriptions/renewalCycleFailureMechanism.js +79 -0
- package/dist/subscriptions/renewalCycleMechanism.d.ts +46 -0
- package/dist/subscriptions/renewalCycleMechanism.js +157 -0
- package/dist/subscriptions/renewalCycleRefusalMechanism.d.ts +41 -0
- package/dist/subscriptions/renewalCycleRefusalMechanism.js +160 -0
- package/dist/subscriptions/renewalIdentity.d.ts +3 -0
- package/dist/subscriptions/renewalIdentity.js +4 -0
- package/package.json +23 -3
- package/release-gates.json +74 -7
- package/sql/20260809210000_accounting_document_rail.sql +889 -0
- package/sql/20260811150000_accounting_payout_rail.sql +333 -0
- package/sql/20260812130000_accounting_document_lifecycle_rail.sql +460 -0
- package/sql/20261022160000_subscription_retry_lifecycle.sql +328 -0
- package/sql/20261022165000_fulfillment_stock_evidence_replay_reopen.sql +30 -0
- package/sql/20261022190000_invoicing_durable_duties.sql +494 -0
- package/sql/20261022191000_invoicing_document_effects.sql +249 -0
- package/sql/20261022192000_invoicing_payment_decoupling.sql +144 -0
- package/sql/20261022195000_invoicing_seller_revision.sql +65 -0
- package/src/fulfillment/adapterConformance.ts +1 -1
- package/src/fulfillment/bindings.ts +1 -1
- package/src/fulfillment/contracts/conformance.ts +3 -1
- package/src/fulfillment/contracts/dispatch.ts +24 -5
- package/src/fulfillment/contracts/index.ts +3 -3
- package/src/fulfillment/contracts/observation.ts +11 -3
- package/src/fulfillment/contracts/readback.ts +13 -0
- package/src/fulfillment/contracts/store.ts +1 -0
- package/src/fulfillment/contracts/sync.ts +13 -2
- package/src/fulfillment/dispatchProjection.ts +94 -7
- package/src/fulfillment/index.ts +3 -2
- package/src/fulfillment/migrations.ts +1 -0
- package/src/fulfillment/observationProjection.ts +64 -13
- package/src/fulfillment/omnipack/client.ts +32 -13
- package/src/fulfillment/omnipack/index.ts +99 -39
- package/src/fulfillment/omnipack/mappers.ts +23 -2
- package/src/fulfillment/omnipack/observation.ts +45 -27
- package/src/fulfillment/omnipack/reconciliation.ts +56 -0
- package/src/fulfillment/postgresDispatchStore.ts +1 -1
- package/src/fulfillment/postgresShipmentSpine.ts +21 -4
- package/src/fulfillment/postgresStockSyncPort.ts +6 -3
- package/src/fulfillment/providerDispatchAcceptance.ts +9 -3
- package/src/fulfillment/providerDispatchWorker.ts +61 -20
- package/src/fulfillment/providerReconciliationWorker.ts +81 -25
- package/src/fulfillment/providerStockSyncWorker.ts +222 -126
- package/src/fulfillment/providerTrackingReferences.ts +3 -2
- package/src/fulfillment/verifiedIngress.ts +44 -11
- package/src/invoicing/accountingCanonicalProviderLines.ts +134 -0
- package/src/invoicing/accountingLineAllocation.ts +69 -0
- package/src/invoicing/adapterConformance.ts +249 -0
- package/src/invoicing/artifacts.ts +19 -0
- package/src/invoicing/bindings.ts +80 -0
- package/src/invoicing/capabilities.ts +9 -0
- package/src/invoicing/channelPolicy.ts +50 -0
- package/src/invoicing/continuity.ts +15 -0
- package/src/invoicing/contracts/capabilities.ts +15 -0
- package/src/invoicing/contracts/companyLookup.ts +13 -0
- package/src/invoicing/contracts/delivery.ts +24 -0
- package/src/invoicing/contracts/facts.ts +71 -0
- package/src/invoicing/contracts/host.ts +84 -0
- package/src/invoicing/contracts/index.ts +16 -0
- package/src/invoicing/contracts/provider.ts +78 -0
- package/src/invoicing/contracts/settlement.ts +39 -0
- package/src/invoicing/contracts/store.ts +102 -0
- package/src/invoicing/contribution.ts +28 -0
- package/src/invoicing/correction.ts +56 -0
- package/src/invoicing/delivery.ts +122 -0
- package/src/invoicing/documentHistory.ts +301 -0
- package/src/invoicing/documentSync.ts +51 -0
- package/src/invoicing/dutyCollector.ts +31 -0
- package/src/invoicing/fulfillmentTrigger.ts +144 -0
- package/src/invoicing/handoff.ts +71 -0
- package/src/invoicing/index.ts +39 -0
- package/src/invoicing/issue.ts +64 -0
- package/src/invoicing/migrations.ts +25 -0
- package/src/invoicing/nativeUnsupported.ts +24 -0
- package/src/invoicing/paymentReadback.ts +70 -0
- package/src/invoicing/postgresInvoiceStore.ts +334 -0
- package/src/invoicing/readModel.ts +80 -0
- package/src/invoicing/readback.ts +26 -0
- package/src/invoicing/reversal.ts +34 -0
- package/src/invoicing/sellerRevision.ts +20 -0
- package/src/invoicing/settlementSync.ts +37 -0
- package/src/invoicing/snapshot.ts +192 -0
- package/src/subscriptions/automaticRenewal.ts +87 -0
- package/src/subscriptions/automaticRenewalExecution.ts +39 -0
- package/src/subscriptions/contracts/commands.ts +46 -0
- package/src/subscriptions/contracts/dunning.ts +48 -0
- package/src/subscriptions/contracts/index.ts +6 -0
- package/src/subscriptions/contracts/messages.ts +18 -0
- package/src/subscriptions/contracts/renewal.ts +325 -0
- package/src/subscriptions/contracts/stockWait.ts +34 -0
- package/src/subscriptions/index.ts +24 -0
- package/src/subscriptions/paymentOutcomes.ts +23 -0
- package/src/subscriptions/postgresRenewalStore.ts +76 -0
- package/src/subscriptions/postgresRetryStore.ts +174 -0
- package/src/subscriptions/renewalCycleFailureMechanism.ts +119 -0
- package/src/subscriptions/renewalCycleMechanism.ts +225 -0
- package/src/subscriptions/renewalCycleRefusalMechanism.ts +215 -0
- package/src/subscriptions/renewalIdentity.ts +5 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { collectInvoiceDuties } from "./dutyCollector.js";
|
|
2
|
+
|
|
3
|
+
/** Explicit independent host invocation, never an inline fulfillment/payment hook.
|
|
4
|
+
* Durable source receipts and fact leases own replay, backoff and fault ceilings.
|
|
5
|
+
* A collector outage remains visible and can be retried by the host later.
|
|
6
|
+
* @consumer Reference native accounting runtime and installed cold consumer.
|
|
7
|
+
*/
|
|
8
|
+
export function createIndependentInvoiceDutyBinding(input: Parameters<typeof collectInvoiceDuties>[0]) {
|
|
9
|
+
return {
|
|
10
|
+
async collect(): Promise<Awaited<ReturnType<typeof collectInvoiceDuties>> | { kind: "unavailable"; code: "retryable" }> {
|
|
11
|
+
try { return await collectInvoiceDuties(input); }
|
|
12
|
+
catch { return { kind: "unavailable", code: "retryable" }; }
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { InvoiceDiagnosticCode, InvoiceUnsupported } from "./provider.js";
|
|
2
|
+
export interface InvoiceCapabilities {
|
|
3
|
+
readonly version: "invoicing.capabilities.v1";
|
|
4
|
+
readonly providerRef: string;
|
|
5
|
+
readonly fiscalScope: { readonly scopeRef: string; readonly version: string };
|
|
6
|
+
readonly policies: { readonly identifierRef: string; readonly taxRef: string; readonly roundingRef: string };
|
|
7
|
+
readonly documentKinds: readonly string[];
|
|
8
|
+
readonly correctionKinds: readonly "full_reversal"[];
|
|
9
|
+
readonly numberingOwner: "provider";
|
|
10
|
+
readonly sellerTiming: "execution" | "first_claim";
|
|
11
|
+
readonly operations: Readonly<Record<"issue" | "correction" | "recovery" | "readback" | "exactAbsence" | "artifact" | "clearance" | "settlement" | "companyLookup", { readonly supported: true } | { readonly supported: false; readonly reason: "host_required" | "not_implemented" | "not_exposed" }>> & { readonly remoteVoid: { readonly supported: false; readonly reason: "not_implemented" } };
|
|
12
|
+
readonly defaultOff: true;
|
|
13
|
+
}
|
|
14
|
+
/** Interpretation only: no environment discovery, activation, writes or provider calls. */
|
|
15
|
+
export type InvoiceCapabilitySelection = InvoiceUnsupported | { readonly kind: "ready"; readonly capability: InvoiceCapabilities } | { readonly kind: "refused"; readonly code: Extract<InvoiceDiagnosticCode, "disabled" | "selection_required">; readonly retryable: false; readonly effectMayHaveSucceeded: false };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { InvoiceProof, InvoiceUnsupported } from "./provider.js";
|
|
2
|
+
/** Injected registry seam. Host owns identifier validation, freshness and fallback;
|
|
3
|
+
* lookup evidence never overwrites checkout truth or becomes a renewal prerequisite.
|
|
4
|
+
* Carrier: accountingControlContracts lookup fields, generalized per A5 fit probe.
|
|
5
|
+
*/
|
|
6
|
+
export interface InvoiceCompanyLookupPort {
|
|
7
|
+
lookup(input: { readonly identifier: { readonly scheme: string; readonly value: string; readonly country?: string }; readonly asOf: string; readonly sourceRef: string }): Promise<
|
|
8
|
+
| { readonly kind: "found" | "partial"; readonly identifier: { readonly scheme: string; readonly value: string; readonly country?: string }; readonly legalName: string | null; readonly registrationIds: readonly { readonly scheme: string; readonly value: string }[]; readonly address: { readonly line1: string; readonly postalCode: string; readonly city: string; readonly country: string } | null; readonly taxRegistrations: readonly { readonly scheme: string; readonly status: "active" | "not_registered" | "exempt" | "unknown" }[]; readonly sourceRef: string; readonly proof: InvoiceProof }
|
|
9
|
+
| { readonly kind: "not_found"; readonly identifier: Parameters<InvoiceCompanyLookupPort["lookup"]>[0]["identifier"]; readonly sourceRef: string; readonly proof: InvoiceProof }
|
|
10
|
+
| { readonly kind: "unknown"; readonly sourceRef: string; readonly code: "unknown" }
|
|
11
|
+
| InvoiceUnsupported
|
|
12
|
+
>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { InvoiceDocumentIdentity, InvoiceProof, InvoiceUnsupported } from "./provider.js";
|
|
2
|
+
/** Durable opaque references; signed URLs and downloaded bytes are transient. */
|
|
3
|
+
export type InvoiceArtifactResult =
|
|
4
|
+
| { readonly kind: "found"; readonly identity: InvoiceDocumentIdentity; readonly artifact: { readonly kind: "pdf" | "structured_document" | "clearance_receipt"; readonly ref: string; readonly owner: "provider" | "host_storage"; readonly mediaType: string; readonly proof: InvoiceProof } }
|
|
5
|
+
| { readonly kind: "absent"; readonly identity: InvoiceDocumentIdentity; readonly evidence: InvoiceProof & { readonly exact: true; readonly complete: true; readonly scope: { readonly identity: InvoiceDocumentIdentity; readonly kind: "pdf" | "structured_document" | "clearance_receipt"; readonly artifactRef: string | null } } }
|
|
6
|
+
| { readonly kind: "unknown"; readonly identity: InvoiceDocumentIdentity; readonly code: "unknown" }
|
|
7
|
+
| InvoiceUnsupported;
|
|
8
|
+
/** Observation only; unsupported is never not_required. No clearance submission. */
|
|
9
|
+
export type InvoiceClearanceObservation = { readonly proof: InvoiceProof } & (
|
|
10
|
+
| { readonly state: "accepted"; readonly reference: string }
|
|
11
|
+
| { readonly state: "not_submitted" | "pending" | "rejected" | "not_required" | "unsupported"; readonly reference: string | null }
|
|
12
|
+
);
|
|
13
|
+
export interface InvoiceDeliveryPort {
|
|
14
|
+
/** Host M04 content/transport; provider acceptance is not inbox delivery.
|
|
15
|
+
* Preserve delivery/request keys, recipient, filename and bytes. Validate PDF
|
|
16
|
+
* type, size and signature before send; uncertain send must never repeat sending.
|
|
17
|
+
*/
|
|
18
|
+
send(input: { readonly deliveryRef: string; readonly requestKey: string; readonly attemptCount: number; readonly documentRef: string; readonly orderId: string; readonly orderRef: string; readonly invoiceRef: string; readonly providerDocumentRef: string; readonly clientRef: string | null; readonly recipient: string; readonly source: string | null; readonly providerDocumentNumber: string | null; readonly filename: string; readonly content: Uint8Array; readonly contentType: string }): Promise<
|
|
19
|
+
| { readonly kind: "provider_accepted"; readonly receiptRef: string; readonly proof: InvoiceProof }
|
|
20
|
+
| { readonly kind: "failed_before_send"; readonly retryable: boolean; readonly code: "retryable" | "invalid_artifact" }
|
|
21
|
+
| { readonly kind: "uncertain"; readonly code: "uncertain_delivery"; readonly receiptRef: string | null }
|
|
22
|
+
| InvoiceUnsupported
|
|
23
|
+
>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** Frozen host facts; no catalogue, registry or country-policy reread on retry.
|
|
2
|
+
* Carrier: donor providerPorts snapshot and invoiceContracts LocalInvoiceSnapshot;
|
|
3
|
+
* native invoicing_install_facts adds dates/refs/policyVersion and explicit totals.
|
|
4
|
+
*/
|
|
5
|
+
export interface InvoiceSeller {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly address: { readonly line1: string; readonly postalCode: string; readonly city: string };
|
|
8
|
+
readonly taxIdentifier: { readonly scheme: string; readonly value: string };
|
|
9
|
+
readonly registrationIds?: readonly { readonly scheme: string; readonly value: string }[];
|
|
10
|
+
readonly bankAccount: string | null;
|
|
11
|
+
/** Opaque host routing reference, interpreted only by the selected adapter. */
|
|
12
|
+
readonly routingRef: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface InvoiceBuyer {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly email: string | null;
|
|
17
|
+
readonly taxIdentifier: { readonly scheme: string; readonly value: string } | null;
|
|
18
|
+
readonly companyName: string | null;
|
|
19
|
+
readonly address: { readonly line1: string | null; readonly postalCode: string | null; readonly city: string | null; readonly country: string | null } | null;
|
|
20
|
+
}
|
|
21
|
+
/** Safe integer minor amounts; taxRateBps is a rate, never a fiscal-support claim.
|
|
22
|
+
* Preserve array order and quantityUnit; no inferred shipping residual or FX.
|
|
23
|
+
*/
|
|
24
|
+
export interface InvoiceLine {
|
|
25
|
+
readonly description: string;
|
|
26
|
+
readonly quantity: number;
|
|
27
|
+
readonly quantityUnit?: string;
|
|
28
|
+
readonly unitNetMinor: number;
|
|
29
|
+
readonly unitGrossMinor: number;
|
|
30
|
+
readonly totalNetMinor: number;
|
|
31
|
+
readonly totalTaxMinor: number;
|
|
32
|
+
readonly totalGrossMinor: number;
|
|
33
|
+
readonly taxRateBps: number;
|
|
34
|
+
readonly taxRateLabel?: string;
|
|
35
|
+
readonly positionKind: "item" | "delivery" | null;
|
|
36
|
+
readonly catalogTotalGrossMinor: number | null;
|
|
37
|
+
readonly discountAllocatedMinor: number | null;
|
|
38
|
+
readonly shippingGrossMinor: number | null;
|
|
39
|
+
readonly shippingDiscountMinor: number | null;
|
|
40
|
+
}
|
|
41
|
+
export interface InvoiceFacts {
|
|
42
|
+
readonly factRevision: string;
|
|
43
|
+
readonly policyVersion: string;
|
|
44
|
+
readonly refs: { readonly orderId: string; readonly orderRef: string; readonly invoiceRef: string };
|
|
45
|
+
readonly dates: { readonly issueDate: string; readonly sellDate: string; readonly handedOverAt: string | null; readonly paymentCompletedAt: string | null };
|
|
46
|
+
/** Host-defined kind selected through the identifier/tax seam, never guessed. */
|
|
47
|
+
readonly documentKind: string;
|
|
48
|
+
readonly clearanceRequired: boolean;
|
|
49
|
+
readonly currency: string;
|
|
50
|
+
readonly buyer: InvoiceBuyer;
|
|
51
|
+
/** Claim projection only: the first durable claim freezes the seller; legacy obligations retain execution-time fallback. */
|
|
52
|
+
readonly seller?: InvoiceSeller;
|
|
53
|
+
readonly payment: { readonly intentRef: string; readonly providerRef: string | null; readonly providerPaymentRef: string | null; readonly amountMinor: number; readonly currency: string; readonly localSettlementState: "matched" | "unavailable" };
|
|
54
|
+
readonly orderMoney: { readonly subtotalMinor: number; readonly discountMinor: number; readonly shippingMinor: number; readonly shippingDiscountMinor: number; readonly totalMinor: number } | null;
|
|
55
|
+
readonly lines: readonly InvoiceLine[];
|
|
56
|
+
readonly totalNetMinor: number;
|
|
57
|
+
readonly totalTaxMinor: number;
|
|
58
|
+
readonly totalGrossMinor: number;
|
|
59
|
+
readonly chargedTotalGrossMinor: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Host-selected immutable revision; it contains no provider or country defaults. */
|
|
63
|
+
export interface InvoiceSellerRevision<Seller = InvoiceSeller> {
|
|
64
|
+
readonly seller: Seller;
|
|
65
|
+
readonly revisionRef: string;
|
|
66
|
+
}
|
|
67
|
+
/** Added only to returned claim facts, never to installed historical revisions. */
|
|
68
|
+
export interface InvoiceClaimedFacts extends InvoiceFacts {
|
|
69
|
+
readonly sellerRevision?: string;
|
|
70
|
+
readonly sellerMode?: "frozen" | "legacy_fallback";
|
|
71
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { InvoiceFacts, InvoiceSeller } from "./facts.js";
|
|
2
|
+
import type { InvoiceDocumentIdentity, InvoiceProof, InvoiceProviderPort, InvoiceUnsupported } from "./provider.js";
|
|
3
|
+
import type { InvoiceArtifactResult, InvoiceClearanceObservation, InvoiceDeliveryPort } from "./delivery.js";
|
|
4
|
+
import type { InvoiceCompanyLookupPort } from "./companyLookup.js";
|
|
5
|
+
import type { InvoiceSettlementPort } from "./settlement.js";
|
|
6
|
+
import type { InvoiceCapabilities } from "./capabilities.js";
|
|
7
|
+
import type { InvoiceStorePort } from "./store.js";
|
|
8
|
+
/** Host facts, fiscal routing, M07 reads, storage, registry, M04 delivery and M12
|
|
9
|
+
* history are required selections. Every missing native capability refuses before
|
|
10
|
+
* any write; an owed duty remains observable. This interface grants no authority.
|
|
11
|
+
*/
|
|
12
|
+
export interface InvoiceHostPorts {
|
|
13
|
+
facts: { read(orderId: string): Promise<{ readonly kind: "found"; readonly facts: InvoiceFacts } | { readonly kind: "unknown"; readonly code: "missing_facts" } | InvoiceUnsupported> };
|
|
14
|
+
/** Read the current host seller for the next claim, never per duty. Null orderId
|
|
15
|
+
* means an unscoped claim (including corrections). The durable wrapper freezes this seller at
|
|
16
|
+
* the first-ever claim; revisionRef feeds the claim wrapper's revision argument.
|
|
17
|
+
*/
|
|
18
|
+
seller: { read(input: { readonly orderId: string | null }): Promise<{ readonly kind: "found"; readonly seller: InvoiceSeller; readonly revisionRef: string } | { readonly kind: "unknown"; readonly code: "unknown" } | InvoiceUnsupported> };
|
|
19
|
+
/** Read-only projection of the final claimed M07 identity, never a refund writer.
|
|
20
|
+
* Missing nullable money is unavailable, never fabricated zero or mismatch.
|
|
21
|
+
*/
|
|
22
|
+
money: { read(input: { readonly intentRef: string; readonly providerRef: string; readonly providerPaymentRef: string | null; readonly expectedMoney: { readonly amountMinor: number; readonly currency: string } }): Promise<
|
|
23
|
+
| { readonly kind: "observed"; readonly status: "succeeded" | "failed" | "pending"; readonly amountMinor: number | null; readonly currency: string | null; readonly proof: InvoiceProof }
|
|
24
|
+
| { readonly kind: "absent"; readonly evidence: InvoiceProof & { readonly exact: true; readonly complete: true; readonly scope: Parameters<InvoiceHostPorts["money"]["read"]>[0] } }
|
|
25
|
+
| { readonly kind: "unknown" | "unavailable"; readonly code: "money_unavailable" }
|
|
26
|
+
| InvoiceUnsupported
|
|
27
|
+
> };
|
|
28
|
+
artifacts: {
|
|
29
|
+
read(input: { readonly identity: InvoiceDocumentIdentity; readonly kind: "pdf" | "structured_document" | "clearance_receipt" }): Promise<InvoiceArtifactResult>;
|
|
30
|
+
/** Ownership must already be authorized; response bytes are not a storage receipt. */
|
|
31
|
+
download(input: { readonly identity: InvoiceDocumentIdentity; readonly artifactRef: string }): Promise<{ readonly kind: "downloaded"; readonly content: Uint8Array; readonly contentType: string } | { readonly kind: "unknown"; readonly code: "unknown" } | InvoiceUnsupported>;
|
|
32
|
+
persist(input: { readonly identity: InvoiceDocumentIdentity; readonly kind: "pdf" | "structured_document" | "clearance_receipt"; readonly content: Uint8Array; readonly contentType: string }): Promise<Exclude<InvoiceArtifactResult, { kind: "absent" }>>;
|
|
33
|
+
};
|
|
34
|
+
/** Host identifier/tax/rounding policy; refs must match the selected descriptor.
|
|
35
|
+
* No jurisdictional default is supplied. Carrier: TaxIdRoutingPort and injected
|
|
36
|
+
* canonical tax safety in accountingProviderSnapshot.
|
|
37
|
+
*/
|
|
38
|
+
fiscal: { verify(input: { readonly facts: InvoiceFacts; readonly policies: InvoiceCapabilities["policies"] }): Promise<{ readonly kind: "verified"; readonly documentKind: string; readonly clearanceRequired: boolean; readonly proof: InvoiceProof } | { readonly kind: "refused"; readonly code: "blocked" | "money_mismatch" } | { readonly kind: "unknown"; readonly code: "unknown" } | InvoiceUnsupported> };
|
|
39
|
+
registry: InvoiceCompanyLookupPort;
|
|
40
|
+
delivery: InvoiceDeliveryPort;
|
|
41
|
+
clearance: { observe(input: { readonly identity: InvoiceDocumentIdentity; readonly providerDocumentRef: string }): Promise<{ readonly kind: "observed"; readonly observation: InvoiceClearanceObservation } | { readonly kind: "unknown"; readonly code: "unknown" } | InvoiceUnsupported> };
|
|
42
|
+
settlement: InvoiceSettlementPort;
|
|
43
|
+
policy: {
|
|
44
|
+
/** Carrier: channelInvoicePolicyGate and the approved explicit operator skip.
|
|
45
|
+
* Suppression receipts have no fabricated document ID. Host persists attribution.
|
|
46
|
+
* Executor decides before installFacts: suppressed orders get no local document.
|
|
47
|
+
* Readback consumers merge recorded receipts with duty readback.
|
|
48
|
+
* Transient read failures return unknown without recording a receipt.
|
|
49
|
+
*/
|
|
50
|
+
decide(input: { readonly requestKey: string; readonly lookup: { readonly by: "order"; readonly orderId: string } | { readonly by: "fulfillmentOrder"; readonly fulfillmentOrderRef: string } }): Promise<
|
|
51
|
+
| { readonly kind: "issue"; readonly policyVersion: string }
|
|
52
|
+
| { readonly kind: "policy_suppressed"; readonly outcome: "channel_issues" | "no_document"; readonly policyVersion: string; readonly requestKey: string }
|
|
53
|
+
| { readonly kind: "operator_skipped"; readonly outcome: "operator_skip"; readonly actorRef: string; readonly reason: string; readonly requestKey: string }
|
|
54
|
+
| { readonly kind: "unknown"; readonly code: "unknown" }
|
|
55
|
+
| InvoiceUnsupported
|
|
56
|
+
>;
|
|
57
|
+
receipts(orderId: string): Promise<readonly (Extract<Awaited<ReturnType<InvoiceHostPorts["policy"]["decide"]>>, { kind: "policy_suppressed" | "operator_skipped" }> & { readonly recordedAt: string })[] | { readonly kind: "unknown"; readonly code: "unknown" } | InvoiceUnsupported>;
|
|
58
|
+
};
|
|
59
|
+
/** Authenticate and authorize ownership before reading artifacts. Safe projection
|
|
60
|
+
* only: no buyer/seller payload, raw provider errors, signed URL or arbitrary meta.
|
|
61
|
+
* Found is a complete owned projection; incomplete reads return unknown.
|
|
62
|
+
* Carrier: donor document history + customer download ticket owner/lineage.
|
|
63
|
+
*/
|
|
64
|
+
accountHistory: {
|
|
65
|
+
read(input: { readonly accountRef: string; readonly orderId: string }): Promise<
|
|
66
|
+
| { readonly kind: "found"; readonly accountRef: string; readonly orderId: string; readonly documents: readonly { readonly documentRef: string; readonly invoiceRef: string; readonly parentDocumentRef: string | null; readonly role: "original" | "correction" | "replacement"; readonly status: string; readonly issuedAt: string | null; readonly clearance: { readonly state: InvoiceClearanceObservation["state"]; readonly reference: string | null } | null; readonly documentKind: string | null; readonly artifact: "invoice" | "correction"; readonly providerDocumentNumber: string | null; readonly totalGrossMinor: number | null; readonly currency: string; readonly emailState: "not_requested" | "pending" | "provider_accepted" | "failed"; readonly isCurrent: boolean; readonly createdAt: string; readonly downloadAvailable: boolean }[] }
|
|
67
|
+
| { readonly kind: "denied" }
|
|
68
|
+
| { readonly kind: "unknown"; readonly code: "unknown" }
|
|
69
|
+
| InvoiceUnsupported
|
|
70
|
+
>;
|
|
71
|
+
resolveDownload(input: { readonly accountRef: string; readonly orderId: string; readonly documentRef: string }): Promise<{ readonly kind: "authorized"; readonly identity: InvoiceDocumentIdentity; readonly artifactRef: string; readonly fileName: string; readonly parentDocumentRef: string | null } | { readonly kind: "denied" } | { readonly kind: "unknown"; readonly code: "unknown" } | InvoiceUnsupported>;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** Explicit service composition inputs; the host retains worker and route authority. Closures/credentials must never be serialized with options.
|
|
75
|
+
*/
|
|
76
|
+
export interface InvoiceServicesOptions {
|
|
77
|
+
readonly enabled?: boolean;
|
|
78
|
+
readonly selection: { readonly providerRef: string } | null;
|
|
79
|
+
readonly capability: InvoiceCapabilities | null;
|
|
80
|
+
readonly provider: InvoiceProviderPort | null;
|
|
81
|
+
readonly store: InvoiceStorePort | null;
|
|
82
|
+
readonly host: InvoiceHostPorts;
|
|
83
|
+
readiness(): Promise<{ readonly kind: "ready" } | InvoiceUnsupported>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** @consumer Frozen fact provider and installed declaration consumer. */
|
|
2
|
+
export type { InvoiceFacts, InvoiceLine, InvoiceSeller, InvoiceBuyer, InvoiceSellerRevision, InvoiceClaimedFacts } from "./facts.js";
|
|
3
|
+
/** @consumer A4 issue/correction bridge, adapters and operator proof readback. */
|
|
4
|
+
export type { InvoiceIdentity, InvoiceDocumentIdentity, InvoiceWriteResult, InvoiceReadbackResult, InvoiceProviderPort, InvoiceUnsupported, InvoiceDiagnosticCode, InvoiceProof } from "./provider.js";
|
|
5
|
+
/** @consumer Host artifact storage/download binding. */
|
|
6
|
+
export type { InvoiceArtifactResult, InvoiceClearanceObservation } from "./delivery.js";
|
|
7
|
+
/** @consumer Host payout item recorder and resolver. */
|
|
8
|
+
export type { InvoiceSettlementResult, InvoiceSettlementPort, InvoiceSettlementItem } from "./settlement.js";
|
|
9
|
+
/** @consumer Explicit host selection and readInvoiceCapabilities. */
|
|
10
|
+
export type { InvoiceCapabilities } from "./capabilities.js";
|
|
11
|
+
/** @consumer Injected registry adapter (A5). */
|
|
12
|
+
export type { InvoiceCompanyLookupPort } from "./companyLookup.js";
|
|
13
|
+
/** @consumer Managed/native persistence adapter and operator duty readback; no implementation supplied. */
|
|
14
|
+
export type { InvoiceStorePort, InvoiceDutyReadback } from "./store.js";
|
|
15
|
+
/** @consumer Host composition and offline port-fit witnesses. */
|
|
16
|
+
export type { InvoiceHostPorts, InvoiceServicesOptions } from "./host.js";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { InvoiceFacts } from "./facts.js";
|
|
2
|
+
/** Original host key bytes, immutable revision and exact lookup scope travel together.
|
|
3
|
+
* Carrier: providerPorts create options + approved exact-lookup envelope (fit probe).
|
|
4
|
+
*/
|
|
5
|
+
export interface InvoiceIdentity {
|
|
6
|
+
readonly providerRef: string;
|
|
7
|
+
readonly requestKey: string;
|
|
8
|
+
/** Persisted create identity from claim, distinct from original host request key. */
|
|
9
|
+
readonly providerCreateIdentity: string;
|
|
10
|
+
readonly factRevision: string;
|
|
11
|
+
readonly orderRef: string;
|
|
12
|
+
readonly invoiceRef: string;
|
|
13
|
+
readonly operation: "issue" | "full_reversal" | "remote_void";
|
|
14
|
+
readonly correctionRef: string | null;
|
|
15
|
+
readonly parentProviderDocumentRef: string | null;
|
|
16
|
+
}
|
|
17
|
+
/** Document rows and donor delivery/history carriers need no issue-duty metadata.
|
|
18
|
+
* @consumer Host artifacts, clearance, delivery and authorized download adapters.
|
|
19
|
+
*/
|
|
20
|
+
export interface InvoiceDocumentIdentity {
|
|
21
|
+
readonly providerRef: string;
|
|
22
|
+
readonly documentRef: string;
|
|
23
|
+
readonly providerDocumentRef: string | null;
|
|
24
|
+
readonly orderRef: string;
|
|
25
|
+
readonly invoiceRef: string;
|
|
26
|
+
readonly parentProviderDocumentRef: string | null;
|
|
27
|
+
}
|
|
28
|
+
/** A closed diagnostic allowlist in the neutral contract. Codes only.
|
|
29
|
+
* @consumer A4 bridge and provider/store adapters. */
|
|
30
|
+
export type InvoiceDiagnosticCode =
|
|
31
|
+
| "faulted" | "missing_facts" | "legacy_facts_unproved" | "retryable" | "blocked"
|
|
32
|
+
| "unsupported" | "unknown" | "absent" | "host_binding_required"
|
|
33
|
+
| "selection_required" | "disabled" | "identity_mismatch" | "parent_mismatch"
|
|
34
|
+
| "money_mismatch" | "money_unavailable" | "invalid_artifact" | "uncertain_delivery";
|
|
35
|
+
/** Missing binding or capability: nothing written.
|
|
36
|
+
* @consumer Host and provider adapters. */
|
|
37
|
+
export interface InvoiceUnsupported {
|
|
38
|
+
readonly kind: "unsupported";
|
|
39
|
+
readonly code: "host_binding_required" | "unsupported";
|
|
40
|
+
readonly retryable: false;
|
|
41
|
+
readonly effectMayHaveSucceeded: false;
|
|
42
|
+
}
|
|
43
|
+
/** Evidence references/fingerprints are adapter-filtered; never raw payloads or errors.
|
|
44
|
+
* @consumer Host recorder, operator readback and adapters. */
|
|
45
|
+
export interface InvoiceProof {
|
|
46
|
+
readonly evidenceRef: string;
|
|
47
|
+
readonly fingerprint: string;
|
|
48
|
+
readonly observedAt: string;
|
|
49
|
+
}
|
|
50
|
+
/** Refused proves a definite provider refusal with no effect; post-request adapter
|
|
51
|
+
* or transport faults are unknown, even when no acknowledgement was received.
|
|
52
|
+
*/
|
|
53
|
+
export type InvoiceWriteResult =
|
|
54
|
+
| { readonly kind: "issued"; readonly identity: InvoiceIdentity; readonly providerDocumentRef: string; readonly providerDocumentNumber: string | null; readonly proof: InvoiceProof; readonly replayed: boolean }
|
|
55
|
+
| { readonly kind: "refused"; readonly identity: InvoiceIdentity; readonly code: Extract<InvoiceDiagnosticCode, "blocked" | "retryable" | "missing_facts" | "identity_mismatch" | "parent_mismatch" | "money_mismatch">; readonly retryable: boolean; readonly effectMayHaveSucceeded: false }
|
|
56
|
+
| { readonly kind: "unknown"; readonly identity: InvoiceIdentity; readonly code: "unknown"; readonly effectMayHaveSucceeded: true }
|
|
57
|
+
| InvoiceUnsupported;
|
|
58
|
+
/** Exact scope is the full identity, including operation, parent and revision.
|
|
59
|
+
* Incomplete/truncated/failed lookup is unknown. Only exact complete absence can
|
|
60
|
+
* authorize another create; found is proof, never a nullable lookup shortcut.
|
|
61
|
+
*/
|
|
62
|
+
export type InvoiceReadbackResult =
|
|
63
|
+
| { readonly kind: "found"; readonly identity: InvoiceIdentity; readonly providerDocumentRef: string; readonly providerDocumentNumber: string | null; readonly proof: InvoiceProof }
|
|
64
|
+
| { readonly kind: "absent"; readonly identity: InvoiceIdentity; readonly evidence: InvoiceProof & { readonly exact: true; readonly complete: true; readonly scope: InvoiceIdentity } }
|
|
65
|
+
| { readonly kind: "unknown"; readonly identity: InvoiceIdentity; readonly code: "unknown" }
|
|
66
|
+
| InvoiceUnsupported;
|
|
67
|
+
/** Correction bridge: positive immutable BEFORE facts, explicit zero AFTER,
|
|
68
|
+
* stable correctionRef and parent proof. Retry requires exact recovery before I/O.
|
|
69
|
+
* Carrier: donor createFullCorrection options and accountingProviderSnapshot.
|
|
70
|
+
* @consumer A4 conforming correction bridge and issue/readback adapters.
|
|
71
|
+
*/
|
|
72
|
+
export interface InvoiceProviderPort {
|
|
73
|
+
issue(input: { readonly identity: InvoiceIdentity & { readonly operation: "issue"; readonly correctionRef: null; readonly parentProviderDocumentRef: null }; readonly facts: InvoiceFacts; readonly recoveryLookupRequired: boolean }): Promise<InvoiceWriteResult>;
|
|
74
|
+
correct(input: { readonly identity: InvoiceIdentity & { readonly operation: "full_reversal"; readonly correctionRef: string; readonly parentProviderDocumentRef: string }; readonly before: InvoiceFacts; readonly after: { readonly totalNetMinor: 0; readonly totalTaxMinor: 0; readonly totalGrossMinor: 0 }; readonly reason: string; readonly recoveryLookupRequired: boolean }): Promise<InvoiceWriteResult>;
|
|
75
|
+
read(input: InvoiceIdentity): Promise<InvoiceReadbackResult>;
|
|
76
|
+
/** Remote void stays cut; always refuse before I/O, never substitute local void. */
|
|
77
|
+
void(input: InvoiceIdentity & { readonly operation: "remote_void" }): Promise<InvoiceUnsupported>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { InvoiceProof, InvoiceUnsupported } from "./provider.js";
|
|
2
|
+
/** One immutable balance item, never unique by batch+payment alone.
|
|
3
|
+
* Carrier: accountingControlContracts settlement + payout balance transaction ID.
|
|
4
|
+
* Null links and bankReceivedAt are facts; settlement is not an invoice gate.
|
|
5
|
+
* @consumer Host payout item recorder.
|
|
6
|
+
*/
|
|
7
|
+
export interface InvoiceSettlementItem {
|
|
8
|
+
readonly requestKey: string;
|
|
9
|
+
readonly providerRef: string;
|
|
10
|
+
readonly batchRef: string;
|
|
11
|
+
readonly itemRef: string;
|
|
12
|
+
readonly providerPaymentRef: string;
|
|
13
|
+
readonly paymentIntentRef: string | null;
|
|
14
|
+
readonly paymentRef: string | null;
|
|
15
|
+
readonly invoiceRef: string | null;
|
|
16
|
+
readonly grossMinor: number;
|
|
17
|
+
readonly feeMinor: number;
|
|
18
|
+
readonly netMinor: number;
|
|
19
|
+
readonly currency: string;
|
|
20
|
+
readonly status: "matched" | "unmatched" | "mismatch" | "bank_pending";
|
|
21
|
+
readonly bankReceivedAt: string | null;
|
|
22
|
+
readonly proof: InvoiceProof;
|
|
23
|
+
}
|
|
24
|
+
type ItemScope = Pick<InvoiceSettlementItem, "providerRef" | "batchRef" | "itemRef" | "providerPaymentRef">;
|
|
25
|
+
export type InvoiceSettlementResult =
|
|
26
|
+
| { readonly kind: "found"; readonly scope: ItemScope; readonly paymentIntentRef: string | null; readonly paymentRef: string | null; readonly invoiceRef: string | null; readonly proof: InvoiceProof }
|
|
27
|
+
| { readonly kind: "absent"; readonly scope: ItemScope; readonly evidence: InvoiceProof & { readonly exact: true; readonly complete: true; readonly scope: ItemScope } }
|
|
28
|
+
| { readonly kind: "recorded"; readonly itemRef: string; readonly settlementItemRef: string; readonly replayed: boolean }
|
|
29
|
+
| { readonly kind: "conflict"; readonly itemRef: string; readonly code: "identity_mismatch" | "money_mismatch" }
|
|
30
|
+
| { readonly kind: "unknown"; readonly itemRef: string; readonly code: "unknown" }
|
|
31
|
+
| InvoiceUnsupported;
|
|
32
|
+
/** Host recorder/resolver owns richer item identity. Unsupported before any write
|
|
33
|
+
* when a native batch/payment row cannot represent distinct items or nullable links.
|
|
34
|
+
* @consumer Host payout item recorder/resolver.
|
|
35
|
+
*/
|
|
36
|
+
export interface InvoiceSettlementPort {
|
|
37
|
+
record(item: InvoiceSettlementItem): Promise<Exclude<InvoiceSettlementResult, { kind: "found" | "absent" }>>;
|
|
38
|
+
resolve(input: ItemScope): Promise<Exclude<InvoiceSettlementResult, { kind: "recorded" | "conflict" }>>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { InvoiceHostPorts } from "./host.js";
|
|
2
|
+
import type { InvoiceFacts, InvoiceClaimedFacts } from "./facts.js";
|
|
3
|
+
import type { InvoiceIdentity, InvoiceDocumentIdentity, InvoiceProof, InvoiceUnsupported, InvoiceWriteResult, InvoiceReadbackResult } from "./provider.js";
|
|
4
|
+
import type { InvoiceClearanceObservation, InvoiceArtifactResult, InvoiceDeliveryPort } from "./delivery.js";
|
|
5
|
+
type InvoiceDutyStatus = "owed" | "blocked" | "ready" | "processing" | "unknown" | "issued" | "voided" | "unsupported" | "faulted";
|
|
6
|
+
/** Native duty/lease fields map to the pinned invoicing_* fixtures. Managed hosts
|
|
7
|
+
* translate their outboxId/attemptCount fences; never claim transaction equality.
|
|
8
|
+
*/
|
|
9
|
+
export interface InvoiceClaim {
|
|
10
|
+
readonly dutyId: string;
|
|
11
|
+
readonly documentId: string;
|
|
12
|
+
readonly requestId: string;
|
|
13
|
+
readonly token: string;
|
|
14
|
+
readonly generation: number;
|
|
15
|
+
readonly leaseUntil: string;
|
|
16
|
+
readonly identity: InvoiceIdentity;
|
|
17
|
+
readonly facts: InvoiceClaimedFacts;
|
|
18
|
+
readonly action: "create" | "readback";
|
|
19
|
+
readonly correction: { readonly before: InvoiceFacts; readonly reason: string; readonly after: { readonly totalNetMinor: 0; readonly totalTaxMinor: 0; readonly totalGrossMinor: 0 } } | null;
|
|
20
|
+
}
|
|
21
|
+
/** Exact camel-case shape returned by invoicing_duty_readback. This is not
|
|
22
|
+
* payment truth's accountingRequested; empty duties never assert invoice absence.
|
|
23
|
+
* @consumer Operator owed-duty readback.
|
|
24
|
+
*/
|
|
25
|
+
export interface InvoiceDutyReadback {
|
|
26
|
+
readonly orderId: string;
|
|
27
|
+
readonly captureFaults: readonly { readonly sourceId: string; readonly state: "retryable" | "faulted" | "resolved"; readonly attempts: number; readonly availableAt: string }[];
|
|
28
|
+
readonly duties: readonly {
|
|
29
|
+
readonly dutyId: string;
|
|
30
|
+
readonly parentDutyId: string | null;
|
|
31
|
+
readonly documentId: string | null;
|
|
32
|
+
readonly state: "owed" | "blocked" | "claimed" | "unknown" | "issued" | "voided" | "unsupported" | "faulted";
|
|
33
|
+
readonly attempts: number;
|
|
34
|
+
readonly factAttempts: number;
|
|
35
|
+
readonly diagnosticCode: "faulted" | "missing_facts" | "legacy_facts_unproved" | "retryable" | "blocked" | "unsupported" | "unknown" | "absent" | null;
|
|
36
|
+
readonly createdAt: string;
|
|
37
|
+
readonly availableAt: string;
|
|
38
|
+
readonly leaseUntil: string | null;
|
|
39
|
+
}[];
|
|
40
|
+
}
|
|
41
|
+
/** Every operation is one local transaction; commit before external I/O. Native
|
|
42
|
+
* success atomically saves late identity and compensation; stale catch writes nothing.
|
|
43
|
+
* Compatibility stores bind provider selection at construction; the structural
|
|
44
|
+
* native store accepts it per claim and persists the first provider on the duty.
|
|
45
|
+
* No implementation, executor or migration is installed by these declarations.
|
|
46
|
+
*/
|
|
47
|
+
export interface InvoiceStorePort {
|
|
48
|
+
/** Never call inside a renewal money, dunning or dispatch transaction without
|
|
49
|
+
* isolation: capture failure must not propagate; the sweep recovers.
|
|
50
|
+
*/
|
|
51
|
+
captureDuty(input: { readonly requestKey: string; readonly sourceKind: "paid" | "handoff"; readonly sourceId: string; readonly orderId: string; readonly occurredAt: string; readonly evidence: { readonly settlementIntentId: string; readonly amountMinor: number; readonly currency: string } | { readonly shipmentId: string; readonly handedOverAt: string } }): Promise<{ readonly dutyId: string; readonly status: InvoiceDutyStatus } | { readonly kind: "conflict"; readonly code: "identity_mismatch" } | InvoiceUnsupported>;
|
|
52
|
+
installFacts(input: { readonly dutyId: string; readonly facts: InvoiceFacts }): Promise<{ readonly dutyId: string; readonly status: InvoiceDutyStatus; readonly replayed?: boolean } | InvoiceUnsupported>;
|
|
53
|
+
/** Pinned split facts lease: sweep and prepare commit separately; finishing
|
|
54
|
+
* requires the same token and authoritative document/fact match. No host I/O
|
|
55
|
+
* under the duty lock, and unsupported never silently drops an owed duty.
|
|
56
|
+
*/
|
|
57
|
+
sweep(limit: number): Promise<{ readonly collected: number; readonly claims: readonly { readonly dutyId: string; readonly token: string }[] } | InvoiceUnsupported>;
|
|
58
|
+
prepareFacts(input: { readonly dutyId: string; readonly token: string }): Promise<{ readonly status: "owed" | "ready" | "issued" | "voided" | "unsupported" | "stale"; readonly documentId?: string; readonly requestId?: string; readonly code?: "retryable" | "legacy_facts_unproved" } | InvoiceUnsupported>;
|
|
59
|
+
finishFacts(input: { readonly dutyId: string; readonly token: string; readonly result: Omit<Exclude<Awaited<ReturnType<InvoiceStorePort["prepareFacts"]>>, InvoiceUnsupported>, "status"> & { readonly status: "owed" | "ready" | "issued" | "voided" | "unsupported" } }): Promise<{ readonly dutyId: string; readonly status: "owed" | "ready" | "issued" | "voided" | "unsupported" | "faulted" | "stale" } | InvoiceUnsupported>;
|
|
60
|
+
readDuties(orderId: string): Promise<InvoiceDutyReadback | InvoiceUnsupported>;
|
|
61
|
+
/** Filter order before locking/limit; never lease unrelated orders then filter.
|
|
62
|
+
* P7 host composition reads the injected seller before the separate first-claim SQL wrapper;
|
|
63
|
+
* its revisionRef supplies p_revision, without requiring a duty from the caller.
|
|
64
|
+
*/
|
|
65
|
+
claim(input: { readonly limit: number; readonly visibilitySeconds: number; readonly providerRef: string; readonly orderId?: string | null }): Promise<readonly InvoiceClaim[] | InvoiceUnsupported>;
|
|
66
|
+
preflight(input: { readonly claim: InvoiceClaim; readonly amountMinor: number | null; readonly currency: string | null } & (
|
|
67
|
+
| { readonly status: "succeeded" | "failed" | "pending" | "unknown" | "unavailable"; readonly proof: InvoiceProof | null }
|
|
68
|
+
| { readonly status: "absent"; readonly evidence: Extract<Awaited<ReturnType<InvoiceHostPorts["money"]["read"]>>, { kind: "absent" }>["evidence"] }
|
|
69
|
+
)): Promise<{ readonly kind: "ready" | "blocked" | "mismatch" | "unavailable" | "stale" } | InvoiceUnsupported>;
|
|
70
|
+
finish(input: { readonly claim: InvoiceClaim; readonly result: Extract<InvoiceWriteResult, { kind: "issued" }> | Extract<InvoiceReadbackResult, { kind: "found" }> }): Promise<{ readonly kind: "issued"; readonly replayed: boolean } | { readonly kind: "stale" } | InvoiceUnsupported>;
|
|
71
|
+
/** Retryable requires a definite refusal (effectMayHaveSucceeded:false) or a
|
|
72
|
+
* pre-I/O failure and phase before_send. Every other failure maps to unknown.
|
|
73
|
+
* Native result kinds other than stale/voided map to recorded.
|
|
74
|
+
*/
|
|
75
|
+
fail(input: { readonly claim: InvoiceClaim; readonly retrySeconds: number } & (
|
|
76
|
+
| { readonly kind: "absent"; readonly evidence: Extract<InvoiceReadbackResult, { kind: "absent" }> }
|
|
77
|
+
| { readonly kind: "retryable"; readonly phase: "before_send"; readonly proof: InvoiceProof | null }
|
|
78
|
+
| { readonly kind: "blocked" | "unsupported" | "unknown"; readonly proof: InvoiceProof | null }
|
|
79
|
+
)): Promise<{ readonly kind: "recorded" | "voided" | "stale" } | InvoiceUnsupported>;
|
|
80
|
+
/** Native post-write unsupported maps to unsupported_recorded, retaining duty/replay.
|
|
81
|
+
* Missing means no M09 duty, never invoice absence: retain/retry the reversal
|
|
82
|
+
* intent and route legacy documents to the incumbent path.
|
|
83
|
+
*/
|
|
84
|
+
requestReversal(input: { readonly requestKey: string; readonly orderId: string; readonly reason: "order_canceled" | "order_refunded"; readonly proof: InvoiceProof }): Promise<{ readonly kind: "missing" | "voided" | "pending_readback" | "correction_requested"; readonly dutyId?: string; readonly replayed?: boolean } | { readonly kind: "unsupported_recorded"; readonly dutyId?: string; readonly replayed?: boolean } | { readonly kind: "conflict"; readonly code: "identity_mismatch" } | InvoiceUnsupported>;
|
|
85
|
+
/** Existing local-only request remains distinct from fiscal issuance. */
|
|
86
|
+
requestLocalDocument(input: { readonly requestKey: string; readonly orderId: string; readonly providerRef: string }): Promise<{ readonly kind: "requested"; readonly documentId: string; readonly invoiceRef: string; readonly status: string; readonly replayed: boolean } | InvoiceUnsupported>;
|
|
87
|
+
listClearanceTargets(limit: number): Promise<readonly { readonly documentId: string; readonly identity: InvoiceDocumentIdentity; readonly providerDocumentRef: string; readonly providerDocumentNumber: string | null; readonly state: InvoiceClearanceObservation["state"] }[] | InvoiceUnsupported>;
|
|
88
|
+
/** Native maps clearance state unsupported to observation kind unsupported;
|
|
89
|
+
* provider_mismatch returns InvoiceUnsupported before recording anything.
|
|
90
|
+
*/
|
|
91
|
+
recordObservation(input: { readonly requestKey: string; readonly documentId: string; readonly eventRef: string; readonly eventKind: "clearance" | "artifacts" | "unsupported" | "provider_mismatch"; readonly identity: InvoiceDocumentIdentity; readonly providerDocumentRef: string; readonly providerDocumentNumber: string | null; readonly clearance: InvoiceClearanceObservation | null; readonly artifacts: readonly Extract<InvoiceArtifactResult, { kind: "found" }>["artifact"][]; readonly observedAt: string; readonly payloadHash: string; readonly source: "provider_api" | "provider_webhook" | "manual_reconciliation" }): Promise<{ readonly replayed: boolean } | InvoiceUnsupported>;
|
|
92
|
+
/** Verify M04 binding before claiming; document rows need no M09 issue duty. */
|
|
93
|
+
claimDeliveries(input: { readonly limit: number; readonly visibilitySeconds: number; readonly requireClearance: boolean; readonly orderId?: string | null }): Promise<readonly { readonly token: string; readonly generation: number; readonly identity: InvoiceDocumentIdentity; readonly providerDocumentRef: string; readonly delivery: Omit<Parameters<InvoiceDeliveryPort["send"]>[0], "filename" | "content" | "contentType"> }[] | InvoiceUnsupported>;
|
|
94
|
+
/** Native uncertain settlement passes p_receipt NULL and retains a non-null
|
|
95
|
+
* receiptRef hint in p_evidence; managed uses it as the provider message id.
|
|
96
|
+
* Managed failed_before_send uses the retrySeconds RPC argument. Native
|
|
97
|
+
* failed_before_send carries { phase: 'before_send', retryable, retrySeconds }
|
|
98
|
+
* in p_evidence: whole seconds clamp to 60..86400, absent/invalid uses 900.
|
|
99
|
+
* retryable:false parks at infinity, with failed status and last_error visible.
|
|
100
|
+
*/
|
|
101
|
+
settleDelivery(input: { readonly deliveryRef: string; readonly token: string; readonly generation: number; readonly providerDocumentRef: string; readonly retrySeconds: number; readonly result: Exclude<Awaited<ReturnType<InvoiceDeliveryPort["send"]>>, InvoiceUnsupported> }): Promise<{ readonly kind: "recorded" | "stale"; readonly replayed: boolean } | InvoiceUnsupported>;
|
|
102
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Contribution, RequiredSchema } from "@openlup/core/readiness";
|
|
2
|
+
|
|
3
|
+
/** Inert descriptors for the host's existing registry and executor. No registration. */
|
|
4
|
+
export function createInvoiceContribution(input: {
|
|
5
|
+
version: string;
|
|
6
|
+
requiredSchema: RequiredSchema;
|
|
7
|
+
invoicing: Readonly<Record<string, ((...args: never[]) => unknown) | null>>;
|
|
8
|
+
handlers: Contribution["handlers"];
|
|
9
|
+
schedules: Contribution["schedules"];
|
|
10
|
+
routes: Contribution["routes"];
|
|
11
|
+
}) {
|
|
12
|
+
const contribution: Contribution = {
|
|
13
|
+
manifest: {
|
|
14
|
+
name: "@openlup/platform", version: input.version, kind: "rail",
|
|
15
|
+
module: { id: "invoicing", kind: "capability", provides: ["./invoicing"], dependsOn: [] },
|
|
16
|
+
emits: [], handles: input.handlers.map(handler => handler.eventType), env: [],
|
|
17
|
+
schedules: input.schedules.map(({ id, cadenceSeconds }) => ({ id, cadenceSeconds })),
|
|
18
|
+
requiredPorts: Object.keys(input.invoicing), routes: input.routes.map(route => route.id), requiredSchema: input.requiredSchema,
|
|
19
|
+
},
|
|
20
|
+
handlers: input.handlers, schedules: input.schedules, routes: input.routes,
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
contributions: [contribution],
|
|
24
|
+
ports: Object.fromEntries(Object.entries(input.invoicing).filter(([, fn]) => typeof fn === "function").map(([name, fn]) => [`@openlup/platform/invoicing:${name}`, fn])),
|
|
25
|
+
routes: Object.fromEntries(input.routes.map(route => [`@openlup/platform/invoicing:${route.id}`, route.handle])),
|
|
26
|
+
triggers: Object.fromEntries(input.schedules.map(schedule => [`@openlup/platform/invoicing:${schedule.id}`, schedule.run])),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { InvoiceWriteResult } from "./contracts/provider.js";
|
|
2
|
+
|
|
3
|
+
/** Durable correction identity, recovery and claim fencing from the donor worker.
|
|
4
|
+
* Hosts project immutable BEFORE facts and bind a conforming recovery provider.
|
|
5
|
+
* @consumer server/domains/accounting/accountingJobService.ts
|
|
6
|
+
*/
|
|
7
|
+
export async function runInvoiceCorrectionJob<Claim extends {
|
|
8
|
+
outboxId: string; attemptCount: number; providerInvoiceId: string;
|
|
9
|
+
correctionReason: string; payload: Record<string, unknown>;
|
|
10
|
+
}, Snapshot, Result extends Pick<Extract<InvoiceWriteResult, { kind: "issued" }>, "providerDocumentRef" | "providerDocumentNumber">>(input: {
|
|
11
|
+
createEnabled: boolean;
|
|
12
|
+
providerEnabled: boolean;
|
|
13
|
+
claim(): Promise<readonly Claim[]>;
|
|
14
|
+
project(claim: Claim): Snapshot;
|
|
15
|
+
/** Resolve accepted/recovered receipts only; unknown is never an ACK or absence. */
|
|
16
|
+
create(snapshot: Snapshot, options: {
|
|
17
|
+
correctionReason: string; correctedProviderInvoiceId: string;
|
|
18
|
+
correctionRef: string; recoveryLookupRequired: boolean;
|
|
19
|
+
}): Promise<Result>;
|
|
20
|
+
finish(claim: Claim, result: Result): Promise<unknown>;
|
|
21
|
+
isClaimFenceError(error: unknown): boolean;
|
|
22
|
+
fail(claim: Claim, error: unknown, retrySeconds: number): Promise<unknown>;
|
|
23
|
+
}) {
|
|
24
|
+
if (!input.createEnabled) return skipped("create_disabled");
|
|
25
|
+
if (!input.providerEnabled) return skipped("provider_disabled");
|
|
26
|
+
const claims = await input.claim();
|
|
27
|
+
let updated = 0;
|
|
28
|
+
let failures = 0;
|
|
29
|
+
for (const claim of claims) {
|
|
30
|
+
try {
|
|
31
|
+
const correctionRef = `accounting-correction:${claim.outboxId}`;
|
|
32
|
+
if (claim.payload.providerCreateIdentity !== correctionRef) {
|
|
33
|
+
throw new Error("accounting_correction_recovery_identity_missing");
|
|
34
|
+
}
|
|
35
|
+
const snapshot = input.project(claim);
|
|
36
|
+
const result = await input.create(snapshot, {
|
|
37
|
+
correctionReason: claim.correctionReason,
|
|
38
|
+
correctedProviderInvoiceId: claim.providerInvoiceId,
|
|
39
|
+
correctionRef,
|
|
40
|
+
recoveryLookupRequired: claim.attemptCount > 1,
|
|
41
|
+
});
|
|
42
|
+
await input.finish(claim, result);
|
|
43
|
+
updated += 1;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
failures += 1;
|
|
46
|
+
if (input.isClaimFenceError(error)) continue;
|
|
47
|
+
try {
|
|
48
|
+
await input.fail(claim, error, Math.min(3600, 300 * Math.max(1, claim.attemptCount)));
|
|
49
|
+
} catch (failureError) {
|
|
50
|
+
if (!input.isClaimFenceError(failureError)) throw failureError;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { ok: failures === 0, checked: claims.length, updated, skipped: false, failures };
|
|
55
|
+
}
|
|
56
|
+
function skipped(reason: string) { return { ok: true, checked: 0, updated: 0, skipped: true, reason, failures: 0 }; }
|