@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.
@@ -0,0 +1,91 @@
1
+ # Portable payment examples and conformance
2
+
3
+ `contracts/conformance.json` is ordinary JSON, also exported as
4
+ `@jskit-ai/payments-core/conformance.json`. Read the file as data; no Node process,
5
+ JSKIT import, editor service or Genesis command is necessary to interpret it.
6
+ It accompanies the draft-07 configuration schema and the semantic contract.
7
+ These are test examples, not a production workflow interpreter.
8
+
9
+ ## Configuration examples
10
+
11
+ Each `configurationCases` entry supplies a complete payment-relevant document
12
+ and two expected booleans:
13
+
14
+ - `schemaValid`: validate `document.extensions.payments` against
15
+ `configuration.schema.json` using a draft-07 validator, without coercing values,
16
+ injecting defaults or removing unknown properties.
17
+ - `configurationValid`: after that validation, apply the connection references
18
+ below. This does not replace full integration-document validation.
19
+
20
+ For each named payment environment:
21
+
22
+ 1. Resolve `integrationId` from `document.integrations`; absence fails.
23
+ 2. Require provider `stripe` or `paddle`, `accountMode` equal to `shared`, and
24
+ `authentication.method` equal to `api-key`.
25
+ 3. Require `authentication.secretRef` to match `env:[A-Z_][A-Z0-9_]*` in its
26
+ entirety. This describes a reference; never read or copy a real secret into
27
+ the test fixture or source configuration.
28
+ 4. For Paddle, require the integration's `settings.environment` to equal the
29
+ payment environment, and require `taxCategory` and `publicClientTokenRef` in
30
+ the payment binding. Their values already passed the payment schema.
31
+
32
+ The examples distinguish malformed prices/credits/features and inline secrets
33
+ from structurally valid but unresolved or mismatched merchant connections.
34
+ The fixture credentials are references only. Passing validation does not prove
35
+ that an Env value exists, that a merchant owns a key, or that it can take payments.
36
+ Resolve and verify those resources separately at application setup.
37
+
38
+ ## Account behavior sequence
39
+
40
+ `accountScenario` supplies the payment-relevant configuration, merchant and
41
+ subject scope, initial UTC clock in milliseconds, and a provider customer to
42
+ bind to that subject. Start with a fresh transactional fixture database. Process
43
+ `steps` in order without contacting a provider:
44
+
45
+ - `clock` replaces the test clock for that and later steps.
46
+ - `subjectId` selects another subject for that step only. Otherwise use the
47
+ scenario's subject. This models trusted app composition, not a public request.
48
+ - `operation` names the semantic operation in the contract. Map it explicitly
49
+ to the framework's service in the test; do not expose dynamic method invocation
50
+ as an app endpoint.
51
+ - `input` is the operation input. For `reconcileEvent`, `facts` is the current
52
+ provider snapshot returned by the controlled loader after customer binding.
53
+ Raw webhook authentication is tested separately; these facts are not an
54
+ unsigned webhook payload to accept in production.
55
+ - `expect` asserts every listed field recursively. Arrays and primitive values
56
+ must match exactly. Unlisted result fields are permitted; extra array entries
57
+ are not. `error` instead requires that semantic error code and a rolled-back
58
+ operation, preserving state for later steps.
59
+
60
+ The sequence covers initial paid renewal, duplicate delivery, another delivery
61
+ for the same invoice, stable debit references, mismatched retry input, isolation
62
+ from another tenant, expiry exactly at period end, expired refunds, promotional
63
+ credits and rejection of overspending. Credit expiry and feature access are
64
+ separate assertions. An expired allowance never becomes a fresh grant on retry.
65
+
66
+ ## Framework and CLI ownership
67
+
68
+ A Laravel application uses its own JSON parser/validator, policies, models,
69
+ transactions and test tools to implement these cases. An assistant can translate
70
+ the sequence into that app's PHPUnit/Pest tests without installing this JavaScript
71
+ runtime. The application selects its billable model and schema; the fixture does
72
+ not prescribe table names or copy JSKIT's storage internals into another framework.
73
+
74
+ A Node CLI consumer can run the same semantic calls against its installed JSKIT
75
+ services. Genesis may describe the project's test command, but these fixtures do
76
+ not require Genesis to execute it. Vibe64 configuration must preserve these same
77
+ values rather than inventing a different hosted payment format.
78
+
79
+ ## Evidence boundaries
80
+
81
+ The package's `test/conformance.test.js` checks all configuration examples and
82
+ the full account sequence against the actual JSKIT service and SQLite storage.
83
+ An independent Python draft-07 validator plus the explicit reference rules also
84
+ checked the 11 configuration examples without importing Node or JSKIT. This
85
+ establishes independent interpretation of configuration, not a PHP payment engine.
86
+
87
+ Native Laravel execution, cross-process MySQL/PostgreSQL transactions, raw webhook
88
+ delivery and real provider checkout are separate evidence. These examples do not
89
+ certify them. The editor management-command conformance also remains a separate
90
+ consumer contract; portable account tests cannot substitute for its authorization,
91
+ review, source/release and environment checks.
@@ -0,0 +1,126 @@
1
+ # Portable payment contract v1
2
+
3
+ The application owns this contract and its runtime data. A UI and a CLI edit the
4
+ same `integrations.json` declaration at `extensions.payments`. Its normative
5
+ structural definition is `../contracts/configuration.schema.json`, shipped as
6
+ `@jskit-ai/payments-core/configuration.schema.json`. Any JSON Schema draft-07
7
+ validator can consume it. Cross-reference constraints below supplement it.
8
+
9
+ Example extension (inside the ordinary integrations document):
10
+
11
+ ```json
12
+ {
13
+ "payments": {
14
+ "version": 1,
15
+ "environments": {
16
+ "sandbox": {
17
+ "integrationId": "billing",
18
+ "providerAccountId": "acct_replace_with_your_account",
19
+ "webhookSecretRef": "env:PAYMENT_WEBHOOK_SECRET",
20
+ "returnUrlRef": "env:PAYMENT_RETURN_URL"
21
+ }
22
+ },
23
+ "plans": {
24
+ "pro": {
25
+ "name": "Pro",
26
+ "amount": 1200,
27
+ "currency": "USD",
28
+ "interval": "month",
29
+ "features": ["export"],
30
+ "renewalCredits": 100
31
+ }
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ Each integration reference must select a shared Stripe/Paddle API-key slot
38
+ using an Env reference. Paddle's connector environment must equal the payment
39
+ environment. Paddle also requires `taxCategory` and `publicClientTokenRef`
40
+ (an Env reference to its public frontend token). Each environment has its own merchant, signing secret and price
41
+ bindings. The same source declaration can name both environments, but callers
42
+ cannot select live operations merely by submitting `environment: live`.
43
+ The server owns environment selection and supplies the matching Env projection.
44
+
45
+ An amount is an integer in the currency's smallest unit. ISO-looking currency
46
+ syntax is not evidence the provider accepts that currency. Initial plans are
47
+ one recurring item, quantity one, monthly or yearly. Logical plan IDs are source
48
+ identities; external product and price IDs are environment/account-bound runtime
49
+ mappings. Publishing those mappings must preserve historical prices referenced
50
+ by existing subscriptions. Never infer a mapping from display names or assign
51
+ a browser-supplied provider price to a plan.
52
+
53
+ ## Access and credits
54
+
55
+ A subscription grants its plan's named features only while its current fetched
56
+ status is `active` and its period end is in the future. Trials, past-due, unpaid,
57
+ paused, incomplete, expired and canceled subscriptions grant no features in v1.
58
+ Cancel-at-period-end remains active until the provider ends the subscription.
59
+ The app may deliberately add its own trial/free-feature policy outside this
60
+ paid entitlement check; it must not silently reinterpret these states.
61
+
62
+ An initial paid subscription period or ordinary paid renewal grants the plan's
63
+ `renewalCredits` once, keyed by the provider invoice/transaction, not delivery
64
+ ID. Zero means no grant. Proration/update events do not grant a fresh allowance.
65
+ Renewal credits expire at the paid period end and never roll over. Late delivery
66
+ may record already-expired credits; it must not give a new period accidentally.
67
+ Cancellation removes feature access but does not revoke an existing credit lot
68
+ before its recorded expiry. Monetary refunds/chargebacks do not yet implement
69
+ automatic credit clawback: that remains explicit application reconciliation.
70
+
71
+ A trusted app can grant top-up or promotional usage units with `grantCredits`:
72
+ positive integer `units`, stable business `reference`, and either an explicit
73
+ millisecond expiry or `null` for no expiry. This call does not collect money or
74
+ prove payment. Only call it after the app's own verified funding/business event.
75
+
76
+ Debits consume the soonest-expiring valid lot first and cannot create a negative
77
+ balance. A stable reference denotes one business action. Reusing it with a
78
+ different quantity fails. Full debit refunds happen once per debit, return
79
+ units only to still-valid lots, and report the amount already expired. Partial
80
+ refunds, monetary balances and transferable credits are outside v1 semantics.
81
+
82
+ Provider event receipts, subscription changes and allowance grants commit in
83
+ one transaction. Customer binding is unique within application, integration,
84
+ merchant and environment. Different delivery IDs for the same paid invoice
85
+ cannot produce another grant. Reconciliation loads current provider state;
86
+ provider event timestamps alone are insufficient ordering evidence.
87
+
88
+ ## Framework implementation contract
89
+
90
+ Laravel uses its own authentication, policies, ORM/migrations, transaction and
91
+ row-locking APIs, provider PHP SDKs, routes and browser components. No PHP belongs
92
+ in this JavaScript package and no Node sidecar is required. A framework adapter
93
+ must preserve these observable behaviors:
94
+
95
+ | Operation | Required authority and outcome |
96
+ |---|---|
97
+ | Inspect account | Authenticated access to the billable subject; balance, features, subscriptions; no remote mutation |
98
+ | Billing history | Explicit billing-read policy for the subject; app-resolved customer, one filtered provider page, bounded display fields; no grant or mutation |
99
+ | Checkout | Billing-management policy, configured logical plan, trusted contact email and stable request ID; provider URL |
100
+ | Portal | Billing-management policy, server-bound customer; provider URL |
101
+ | Verify webhook | Raw bytes and signing secret for the exact environment; no reliance on a browser session |
102
+ | Reconcile event | Verified event, server customer binding, fresh provider facts and atomic receipt/state/grant |
103
+ | Debit/refund | Authorized app business action, stable reference and atomic balance change |
104
+ | Resolve uncertain operation | Explicit administrator policy and provider evidence; never blind retry |
105
+
106
+ Persistence must serialize mutations to the same scoped account and uniquely
107
+ index business receipts. A missing/failed transaction cannot fall back to
108
+ unlocked writes. Store customer mappings and catalogue bindings in the app's
109
+ database, not editor state. Retain an operation intent across crashes before
110
+ performing remote creation; refuse duplicates until reconciliation resolves it.
111
+
112
+ When integrating Laravel, give its assistant this document, the JSON schema and
113
+ [portable conformance fixtures](conformance.md),
114
+ the application's chosen database and billable identity, and its configured
115
+ provider. Ask it to use the installed framework/provider APIs to implement this
116
+ contract and test the fixture scenarios in the package tests. Those scenarios
117
+ are evidence requirements, not permission to import JavaScript into PHP or a
118
+ claim that a Laravel implementation already exists. The static fixtures require
119
+ no JavaScript execution. The application's editor-command implementation must
120
+ add the invoking host's operation contract separately; this package does not
121
+ parse Stack files or implement editor authorization.
122
+
123
+ Configuration failures use `payment_configuration_invalid` (422). Field errors
124
+ identify `extensions.payments/...` paths, including cross-reference failures at
125
+ `environments/<environment>/integrationId` and missing Paddle token/tax-category
126
+ fields. Messages explain the repair without echoing credential values.
@@ -0,0 +1,259 @@
1
+ # Set up payments without an editor
2
+
3
+ A hand-written JSKIT application uses exactly the same payment declaration as an
4
+ application configured through an editor. The application reads the file; the
5
+ library receives the parsed configuration. There is no editor discovery, remote
6
+ configuration lookup, or dependency on an editor session.
7
+
8
+ Install `@jskit-ai/payments-core` and `@jskit-ai/connectors-core` alongside the
9
+ application's existing Knex integration and selected database driver. These
10
+ packages are currently worktree implementations: registry installation must wait
11
+ for the coordinated release. Run the payment package migrations through the
12
+ application's normal migration discovery before starting billing routes.
13
+
14
+ ## Prepare provider access
15
+
16
+ Create an application-owned merchant account and key using the
17
+ [Stripe setup guide](../../connectors-catalog/docs/stripe.md) or
18
+ [Paddle setup guide](../../connectors-catalog/docs/paddle.md). The basic connector
19
+ check reads only balances or products; it does not verify billing permissions.
20
+
21
+ For the current Stripe payment adapter, the key must allow account and balance
22
+ reads, invoice and subscription reads, customer/product/price writes, and Checkout
23
+ and customer portal session creation. These correspond to the SDK calls in
24
+ `src/server/stripe.js`; grant only the resource permissions used by the app.
25
+ Stripe's [restricted-key guide](https://docs.stripe.com/keys/restricted-api-keys)
26
+ explains how to create/edit a key and inspect failed request logs. Connected-account
27
+ permissions are unnecessary for this app-owned merchant flow. Configure the
28
+ customer portal in Stripe's Billing settings separately.
29
+
30
+ For Paddle, enable `product.read`, `product.write`, `price.read`, `price.write`,
31
+ `customer.write`, `transaction.read`, `transaction.write`, `subscription.read`
32
+ and `customer_portal_session.write` for the current complete billing composition.
33
+ See the [permission reference](https://developer.paddle.com/api-reference/about/permissions/).
34
+ Notification destination editing additionally requires `notification_setting.write`;
35
+ domain approval lookup requires `checkout_domain.read`. Those connector operations
36
+ are separate from receiving and verifying webhooks.
37
+
38
+ For each environment, create a webhook destination pointing at the app's own
39
+ receiver and save its signing secret in the configured Env reference. Use the
40
+ [event selection instructions](../README.md#webhook-event-selection). A provider
41
+ API key is not a webhook signing secret. Keep sandbox and live credentials,
42
+ destinations and catalogue mappings separate. No live permission check or merchant
43
+ approval is inferred from the package's controlled tests.
44
+
45
+ ## Declare the application's plans
46
+
47
+ Add this payment extension to the application's integration document. `billing`
48
+ refers to a shared Stripe integration whose API key is an Env reference. The
49
+ merchant account ID below is illustrative; replace it with the actual account.
50
+ Keep the complete integration document valid under the connector schema too.
51
+
52
+ ```json
53
+ {
54
+ "version": 1,
55
+ "environments": {
56
+ "sandbox": {
57
+ "integrationId": "billing",
58
+ "providerAccountId": "acct_replace_me",
59
+ "webhookSecretRef": "env:STRIPE_WEBHOOK_SECRET",
60
+ "returnUrlRef": "env:BILLING_RETURN_URL"
61
+ }
62
+ },
63
+ "plans": {
64
+ "pro": {
65
+ "name": "Pro",
66
+ "amount": 1200,
67
+ "currency": "USD",
68
+ "interval": "month",
69
+ "features": ["export"],
70
+ "renewalCredits": 100
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ Save that object at `extensions.payments` in `integrations.json`. Keep the
77
+ provider API key in the environment named by
78
+ `integrations.billing.authentication.secretRef`. Set the signing secret and
79
+ return URL through the application's normal environment mechanism. Missing
80
+ values, including the placeholder `MISSING`, must fail setup visibly.
81
+
82
+ ## Compose the server libraries
83
+
84
+ This is application startup code. `knex`, `authorizeBilling`, `applicationId`,
85
+ `environment` and `configurationUrl` are supplied by the application's existing
86
+ server composition. Select them on the server. `applicationId` is a durable
87
+ application identity, not its current domain. The environment is explicitly
88
+ `sandbox` or `live`; do not silently fall back between them.
89
+
90
+ ```js
91
+ import { readFile } from 'node:fs/promises';
92
+ import { createEnvironmentReferenceResolver } from '@jskit-ai/connectors-core/server';
93
+ import { validatePaymentConfiguration } from '@jskit-ai/payments-core/shared';
94
+ import { createKnexPaymentStore } from '@jskit-ai/payments-core/server/storage';
95
+ import { createPaymentService } from '@jskit-ai/payments-core/server';
96
+ import { createStripePaymentAdapter } from '@jskit-ai/payments-core/server/stripe';
97
+ import { createPaymentCheckoutService } from '@jskit-ai/payments-core/server/checkout';
98
+ import { createPaymentCatalogue } from '@jskit-ai/payments-core/server/catalogue';
99
+
100
+ const document = JSON.parse(await readFile(configurationUrl, 'utf8'));
101
+ // Also run the application's complete connector validation with its provider list.
102
+ const configuration = validatePaymentConfiguration(document);
103
+ if (!Object.hasOwn(configuration.environments, environment)) {
104
+ throw new Error('Configure the selected payment environment first.');
105
+ }
106
+ const binding = configuration.environments[environment];
107
+ const integration = document.integrations[binding.integrationId];
108
+ if (integration.provider !== 'stripe') throw new Error('This composition selects Stripe.');
109
+ const resolve = createEnvironmentReferenceResolver(process.env);
110
+ const apiKey = await resolve(integration.authentication.secretRef);
111
+ const webhookSecret = await resolve(binding.webhookSecretRef);
112
+ const returnUrl = await resolve(binding.returnUrlRef);
113
+ const merchantScope = {
114
+ applicationId, integrationId: binding.integrationId,
115
+ providerAccountId: binding.providerAccountId, environment
116
+ };
117
+ const store = createKnexPaymentStore({ knex });
118
+ const payments = createPaymentService({ store, configuration });
119
+ const published = await store.inspectCatalogue(merchantScope);
120
+ const priceBindings = Object.fromEntries(Object.entries(published.plans)
121
+ .filter(([planId, plan]) => Object.hasOwn(configuration.plans, planId) && plan.priceId)
122
+ .map(([planId, plan]) => [planId, plan.priceId]));
123
+ const historicalPriceBindings = Object.fromEntries(published.history
124
+ .map((plan) => [plan.priceId, plan.planId]));
125
+ const adapter = createStripePaymentAdapter({
126
+ apiKey, webhookSecret, environment, providerAccountId: binding.providerAccountId,
127
+ priceBindings, historicalPriceBindings
128
+ });
129
+ const catalogue = createPaymentCatalogue({ store, adapter, scope: merchantScope, configuration });
130
+ const checkout = createPaymentCheckoutService({
131
+ adapter, store, payments, merchantScope, returnUrl, authorize: authorizeBilling
132
+ });
133
+ ```
134
+
135
+ The returned objects stay inside the backend. Never serialize the environment,
136
+ adapter or secret-bearing startup variables to the client. `authorizeBilling`
137
+ must return exactly `true` only for an actor allowed to manage the requested
138
+ billable subject and action. A subject can be a workspace or user; the app owns
139
+ that decision and obtains its ID from authenticated membership, not request
140
+ claims alone.
141
+
142
+ An authorized administrator first calls `catalogue.preview()`, reviews its
143
+ changes and calls `catalogue.publish({reviewId})`. An ordinary application CLI
144
+ can do this. Configuration reads never publish anything. After successful
145
+ publication, rebuild this composition from the current bindings (or restart the
146
+ app) before accepting checkout requests. An empty catalogue cannot sell a plan.
147
+
148
+ Map authenticated checkout and portal routes to the corresponding `checkout`
149
+ methods. Map the webhook route to `checkout.webhook({rawBody, signature})`,
150
+ preserving the unmodified request Buffer. Enforce features using
151
+ `payments.requireFeature({...merchantScope, subjectId}, 'export')`; debit credits
152
+ using a stable business-operation reference. A successful checkout redirect is
153
+ not proof of payment: verified provider reconciliation updates access and credits.
154
+
155
+ ## Paddle composition
156
+
157
+ Use `createPaddlePaymentAdapter` from `@jskit-ai/payments-core/server/paddle`
158
+ with the same store, services and mappings, plus the binding's explicit
159
+ `taxCategory`. The selected Paddle integration's environment must match the
160
+ payment environment. Resolve `publicClientTokenRef` separately and supply only
161
+ that public client token to the app's Paddle.js checkout page. Never supply its
162
+ API key or signing secret to the browser. The return URL must be the approved
163
+ application checkout page described in the package README. Merchant approval
164
+ and checkout-domain approval remain provider setup steps.
165
+
166
+ ## Changes and portability
167
+
168
+ Editing the JSON file manually changes the same configuration the editor would
169
+ change. Restart/reload the application composition deliberately after validation;
170
+ the library does not watch files. Price changes need a reviewed catalogue publish.
171
+ Keep old plans available until existing subscribers have an explicit migration
172
+ policy; removing a plan does not cancel subscriptions or invent replacement access.
173
+
174
+ Move the source, environment bindings and application database together when
175
+ changing hosts. Back up customer mappings, publication mappings and credit
176
+ entries with the rest of the database. Domain changes require the app's return
177
+ URL and provider webhook/checkout settings to be updated; they do not change
178
+ its application identity or move ownership to an editor.
179
+
180
+ This guide describes explicit composition, not a completed automatic installer.
181
+ HTTP framework wiring and native PostgreSQL/MySQL deployment verification remain
182
+ application responsibilities; focused package tests use controlled fixtures.
183
+
184
+ ## Customer-facing billing page
185
+
186
+ The optional `@jskit-ai/payments-web` package owns the reusable Vue/Vuetify
187
+ `PaymentAccount` view. Its README specifies the props, emitted actions and
188
+ existing JSKIT HTTP-hook composition. Node-only consumers do not install Vue.
189
+ `checkout.account({actor, subjectId})` authorizes action `account`, then returns
190
+ balance, features, subscriptions and a `hasCustomer` flag without provider customer
191
+ IDs or credentials. The application adds safe display plans and permission hints.
192
+ Checkout and portal calls still authorize their own actions independently.
193
+
194
+ ## Provider billing history from an app or CLI
195
+
196
+ The same checkout service exposes `history({actor, subjectId, collection, after})`.
197
+ Authorize action `history` using the application's billing-read policy. An editor
198
+ owner is not automatically authorized to read a tenant's records. The service
199
+ resolves the provider customer from the scoped application database; callers
200
+ cannot select a provider customer, merchant or environment.
201
+
202
+ ```js
203
+ // actor and subjectId come from the app's authenticated route or CLI policy.
204
+ const page = await checkout.history({
205
+ actor, subjectId, collection: 'transactions', after: null
206
+ });
207
+ // Display this page; load another only on an explicit request, using nextCursor.
208
+ ```
209
+
210
+ Collections are `subscriptions` and `transactions`. Each call fetches at most 20
211
+ records and returns `{collection, items, nextCursor}`. A missing customer returns
212
+ an empty page without creating one. Items expose only provider ID, kind, status,
213
+ and ISO creation time. Financial records also expose currency, `totalMinor` and
214
+ `paidMinor` as integer strings or `null` when unknown; preserve these strings
215
+ when using native decimal/money facilities. Do not convert large amounts to a
216
+ JavaScript Number.
217
+
218
+ Stripe financial history contains **invoices**, not every charge, refund or
219
+ payment attempt. Paddle contains **transactions**; its total is not a paid
220
+ amount, so `paidMinor` stays null. Draft Paddle totals may also be null. Preserve
221
+ the `kind` and status in the UI rather than labelling every row a successful
222
+ payment. Subscription history includes canceled subscriptions and is a provider
223
+ view, not the source of application entitlements. A read never grants credits.
224
+
225
+ Laravel can use its native policies and stored customer relation to call
226
+ [Stripe subscription lists](https://docs.stripe.com/api/subscriptions/list) and
227
+ [invoice lists](https://docs.stripe.com/api/invoices/list), or
228
+ [Paddle subscription lists](https://developer.paddle.com/api-reference/subscriptions/list-subscriptions/)
229
+ and [transaction lists](https://developer.paddle.com/api-reference/transactions/list-transactions/).
230
+ Apply the customer filter on every page, retain provider status, and project only
231
+ these display fields. Do not pass arbitrary pagination URLs or SDK objects to
232
+ the client. JSKIT's provider adapters are trusted server primitives; UI/CLI
233
+ entry points use the authorized service above.
234
+
235
+ This is an app/CLI library capability. The editor history command and history
236
+ screens remain separate integration work; this method does not add them by itself.
237
+
238
+ ## Read-only readiness inspection
239
+
240
+ Compose `createPaymentReadiness` from
241
+ `@jskit-ai/payments-core/server/readiness` with `{adapter, catalogue,
242
+ scope: merchantScope}`. Its `inspect()` reports separate credential, account,
243
+ charges, payouts, catalogue, webhook, checkout, site and deployment checks. No
244
+ provider writes occur. Scope mismatches fail, provider exceptions are replaced
245
+ with safe diagnostics, and unknown/manual checks remain explicit.
246
+
247
+ An optional app-owned `inspectApplication` callback may supply `webhook`,
248
+ `checkout`, `site` and `deployment` evidence as `{status, detail}` records. Use
249
+ `passed`, `failed`, `unknown` or `manual` and a safe explanation of at most 500
250
+ characters. Record only observed checks. Never turn an unavailable check into
251
+ success or claim provider approval from a fixture. This callback is a normal app
252
+ function, not a Vibe64 API. Authorize any route/CLI wrapper exposing the service.
253
+
254
+ Catalogue recovery requires a fresh `catalogue.preview()` and its `reviewId`:
255
+ `catalogue.recover({reviewId, providerId})`. The adapter reads the candidate object;
256
+ its ID and pending operation must match, and a recovered price must be active.
257
+ A stale review is rejected. Trusted administrator code may instead supply
258
+ `confirmedNotCreated: true` with that review only after actual provider inspection
259
+ establishes absence. Do not expose that assertion as an ordinary browser flag.
@@ -0,0 +1,26 @@
1
+ exports.up = async function up(knex) {
2
+ await knex.schema.createTable("payment_catalogues", (table) => {
3
+ table.string("catalogue_key", 64).primary();
4
+ table.text("payload", "mediumtext").notNullable();
5
+ });
6
+ await knex.schema.createTable("payment_accounts", (table) => {
7
+ table.string("account_key", 64).primary();
8
+ table.text("payload", "mediumtext").notNullable();
9
+ });
10
+ await knex.schema.createTable("payment_entries", (table) => {
11
+ table.string("entry_key", 64).primary();
12
+ table.string("account_key", 64).notNullable().references("account_key").inTable("payment_accounts");
13
+ table.text("payload", "mediumtext").notNullable();
14
+ });
15
+ await knex.schema.createTable("payment_customers", (table) => {
16
+ table.string("customer_key", 64).primary();
17
+ table.string("subject_id", 200).notNullable();
18
+ });
19
+ };
20
+
21
+ exports.down = async function down(knex) {
22
+ await knex.schema.dropTable("payment_customers");
23
+ await knex.schema.dropTable("payment_entries");
24
+ await knex.schema.dropTable("payment_accounts");
25
+ await knex.schema.dropTable("payment_catalogues");
26
+ };
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@jskit-ai/payments-core",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "description": "Application-owned payment configuration, subscription access and transactional usage credits.",
6
+ "exports": {
7
+ "./shared": "./src/shared/configuration.js",
8
+ "./configuration.schema.json": "./contracts/configuration.schema.json",
9
+ "./conformance.json": "./contracts/conformance.json",
10
+ "./docs/contract.md": "./docs/contract.md",
11
+ "./docs/conformance.md": "./docs/conformance.md",
12
+ "./server": "./src/server/service.js",
13
+ "./server/storage": "./src/server/knexStore.js",
14
+ "./server/checkout": "./src/server/checkout.js",
15
+ "./server/stripe": "./src/server/stripe.js",
16
+ "./server/paddle": "./src/server/paddle.js",
17
+ "./server/catalogue": "./src/server/catalogue.js",
18
+ "./server/readiness": "./src/server/readiness.js"
19
+ },
20
+ "scripts": {
21
+ "test": "node --test --test-concurrency=1"
22
+ },
23
+ "dependencies": {
24
+ "@paddle/paddle-node-sdk": "3.10.0",
25
+ "ajv": "8.20.0",
26
+ "stripe": "22.6.2"
27
+ },
28
+ "jskit": {
29
+ "kind": "runtime",
30
+ "migrations": {
31
+ "directories": [
32
+ "migrations"
33
+ ]
34
+ },
35
+ "metadata": {
36
+ "jskit": {
37
+ "tableOwnership": {
38
+ "tables": [
39
+ {
40
+ "tableName": "payment_accounts"
41
+ },
42
+ {
43
+ "tableName": "payment_entries"
44
+ },
45
+ {
46
+ "tableName": "payment_customers"
47
+ },
48
+ {
49
+ "tableName": "payment_catalogues"
50
+ }
51
+ ]
52
+ }
53
+ }
54
+ },
55
+ "capabilities": {
56
+ "provides": [],
57
+ "requires": []
58
+ },
59
+ "runtime": {
60
+ "server": {
61
+ "providers": []
62
+ },
63
+ "client": {
64
+ "providers": []
65
+ }
66
+ }
67
+ }
68
+ }