@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 +147 -0
- package/contracts/configuration.schema.json +148 -0
- package/contracts/conformance.json +718 -0
- package/docs/conformance.md +91 -0
- package/docs/contract.md +126 -0
- package/docs/standalone.md +259 -0
- package/migrations/payments_core_initial.cjs +26 -0
- package/package.json +68 -0
- package/src/server/catalogue.js +117 -0
- package/src/server/checkout.js +131 -0
- package/src/server/knexStore.js +82 -0
- package/src/server/paddle.js +117 -0
- package/src/server/readiness.js +63 -0
- package/src/server/service.js +160 -0
- package/src/server/stripe.js +138 -0
- package/src/shared/configuration.js +40 -0
- package/test/catalogue.test.js +112 -0
- package/test/checkout.test.js +229 -0
- package/test/conformance.test.js +77 -0
- package/test/history.test.js +105 -0
- package/test/readiness.test.js +55 -0
- package/test/service.test.js +118 -0
- package/test/standalone.test.js +61 -0
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
|
+
}
|