@jskit-ai/payments-core 0.1.1

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/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # Application-owned payments
2
+
3
+ `@jskit-ai/payments-core` supplies JavaScript libraries for a generated or
4
+ hand-written Node application. No editor, hosted platform or coding assistant
5
+ is required to run them. PHP applications implement the same documented
6
+ contract using their own framework; they do not install this runtime.
7
+
8
+ Current implementation: a strict payment declaration, transactional Knex
9
+ storage, subscription feature checks, usage credits, official-SDK Stripe and
10
+ Paddle webhook verification, subscription reconciliation, checkout and customer
11
+ portal adapters, and an explicitly authorized checkout composition service.
12
+ Reviewed catalogue publication preserves environment-bound product/price IDs and
13
+ records ambiguous provider writes for recovery. Editor execution wiring and
14
+ automatic merchant onboarding remain separate unfinished work.
15
+
16
+ ## Ownership and installation
17
+
18
+ The application installs this package and its selected Knex database driver,
19
+ loads `integrations.json`, resolves referenced Env values server-side and runs
20
+ the package's `migrations` through normal JSKIT migration discovery. The package
21
+ never prepares a database itself. Its tables contain application runtime data;
22
+ they are not an editor configuration database.
23
+
24
+ Public API imports:
25
+
26
+ - `@jskit-ai/payments-core/shared`: `validatePaymentConfiguration(document)`.
27
+ - `@jskit-ai/payments-core/server/storage`: `createKnexPaymentStore({ knex })`.
28
+ - `@jskit-ai/payments-core/server`: `createPaymentService({ store, configuration, clock })`.
29
+ - `@jskit-ai/payments-core/server/stripe`: `createStripePaymentAdapter(options)`.
30
+ - `@jskit-ai/payments-core/server/paddle`: `createPaddlePaymentAdapter(options)`.
31
+ - `@jskit-ai/payments-core/server/checkout`: `createPaymentCheckoutService(options)`.
32
+
33
+ The app authorizes billing identities and mounts its own routes. A billable
34
+ subject may be a user or an organization. Never derive that subject from an
35
+ untrusted customer ID or webhook metadata. The checkout service requires an
36
+ `authorize(actor, { subjectId, action })` function that returns exactly `true`.
37
+ The app must distinguish `account`, `history`, `checkout`, `portal` and
38
+ administrator-only `reconcile`. `history` reads customer-scoped subscriptions
39
+ and invoice/transaction pages with no provider writes; its contract and CLI
40
+ composition are in [standalone setup](docs/standalone.md#provider-billing-history-from-an-app-or-cli).
41
+ Core grant/debit/reconciliation methods are trusted server APIs, not public
42
+ routes. The app authorizes them before calling them.
43
+
44
+ See [the portable contract](docs/contract.md) for file semantics and framework
45
+ requirements. [Configuration schema](contracts/configuration.schema.json) is
46
+ ordinary draft-07 JSON Schema and contains no executable JavaScript.
47
+ The [conformance guide](docs/conformance.md) supplies static JSON examples and
48
+ expected account outcomes for native-framework implementations and CLI users.
49
+
50
+ For concrete file loading, Env resolution and provider composition, see
51
+ [standalone setup](docs/standalone.md).
52
+
53
+ ## First composition
54
+
55
+ 1. Validate the complete integration document with the connector validator,
56
+ then validate its `extensions.payments` with this package.
57
+ 2. Select the environment on the server. Obtain its integration ID, provider
58
+ account ID, API-key Env reference, signing secret and return URL. Do not
59
+ accept environment, merchant ID or credentials from a browser.
60
+ 3. Create the store with the app's transactional Knex client and create the
61
+ payment service with the validated configuration.
62
+ 4. Load the environment's durable logical-plan-to-provider-price bindings.
63
+ Create one Stripe or Paddle adapter using `apiKey`, `webhookSecret`,
64
+ `environment`, `providerAccountId` (Stripe), `taxCategory` (Paddle), and `priceBindings`. Account
65
+ identity in this configuration is not a claim of provider verification.
66
+ 5. Create the checkout service with `adapter`, `store`, `payments`,
67
+ `merchantScope`, `returnUrl`, and the app's `authorize` function.
68
+ 6. Authenticated app routes call `checkout({actor, subjectId, email, planId,
69
+ requestId})` or `portal({actor, subjectId})`. Keep the returned checkout
70
+ request ID stable on transport retries; begin a new request for an expired
71
+ checkout session. The library returns provider URLs to navigate to.
72
+ 7. The webhook route passes a raw `Buffer` and the provider signature header
73
+ to `webhook({rawBody, signature})`. Do not parse and reserialize its body.
74
+ Return success only after completion; preserve failure/retry visibility.
75
+ 8. Protect app actions with `requireFeature(scope, feature)` and, where
76
+ applicable, `debitCredits(scope, {reference, units})`. For a failed job,
77
+ `refundDebit(scope, {debitReference})` restores its unexpired allocation once.
78
+
79
+ `merchantScope` has `applicationId`, `integrationId`, `providerAccountId`,
80
+ `environment`. Account methods additionally require `subjectId`. All are
81
+ server-owned values. CLI scripts call these same libraries with trusted scope.
82
+
83
+ Paddle's return URL is an approved app-owned checkout page that initializes
84
+ Paddle.js using a **public client token** and opens the transaction supplied in
85
+ the checkout URL. It is not just a generic success page. Stripe uses hosted
86
+ Checkout and a return page. Both providers' customer portals remain provider
87
+ hosted. Provider account registration and Paddle domain approval are separate
88
+ setup tasks; a successful API request does not prove approval.
89
+
90
+ ## Failures and evidence
91
+
92
+ Checkout writes an intent before contacting the provider. A crash or ambiguous
93
+ failure leaves it pending and prevents another write. The administrator invokes
94
+ `reconcilePending` with a trusted server inspection function that checks provider
95
+ activity and returns the recovered result or `confirmedNotCreated: true`.
96
+ Neither a browser assertion nor a blind retry is evidence. The callback must not
97
+ be exposed as user-supplied executable code. Recovery does not require platform
98
+ access: an ordinary authorized application CLI can invoke it.
99
+
100
+ Official SDKs own signature verification. Normal subscription reconciliation
101
+ fetches current provider facts while holding the account transaction lock; old
102
+ event payloads cannot resurrect canceled subscriptions. Provider failures roll
103
+ back receipts and grants. Keep this path bounded by application request timeouts.
104
+ The package deliberately handles a single recurring price, quantity one, per
105
+ subscription; unsupported shapes fail visibly rather than granting guessed
106
+ features or allowances.
107
+
108
+ Focused fixture evidence currently covers raw signatures with both official
109
+ SDKs, storage rollback, duplicate grants/renewals, subject/environment isolation,
110
+ concurrent debit requests, expired refunds, checkout authorization, uncertain
111
+ writes and explicit recovery. SQLite fixtures do not establish PostgreSQL/MySQL
112
+ multi-process locking behavior. Live provider accounts and full generated-app
113
+ acceptance have not been exercised.
114
+
115
+
116
+ Catalogue operations are exported from `./server/catalogue` as
117
+ `createPaymentCatalogue({store, adapter, scope, configuration})`. `preview()`
118
+ returns proposed changes, provider drift and a review ID. `publish({reviewId})`
119
+ rechecks that review before writes; partial completion and a pending request
120
+ remain durable. `recover` requires an authorized server administrator to inspect
121
+ a provider object or explicitly establish that the request did not create one.
122
+ Never wire that assertion directly to an untrusted client. Old price mappings
123
+ remain in catalogue history. Pass current plan-to-price mappings as
124
+ `priceBindings` and historical price-to-plan mappings as `historicalPriceBindings`
125
+ when constructing payment adapters. A removed plan needs explicit handling for
126
+ existing subscribers; publication does not cancel their subscriptions.
127
+
128
+ ### Webhook event selection
129
+
130
+ Configure Stripe deliveries for `invoice.paid`, `invoice.payment_failed`,
131
+ `invoice.payment_action_required`, and the `customer.subscription.*` lifecycle
132
+ used by the application. Configure Paddle for `transaction.completed` and
133
+ `subscription.*` lifecycle deliveries. The provider adapter selects relevant
134
+ subscription events only after signature verification; signed standalone
135
+ invoices/transactions are ignored before application customer lookup.
136
+
137
+ Stripe invoice payloads must use the SDK-supported `parent.subscription_details`
138
+ shape. An absent parent field is a configuration/version error, rather than a
139
+ successful ignored delivery. An explicit null parent denotes a standalone
140
+ invoice. Paddle uses the SDK-normalized `subscriptionId` for completed
141
+ transactions. Configure webhook versions consistently with the installed SDK.
142
+
143
+ Reconciliation fetches current provider state instead of trusting event order.
144
+ Failed-payment notifications update subscription state without awarding unpaid
145
+ renewal credits. A delayed notification received after payment recovery can
146
+ reconcile the now-paid invoice; its invoice identity still deduplicates the
147
+ credit grant. Monetary refund and dispute clawbacks remain unimplemented.
@@ -0,0 +1,148 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Application payment declaration, integrations.json extensions.payments",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": [
7
+ "version",
8
+ "environments",
9
+ "plans"
10
+ ],
11
+ "properties": {
12
+ "version": {
13
+ "const": 1
14
+ },
15
+ "environments": {
16
+ "type": "object",
17
+ "additionalProperties": false,
18
+ "minProperties": 1,
19
+ "properties": {
20
+ "sandbox": {
21
+ "$ref": "#/definitions/environment"
22
+ },
23
+ "live": {
24
+ "$ref": "#/definitions/environment"
25
+ }
26
+ }
27
+ },
28
+ "plans": {
29
+ "type": "object",
30
+ "minProperties": 1,
31
+ "maxProperties": 100,
32
+ "propertyNames": {
33
+ "$ref": "#/definitions/id"
34
+ },
35
+ "additionalProperties": {
36
+ "$ref": "#/definitions/plan"
37
+ }
38
+ }
39
+ },
40
+ "definitions": {
41
+ "id": {
42
+ "type": "string",
43
+ "pattern": "^[a-z][a-z0-9-]*$",
44
+ "maxLength": 100,
45
+ "not": {
46
+ "enum": [
47
+ "constructor",
48
+ "prototype"
49
+ ]
50
+ }
51
+ },
52
+ "env": {
53
+ "type": "string",
54
+ "pattern": "^env:[A-Z_][A-Z0-9_]*$",
55
+ "maxLength": 200
56
+ },
57
+ "environment": {
58
+ "type": "object",
59
+ "additionalProperties": false,
60
+ "required": [
61
+ "integrationId",
62
+ "providerAccountId",
63
+ "webhookSecretRef",
64
+ "returnUrlRef"
65
+ ],
66
+ "properties": {
67
+ "integrationId": {
68
+ "$ref": "#/definitions/id"
69
+ },
70
+ "providerAccountId": {
71
+ "type": "string",
72
+ "minLength": 1,
73
+ "maxLength": 200
74
+ },
75
+ "webhookSecretRef": {
76
+ "$ref": "#/definitions/env"
77
+ },
78
+ "returnUrlRef": {
79
+ "$ref": "#/definitions/env"
80
+ },
81
+ "taxCategory": {
82
+ "type": "string",
83
+ "enum": [
84
+ "digital-goods",
85
+ "ebooks",
86
+ "implementation-services",
87
+ "professional-services",
88
+ "saas",
89
+ "software-programming-services",
90
+ "standard",
91
+ "training-services",
92
+ "website-hosting"
93
+ ]
94
+ },
95
+ "publicClientTokenRef": {
96
+ "$ref": "#/definitions/env"
97
+ }
98
+ }
99
+ },
100
+ "plan": {
101
+ "type": "object",
102
+ "additionalProperties": false,
103
+ "required": [
104
+ "name",
105
+ "amount",
106
+ "currency",
107
+ "interval",
108
+ "features",
109
+ "renewalCredits"
110
+ ],
111
+ "properties": {
112
+ "name": {
113
+ "type": "string",
114
+ "minLength": 1,
115
+ "maxLength": 200
116
+ },
117
+ "amount": {
118
+ "type": "integer",
119
+ "minimum": 1,
120
+ "maximum": 1000000000
121
+ },
122
+ "currency": {
123
+ "type": "string",
124
+ "pattern": "^[A-Z]{3}$"
125
+ },
126
+ "interval": {
127
+ "enum": [
128
+ "month",
129
+ "year"
130
+ ]
131
+ },
132
+ "features": {
133
+ "type": "array",
134
+ "maxItems": 100,
135
+ "uniqueItems": true,
136
+ "items": {
137
+ "$ref": "#/definitions/id"
138
+ }
139
+ },
140
+ "renewalCredits": {
141
+ "type": "integer",
142
+ "minimum": 0,
143
+ "maximum": 1000000000
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }