@weft-js/pay 0.1.1 → 0.1.4
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 +654 -3
- package/docs/pay.md +55 -24
- package/package.json +35 -22
- package/plugin.mjs +131 -23
- package/square-plugin.d.mts +9 -0
- package/square-plugin.mjs +47 -0
- package/src/checks/config.mjs +24 -0
- package/src/checks.mjs +219 -70
- package/src/drizzle.ts +33 -14
- package/src/index.ts +89 -25
- package/src/server/catalog.ts +80 -0
- package/src/server/ledger.ts +84 -0
- package/src/server/operations.ts +215 -0
- package/src/server/rails.ts +45 -0
- package/src/server.ts +113 -172
- package/src/setup.mjs +26 -9
- package/src/square.ts +231 -0
- package/src/stripe.ts +170 -56
- package/src/test.ts +209 -65
- package/stripe-plugin.d.mts +6 -1
- package/stripe-plugin.mjs +24 -5
package/docs/pay.md
CHANGED
|
@@ -12,11 +12,17 @@ App code names products, never a rail:
|
|
|
12
12
|
// src/pay/catalog.ts: reaches the client, so names and amounts only
|
|
13
13
|
import { product } from '@weft-js/pay'
|
|
14
14
|
|
|
15
|
-
export const pro = product({
|
|
16
|
-
|
|
15
|
+
export const pro = product({
|
|
16
|
+
id: 'pro',
|
|
17
|
+
kind: 'subscription',
|
|
18
|
+
use: 'digital',
|
|
19
|
+
plans: { month: { usd: 999 }, year: { usd: 7999, trialDays: 7 } },
|
|
20
|
+
grants: ['pro'],
|
|
21
|
+
})
|
|
17
22
|
```
|
|
18
23
|
|
|
19
24
|
<!-- example: skip needs what `weft add pay` writes -->
|
|
25
|
+
|
|
20
26
|
```tsx
|
|
21
27
|
// a page
|
|
22
28
|
import { price } from '@weft-js/pay'
|
|
@@ -27,8 +33,15 @@ export default function Pricing() {
|
|
|
27
33
|
const mine = entitlements.live()
|
|
28
34
|
return (
|
|
29
35
|
<section>
|
|
30
|
-
<button
|
|
31
|
-
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onClick={() => checkout({ product: 'pro', plan: 'year' })}
|
|
39
|
+
>
|
|
40
|
+
Go Pro · {price(pro, 'year').display}
|
|
41
|
+
</button>
|
|
42
|
+
<button type="button" onClick={() => portal()}>
|
|
43
|
+
Manage
|
|
44
|
+
</button>
|
|
32
45
|
{mine.value?.includes('pro') && <p>You have Pro.</p>}
|
|
33
46
|
</section>
|
|
34
47
|
)
|
|
@@ -44,12 +57,23 @@ verified on the exact bytes, applied to `purchases`, and pokes `entitlements:<us
|
|
|
44
57
|
Delivery is at least once and in any order: a repeat is a no-op, and an old event changes nothing, by the rail's own clock.
|
|
45
58
|
A bad signature answers 400; a failure answers 500, so the rail delivers it again.
|
|
46
59
|
|
|
60
|
+
**A cart the server prices.** `pay.order(ctx, { ref, lines: [{ name, amount, quantity? }], currency?, email?, title? })`
|
|
61
|
+
opens one payment for lines your server priced from its own tables, for a guest or a signed-in buyer. It grants nothing.
|
|
62
|
+
`definePay({ onOrder({ ref, state, purchase }) })` hears each verified state, before the event is marked, so a throw
|
|
63
|
+
redelivers. Each order event pokes `orders` and `orderTag(ref)`. `urls.order` and `urls.orderCancel` may hold `{ref}`.
|
|
64
|
+
|
|
47
65
|
**Enforce on the server.** What the client shows is decoration (PAY013):
|
|
48
66
|
|
|
49
67
|
<!-- example: skip fragments of a server module -->
|
|
68
|
+
|
|
50
69
|
```ts
|
|
51
|
-
export const report = query({
|
|
52
|
-
|
|
70
|
+
export const report = query({
|
|
71
|
+
auth: 'user',
|
|
72
|
+
requires: ['pro'],
|
|
73
|
+
run: ({ ctx }) => buildReport(ctx),
|
|
74
|
+
}) // a typed 403: ENTITLEMENT_REQUIRED
|
|
75
|
+
// Await it (PAY015).
|
|
76
|
+
if (!(await ctx.entitlements.has('pro'))) redirect('/pricing')
|
|
53
77
|
```
|
|
54
78
|
|
|
55
79
|
`ctx.entitlements` has `has(id)` and `list()`, and works in any `query`, `mutation` or `load`. A query with `cache: { life }`
|
|
@@ -70,6 +94,11 @@ changes. A rail implements `PayRail` from `@weft-js/pay`: `checkout`, `portal`,
|
|
|
70
94
|
`PayError('PAY011', …)` for anything it can't prove. An app with no `src/pay.server.ts` has no route and no code from any
|
|
71
95
|
of this.
|
|
72
96
|
|
|
97
|
+
`square({ environment?, api?, apiVersion? })` from `@weft-js/pay/square/plugin` is the Square rail: one-time payments
|
|
98
|
+
through payment links, so web orders land beside the shop's card readers. Env: `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`,
|
|
99
|
+
`SQUARE_WEBHOOK_SIGNATURE_KEY`, `SQUARE_WEBHOOK_URL` (the exact URL given to Square, which it signs), `SQUARE_ENV=sandbox`.
|
|
100
|
+
Subscribe the webhook to `payment.created` and `payment.updated`. Subscriptions and `portal` are PAY022 on it.
|
|
101
|
+
|
|
73
102
|
`stripe({ prices?, api?, apiVersion?, tolerance?, shippingCountries? })` from `@weft-js/pay/stripe/plugin`. It reads
|
|
74
103
|
`STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` from the server env. Don't put a key in its options: they land in a
|
|
75
104
|
generated file (PAY003).
|
|
@@ -82,13 +111,15 @@ generated file (PAY003).
|
|
|
82
111
|
ledger, so a test sees what a user would:
|
|
83
112
|
|
|
84
113
|
```tsx
|
|
85
|
-
const fake = fakePay(pay)
|
|
114
|
+
const fake = fakePay(pay) // your `definePay`, or fakePay({ catalog })
|
|
86
115
|
net.context({ user: { id: 'u1' } })
|
|
87
116
|
render(Paywall)
|
|
88
117
|
await user.click(screen.getByRole('button', { name: /Go Pro/ }))
|
|
89
|
-
await fake.complete()
|
|
118
|
+
await fake.complete() // the buyer finishes the hosted page
|
|
90
119
|
expect(screen.getByLabelText('plan').textContent).toBe('Pro')
|
|
91
|
-
|
|
120
|
+
// renewal, recovered, grace-expired, cancel, pause, resume,
|
|
121
|
+
// refund, revoke
|
|
122
|
+
await fake.simulate('renewal-failed')
|
|
92
123
|
```
|
|
93
124
|
|
|
94
125
|
`fake.buy(user, product, plan)` skips the page. `renewal` and `renewal-failed` move `clock` to the period's end when it's
|
|
@@ -97,22 +128,22 @@ installed. `fake.redeliver()`, `fake.deliver(event, { forged: true })`, `fake.de
|
|
|
97
128
|
|
|
98
129
|
## Diagnostics
|
|
99
130
|
|
|
100
|
-
| Code
|
|
101
|
-
|
|
102
|
-
| PAY003 | A key in the source, or a server secret in client code
|
|
103
|
-
| PAY004 | Paid state written on the client after `checkout()`
|
|
104
|
-
| PAY005 | A price typed into a page that sells something (warning)
|
|
105
|
-
| PAY008 | A float or a currency-less amount in `product()`
|
|
106
|
-
| PAY010 | A checkout mutation without `auth: 'user'`
|
|
107
|
-
| PAY011 | A webhook left unverified, or no `STRIPE_WEBHOOK_SECRET`
|
|
108
|
-
| PAY012 | A rail's price or product id in client code
|
|
109
|
-
| PAY013 | An entitlement checked only on the client
|
|
110
|
-
| PAY014 | A rail bound with no `src/pay.server.ts`
|
|
111
|
-
| PAY015 | `ctx.entitlements.has()` tested without `await`
|
|
112
|
-
| PAY016 | `memoryPayStore()` in `src/pay.server.ts` (warning)
|
|
131
|
+
| Code | Catches |
|
|
132
|
+
| ------ | ----------------------------------------------------------- |
|
|
133
|
+
| PAY003 | A key in the source, or a server secret in client code |
|
|
134
|
+
| PAY004 | Paid state written on the client after `checkout()` |
|
|
135
|
+
| PAY005 | A price typed into a page that sells something (warning) |
|
|
136
|
+
| PAY008 | A float or a currency-less amount in `product()` |
|
|
137
|
+
| PAY010 | A checkout mutation without `auth: 'user'` |
|
|
138
|
+
| PAY011 | A webhook left unverified, or no `STRIPE_WEBHOOK_SECRET` |
|
|
139
|
+
| PAY012 | A rail's price or product id in client code |
|
|
140
|
+
| PAY013 | An entitlement checked only on the client |
|
|
141
|
+
| PAY014 | A rail bound with no `src/pay.server.ts` |
|
|
142
|
+
| PAY015 | `ctx.entitlements.has()` tested without `await` |
|
|
143
|
+
| PAY016 | `memoryPayStore()` in `src/pay.server.ts` (warning) |
|
|
113
144
|
| PAY017 | `@weft-js/pay/server` or `/drizzle` imported in client code |
|
|
114
|
-
| PAY018 | `src/pay.server.ts` with no `pay.rail` provider
|
|
115
|
-
| PAY031 | A live key in a test
|
|
145
|
+
| PAY018 | `src/pay.server.ts` with no `pay.rail` provider |
|
|
146
|
+
| PAY031 | A live key in a test |
|
|
116
147
|
|
|
117
148
|
At run time: PAY021 (not in the catalog), PAY022 (no rail chosen), PAY023 (already subscribed, 409), PAY024 (nothing to
|
|
118
149
|
manage or refund), PAY030 (the rail refused). Native rails, store policy and `weft pay sync` aren't built.
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weft-js/pay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "@weft-js/pay: products, one ledger for every rail, and entitlements. weft's reference plugin: read it to build your own",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"weft"
|
|
7
|
+
],
|
|
8
|
+
"homepage": "https://github.com/Ravonus/weft/tree/main/packages/pay#readme",
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/Ravonus/weft/issues"
|
|
11
|
+
},
|
|
5
12
|
"license": "MIT",
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"./drizzle": "./src/drizzle.ts",
|
|
11
|
-
"./test": "./src/test.ts",
|
|
12
|
-
"./plugin": "./plugin.mjs",
|
|
13
|
-
"./stripe": "./src/stripe.ts",
|
|
14
|
-
"./stripe/plugin": "./stripe-plugin.mjs"
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/Ravonus/weft.git",
|
|
16
|
+
"directory": "packages/pay"
|
|
15
17
|
},
|
|
16
18
|
"files": [
|
|
17
19
|
"src",
|
|
@@ -21,15 +23,32 @@
|
|
|
21
23
|
"stripe-plugin.mjs",
|
|
22
24
|
"stripe-plugin.d.mts",
|
|
23
25
|
"README.md",
|
|
24
|
-
"LICENSE"
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"square-plugin.mjs",
|
|
28
|
+
"square-plugin.d.mts"
|
|
25
29
|
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./src/index.ts",
|
|
33
|
+
"./server": "./src/server.ts",
|
|
34
|
+
"./drizzle": "./src/drizzle.ts",
|
|
35
|
+
"./test": "./src/test.ts",
|
|
36
|
+
"./plugin": "./plugin.mjs",
|
|
37
|
+
"./stripe": "./src/stripe.ts",
|
|
38
|
+
"./stripe/plugin": "./stripe-plugin.mjs",
|
|
39
|
+
"./square": "./src/square.ts",
|
|
40
|
+
"./square/plugin": "./square-plugin.mjs"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
26
45
|
"dependencies": {
|
|
27
46
|
"typescript": "6.0.3"
|
|
28
47
|
},
|
|
29
48
|
"peerDependencies": {
|
|
30
|
-
"@weft-js/
|
|
31
|
-
"
|
|
32
|
-
"
|
|
49
|
+
"@weft-js/core": "^0.1.4",
|
|
50
|
+
"@weft-js/start": "^0.1.4",
|
|
51
|
+
"drizzle-orm": "^0.45.0"
|
|
33
52
|
},
|
|
34
53
|
"peerDependenciesMeta": {
|
|
35
54
|
"drizzle-orm": {
|
|
@@ -37,12 +56,6 @@
|
|
|
37
56
|
}
|
|
38
57
|
},
|
|
39
58
|
"engines": {
|
|
40
|
-
"node": ">=
|
|
41
|
-
},
|
|
42
|
-
"keywords": [
|
|
43
|
-
"weft"
|
|
44
|
-
],
|
|
45
|
-
"publishConfig": {
|
|
46
|
-
"access": "public"
|
|
59
|
+
"node": ">=24"
|
|
47
60
|
}
|
|
48
|
-
}
|
|
61
|
+
}
|
package/plugin.mjs
CHANGED
|
@@ -5,22 +5,92 @@
|
|
|
5
5
|
import { definePlugin, defineSlot, provide } from '@weft-js/core/plugin'
|
|
6
6
|
import { setup } from './src/setup.mjs'
|
|
7
7
|
|
|
8
|
-
const extension = defineSlot({
|
|
8
|
+
const extension = defineSlot({
|
|
9
|
+
id: 'server.extension',
|
|
10
|
+
version: 1,
|
|
11
|
+
cardinality: 'keyed',
|
|
12
|
+
sides: ['server'],
|
|
13
|
+
})
|
|
9
14
|
|
|
10
15
|
const diagnostics = [
|
|
11
|
-
[
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
[
|
|
16
|
+
[
|
|
17
|
+
'PAY003',
|
|
18
|
+
'error',
|
|
19
|
+
'A Stripe secret, restricted or webhook key in the source, a server secret read in client code, or a key in plugin options',
|
|
20
|
+
'Move it to the server env (`src/env.server.ts`), and roll any real key that was committed',
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
'PAY004',
|
|
24
|
+
'error',
|
|
25
|
+
"Paid state written on the client after `checkout()`, which only opens the rail's page",
|
|
26
|
+
'Read `entitlements` instead: a grant arrives from a verified event',
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
'PAY005',
|
|
30
|
+
'warning',
|
|
31
|
+
'A price typed into a page that sells something',
|
|
32
|
+
'`price(product, plan).display`',
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
'PAY008',
|
|
36
|
+
'error',
|
|
37
|
+
'Money as a float, or with no currency, in `product()`',
|
|
38
|
+
'Whole minor units with a currency: `{ usd: 999 }`',
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
'PAY010',
|
|
42
|
+
'error',
|
|
43
|
+
"A mutation that opens a checkout without `auth: 'user'`",
|
|
44
|
+
"Add `auth: 'user'`: a purchase has to belong to someone",
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
'PAY011',
|
|
48
|
+
'error',
|
|
49
|
+
'A webhook left unverified: a handler of payment events with no signature check, a `PayRail` whose `verify` ignores `headers`, or no `STRIPE_WEBHOOK_SECRET`',
|
|
50
|
+
'Delete the handler: `/_weft/x/pay/<rail>` verifies. Give Stripe that URL, and set `STRIPE_WEBHOOK_SECRET`',
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
'PAY012',
|
|
54
|
+
'error',
|
|
55
|
+
"A rail's price or product id in client code",
|
|
56
|
+
"Name the catalog: `checkout({ product: 'pro', plan: 'month' })`. Map ids on the server: `stripe({ prices: { 'pro.month': … } })`",
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
'PAY013',
|
|
60
|
+
'error',
|
|
61
|
+
'An entitlement checked only on the client: nothing on the server enforces it',
|
|
62
|
+
"`requires: ['pro']` on the query or mutation, or `await ctx.entitlements.has('pro')` in a `load`",
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
'PAY014',
|
|
66
|
+
'error',
|
|
67
|
+
'The plugin with no `src/pay.server.ts`: no webhook is mounted, so payments grant nothing',
|
|
68
|
+
'Default-export `definePay({ … })` from `src/pay.server.ts`',
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
'PAY015',
|
|
72
|
+
'error',
|
|
73
|
+
'`ctx.entitlements.has()` tested without `await`: a promise is truthy, so everyone passes',
|
|
74
|
+
'Await it (`weft fix` does, in an `async` function)',
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
'PAY016',
|
|
78
|
+
'warning',
|
|
79
|
+
'`memoryPayStore()` in `src/pay.server.ts`: a production server refuses to start with it',
|
|
80
|
+
'`drizzlePayStore(db, { purchases, payEvents })`, or your own `PayStore`',
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
'PAY017',
|
|
84
|
+
'error',
|
|
85
|
+
'`@weft-js/pay/server` or `/drizzle` imported in client code',
|
|
86
|
+
'Import them in a server module; the client gets `@weft-js/pay`',
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
'PAY018',
|
|
90
|
+
'error',
|
|
91
|
+
'`src/pay.server.ts` with no `pay.rail` provider',
|
|
92
|
+
'Add a rail to `plugins`: `weft add pay-stripe`',
|
|
93
|
+
],
|
|
24
94
|
['PAY031', 'error', 'A live key in a test', '`fakePay` from `@weft-js/pay/test`'],
|
|
25
95
|
].map(([code, severity, catches, fix]) => ({ code, severity, catches, fix }))
|
|
26
96
|
|
|
@@ -31,19 +101,57 @@ export const pay = definePlugin({
|
|
|
31
101
|
namespace: 'PAY',
|
|
32
102
|
permissions: {},
|
|
33
103
|
contribute: () => ({
|
|
34
|
-
provides: [
|
|
104
|
+
provides: [
|
|
105
|
+
provide(extension, {
|
|
106
|
+
key: 'pay',
|
|
107
|
+
sides: { server: { module: '@weft-js/pay/server', export: 'extension' } },
|
|
108
|
+
}),
|
|
109
|
+
],
|
|
35
110
|
diagnostics,
|
|
36
|
-
check: {
|
|
37
|
-
|
|
111
|
+
check: {
|
|
112
|
+
include: ['src/**/*.{ts,tsx,js,jsx,mts,mjs}', 'test/**/*.{ts,tsx,js,mjs}', 'weft.config.*'],
|
|
113
|
+
run: (ctx) => import('./src/checks.mjs').then((c) => c.run(ctx)),
|
|
114
|
+
},
|
|
115
|
+
docs: {
|
|
116
|
+
topic: 'pay',
|
|
117
|
+
summary:
|
|
118
|
+
'Payments: products, checkout, entitlements, webhooks, fakePay, another provider; PAY-codes',
|
|
119
|
+
file: new URL('./docs/pay.md', import.meta.url).pathname,
|
|
120
|
+
},
|
|
38
121
|
setup,
|
|
39
|
-
agents: {
|
|
122
|
+
agents: {
|
|
123
|
+
title: 'Payments',
|
|
124
|
+
text: 'Products are in `src/pay/catalog.ts`, in minor units. `checkout` and `entitlements` are in `src/server/pay.ts`. App code never names a rail or a price id, and `checkout` grants nothing: a verified webhook does. Enforce on the server, with `requires: [...]` or `await ctx.entitlements.has(...)`: a check only on the client protects nothing. Test with `fakePay` from `@weft-js/pay/test`. `weft docs pay`.',
|
|
125
|
+
},
|
|
40
126
|
catalog: [
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
127
|
+
{
|
|
128
|
+
name: 'product',
|
|
129
|
+
import: '@weft-js/pay',
|
|
130
|
+
summary: 'One thing you sell, in integer minor units',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'price',
|
|
134
|
+
import: '@weft-js/pay',
|
|
135
|
+
summary: "A product's price, formatted for the reader",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'definePay',
|
|
139
|
+
import: '@weft-js/pay/server',
|
|
140
|
+
summary: 'The ledger: src/pay.server.ts default-exports it',
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'fakePay',
|
|
144
|
+
import: '@weft-js/pay/test',
|
|
145
|
+
summary: 'A rail for tests: renewals, refunds and disputes on the clock',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
map: [
|
|
149
|
+
{
|
|
150
|
+
want: 'Sell something',
|
|
151
|
+
call: 'add it to `src/pay/catalog.ts`, then `checkout({ product, plan })`',
|
|
152
|
+
have: '`weft docs pay`',
|
|
153
|
+
},
|
|
45
154
|
],
|
|
46
|
-
map: [{ want: 'Sell something', call: 'add it to `src/pay/catalog.ts`, then `checkout({ product, plan })`', have: '`weft docs pay`' }],
|
|
47
155
|
}),
|
|
48
156
|
})
|
|
49
157
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PluginInstance } from '@weft-js/core/plugin'
|
|
2
|
+
|
|
3
|
+
declare const plugin: (options?: {
|
|
4
|
+
environment?: 'sandbox' | 'production'
|
|
5
|
+
api?: string
|
|
6
|
+
apiVersion?: string
|
|
7
|
+
}) => PluginInstance<unknown>
|
|
8
|
+
/** `square()` for `weft.config.ts`: Square as a `pay.rail` provider. Tokens stay in the environment. */
|
|
9
|
+
export default plugin
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// square() for weft.config.ts: Square as the `pay.rail` provider, so web orders land in the same account as the shop's
|
|
2
|
+
// card readers. Options are in the generated slot module, so they name env vars and never hold a token (PAY003).
|
|
3
|
+
import { definePlugin, defineSlot, provide } from '@weft-js/core/plugin'
|
|
4
|
+
|
|
5
|
+
const slot = defineSlot({
|
|
6
|
+
id: 'pay.rail',
|
|
7
|
+
version: 1,
|
|
8
|
+
cardinality: 'keyed',
|
|
9
|
+
sides: ['server', 'client', 'native'],
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const square = definePlugin({
|
|
13
|
+
name: '@weft-js/pay/square',
|
|
14
|
+
version: '0.0.0',
|
|
15
|
+
weft: '*',
|
|
16
|
+
permissions: {
|
|
17
|
+
net: ['connect.squareup.com', 'connect.squareupsandbox.com'],
|
|
18
|
+
env: [
|
|
19
|
+
'SQUARE_ACCESS_TOKEN',
|
|
20
|
+
'SQUARE_LOCATION_ID',
|
|
21
|
+
'SQUARE_WEBHOOK_SIGNATURE_KEY',
|
|
22
|
+
'SQUARE_WEBHOOK_URL',
|
|
23
|
+
'SQUARE_ENV',
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
contribute: (options) => ({
|
|
27
|
+
provides: [
|
|
28
|
+
provide(slot, {
|
|
29
|
+
key: 'square',
|
|
30
|
+
sides: { server: { module: '@weft-js/pay/square', export: 'squareRail', options } },
|
|
31
|
+
}),
|
|
32
|
+
],
|
|
33
|
+
agents: {
|
|
34
|
+
title: 'Pay rail: Square',
|
|
35
|
+
text: 'Square payment links, one-time payments only (`pay.order`, one-time products): `SQUARE_ACCESS_TOKEN`, `SQUARE_LOCATION_ID`, and for `/_weft/x/pay/square` (subscribe it to `payment.created` and `payment.updated`) `SQUARE_WEBHOOK_SIGNATURE_KEY` plus `SQUARE_WEBHOOK_URL`, the exact URL given to Square. `SQUARE_ENV=sandbox` for test.',
|
|
36
|
+
},
|
|
37
|
+
map: [
|
|
38
|
+
{
|
|
39
|
+
want: 'Change the payment provider',
|
|
40
|
+
call: 'replace this plugin in `plugins`: app code stays',
|
|
41
|
+
have: 'Square payment links',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
}),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export default square
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Check config and test files for exposed credentials. */
|
|
2
|
+
export function configChecks({ ctx, out, secrets, spanOf, isTest, SECRET_OPTION }) {
|
|
3
|
+
for (const rel of ctx.files.filter((r) => r.startsWith('weft.config.'))) {
|
|
4
|
+
const text = ctx.read(rel)
|
|
5
|
+
secrets(rel, text, true, out)
|
|
6
|
+
const m = SECRET_OPTION.exec(text)
|
|
7
|
+
if (m)
|
|
8
|
+
out.push({
|
|
9
|
+
code: 'PAY003',
|
|
10
|
+
...spanOf(rel, m.index, m[0].length),
|
|
11
|
+
why: 'Plugin options are written into `.weft/slots/`, so a key given here sits in a generated file.',
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for (const rel of ctx.files.filter(isTest)) {
|
|
16
|
+
const text = ctx.read(rel)
|
|
17
|
+
for (const m of text.matchAll(/\b(?:sk|rk)_live_\w+/g))
|
|
18
|
+
out.push({
|
|
19
|
+
code: 'PAY031',
|
|
20
|
+
...spanOf(rel, m.index, m[0].length),
|
|
21
|
+
why: 'A live key in a test: it charges real cards, and it is now in git.',
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
}
|