@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/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({ id: 'pro', kind: 'subscription', use: 'digital',
16
- plans: { month: { usd: 999 }, year: { usd: 7999, trialDays: 7 } }, grants: ['pro'] })
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 type="button" onClick={() => checkout({ product: 'pro', plan: 'year' })}>Go Pro · {price(pro, 'year').display}</button>
31
- <button type="button" onClick={() => portal()}>Manage</button>
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({ auth: 'user', requires: ['pro'], run: ({ ctx }) => buildReport(ctx) }) // a typed 403: ENTITLEMENT_REQUIRED
52
- if (!(await ctx.entitlements.has('pro'))) redirect('/pricing') // await it (PAY015)
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) // your `definePay`, or fakePay({ catalog })
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() // the buyer finishes the hosted page
118
+ await fake.complete() // the buyer finishes the hosted page
90
119
  expect(screen.getByLabelText('plan').textContent).toBe('Pro')
91
- await fake.simulate('renewal-failed') // renewal, recovered, grace-expired, cancel, pause, resume, refund, revoke
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 | Catches |
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.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
- "type": "module",
7
- "exports": {
8
- ".": "./src/index.ts",
9
- "./server": "./src/server.ts",
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/start": "^0.1.1",
31
- "drizzle-orm": "^0.45.0",
32
- "@weft-js/core": "^0.1.1"
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": ">=26"
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({ id: 'server.extension', version: 1, cardinality: 'keyed', sides: ['server'] })
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
- ['PAY003', 'error', 'A Stripe secret, restricted or webhook key in the source, a server secret read in client code, or a key in plugin options', 'Move it to the server env (`src/env.server.ts`), and roll any real key that was committed'],
12
- ['PAY004', 'error', "Paid state written on the client after `checkout()`, which only opens the rail's page", 'Read `entitlements` instead: a grant arrives from a verified event'],
13
- ['PAY005', 'warning', 'A price typed into a page that sells something', '`price(product, plan).display`'],
14
- ['PAY008', 'error', 'Money as a float, or with no currency, in `product()`', 'Whole minor units with a currency: `{ usd: 999 }`'],
15
- ['PAY010', 'error', "A mutation that opens a checkout without `auth: 'user'`", "Add `auth: 'user'`: a purchase has to belong to someone"],
16
- ['PAY011', 'error', 'A webhook left unverified: a handler of payment events with no signature check, a `PayRail` whose `verify` ignores `headers`, or no `STRIPE_WEBHOOK_SECRET`', 'Delete the handler: `/_weft/x/pay/<rail>` verifies. Give Stripe that URL, and set `STRIPE_WEBHOOK_SECRET`'],
17
- ['PAY012', 'error', "A rail's price or product id in client code", "Name the catalog: `checkout({ product: 'pro', plan: 'month' })`. Map ids on the server: `stripe({ prices: { 'pro.month': … } })`"],
18
- ['PAY013', 'error', 'An entitlement checked only on the client: nothing on the server enforces it', "`requires: ['pro']` on the query or mutation, or `await ctx.entitlements.has('pro')` in a `load`"],
19
- ['PAY014', 'error', 'The plugin with no `src/pay.server.ts`: no webhook is mounted, so payments grant nothing', 'Default-export `definePay({ … })` from `src/pay.server.ts`'],
20
- ['PAY015', 'error', '`ctx.entitlements.has()` tested without `await`: a promise is truthy, so everyone passes', 'Await it (`weft fix` does, in an `async` function)'],
21
- ['PAY016', 'warning', '`memoryPayStore()` in `src/pay.server.ts`: a production server refuses to start with it', '`drizzlePayStore(db, { purchases, payEvents })`, or your own `PayStore`'],
22
- ['PAY017', 'error', '`@weft-js/pay/server` or `/drizzle` imported in client code', 'Import them in a server module; the client gets `@weft-js/pay`'],
23
- ['PAY018', 'error', '`src/pay.server.ts` with no `pay.rail` provider', 'Add a rail to `plugins`: `weft add pay-stripe`'],
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: [provide(extension, { key: 'pay', sides: { server: { module: '@weft-js/pay/server', export: 'extension' } } })],
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: { include: ['src/**/*.{ts,tsx,js,jsx,mts,mjs}', 'test/**/*.{ts,tsx,js,mjs}', 'weft.config.*'], run: ctx => import('./src/checks.mjs').then(c => c.run(ctx)) },
37
- docs: { topic: 'pay', summary: 'Payments: products, checkout, entitlements, webhooks, fakePay, another provider; PAY-codes', file: new URL('./docs/pay.md', import.meta.url).pathname },
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: { title: 'Payments', 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`.' },
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
- { name: 'product', import: '@weft-js/pay', summary: 'One thing you sell, in integer minor units' },
42
- { name: 'price', import: '@weft-js/pay', summary: "A product's price, formatted for the reader" },
43
- { name: 'definePay', import: '@weft-js/pay/server', summary: 'The ledger: src/pay.server.ts default-exports it' },
44
- { name: 'fakePay', import: '@weft-js/pay/test', summary: 'A rail for tests: renewals, refunds and disputes on the clock' },
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
+ }