glyphpay 0.4.0 → 1.0.0
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 +190 -184
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +117 -63
- package/package.json +60 -60
package/README.md
CHANGED
|
@@ -1,184 +1,190 @@
|
|
|
1
|
-
# glyphpay
|
|
2
|
-
|
|
3
|
-
Drop-in connector that lets a website take payments through [GlyphPay](https://glyphpay.net) by Design Glyph. Payments go to the site owner's own Stripe connected account.
|
|
4
|
-
|
|
5
|
-
| Import | Runs on | What it gives you |
|
|
6
|
-
| --- | --- | --- |
|
|
7
|
-
| `glyphpay/server` | Site's server | `glyphPay()`: `checkout` and `webhook` route handlers, plus `createPayment`, `getPayment`, `refund`, `reconcile` |
|
|
8
|
-
| `glyphpay/react` | Browser | `<GlyphPayCheckout />` |
|
|
9
|
-
| `glyphpay/browser` | Browser, any site | `mountCheckout(el, options)` for sites without React |
|
|
10
|
-
|
|
11
|
-
The CLI covers setup, diagnostics, and credential lifecycle:
|
|
12
|
-
|
|
13
|
-
| Command | What it does |
|
|
14
|
-
| --- | --- |
|
|
15
|
-
| `glyphpay connect` | Pair a site through the browser and write the server credential. |
|
|
16
|
-
| `glyphpay doctor` | Check env, token, Stripe readiness, webhook URL, and an optional signed test event. |
|
|
17
|
-
| `glyphpay rotate` | Re-pair the site and replace the credential. |
|
|
18
|
-
| `glyphpay disconnect` | Revoke the credential and remove the local keys. |
|
|
19
|
-
| `glyphpay deliveries` |
|
|
20
|
-
| `glyphpay init` | Scaffold routes for Next.js, Remix, Astro, SvelteKit, Hono, or Express. |
|
|
21
|
-
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```sh
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
npx glyphpay
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
##
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```sh
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
```
|
|
1
|
+
# glyphpay
|
|
2
|
+
|
|
3
|
+
Drop-in connector that lets a website take payments through [GlyphPay](https://glyphpay.net) by Design Glyph. Payments go to the site owner's own Stripe connected account.
|
|
4
|
+
|
|
5
|
+
| Import | Runs on | What it gives you |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `glyphpay/server` | Site's server | `glyphPay()`: `checkout` and `webhook` route handlers, plus `createPayment`, `getPayment`, `refund`, `reconcile` |
|
|
8
|
+
| `glyphpay/react` | Browser | `<GlyphPayCheckout />` |
|
|
9
|
+
| `glyphpay/browser` | Browser, any site | `mountCheckout(el, options)` for sites without React |
|
|
10
|
+
|
|
11
|
+
The CLI covers setup, diagnostics, and credential lifecycle:
|
|
12
|
+
|
|
13
|
+
| Command | What it does |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `glyphpay connect` | Pair a site through the browser and write the server credential. |
|
|
16
|
+
| `glyphpay doctor` | Check env, token, Stripe readiness, webhook URL, and an optional signed test event. |
|
|
17
|
+
| `glyphpay rotate` | Re-pair the site and replace the credential. |
|
|
18
|
+
| `glyphpay disconnect` | Revoke the credential and remove the local keys. |
|
|
19
|
+
| `glyphpay deliveries` | Page/filter webhook deliveries and retry one with `--retry <id>`. |
|
|
20
|
+
| `glyphpay init` | Scaffold routes for Next.js, Remix, Astro, SvelteKit, Hono, or Express. |
|
|
21
|
+
|
|
22
|
+
## 1.0 compatibility
|
|
23
|
+
|
|
24
|
+
`glyphpay` 1.x follows Semantic Versioning. The documented package exports and the `/api/client/v1` and `/api/cli/v1` HTTP contracts accept additive changes only; ignore unknown response fields and event types. Breaking changes ship under a new API version with an overlap period. The full rules are in `docs/versioning-policy.md`.
|
|
25
|
+
|
|
26
|
+
The CLI supports **Node 18+**. The server connector (`glyphpay/server`) uses `fetch` and Web Crypto and targets **Node 20+**, Next.js, edge runtimes, and Cloudflare Workers. A 1.0 release is a code and contract milestone: live payments remain gated by sandbox acceptance, deployment verification, and written approval.
|
|
27
|
+
|
|
28
|
+
## Connect a site from the CLI
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
npx glyphpay connect --url https://client-site.com
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The command opens Glyph Pay in your browser. Sign in as the client's billing email (or a Design Glyph admin), pick the client account, confirm the webhook URL and approve the pairing code. The terminal receives a test service credential and, when a webhook URL was registered, its signing secret. It writes both to `.env.local`:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
GLYPHPAY_TOKEN=dgsvc_test_...
|
|
38
|
+
GLYPHPAY_CALLBACK_SECRET=dgcb_...
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `--url <site>` registers `<site>/api/glyphpay/webhook`. Pass a full URL to use a different route.
|
|
42
|
+
- `--env <path>` targets another env file; `--print` prints the values instead of writing.
|
|
43
|
+
- `--no-open`, `--base-url` and `--timeout` help scripts and local testing.
|
|
44
|
+
- Codes expire after ten minutes. Approving again rotates the token: the previous token stops working immediately.
|
|
45
|
+
- Pairing is disabled while the platform is in live mode.
|
|
46
|
+
|
|
47
|
+
The browser never receives the credential. Keep the token in server environment variables and never prefix it with `NEXT_PUBLIC_`, `VITE_` or `PUBLIC_`.
|
|
48
|
+
|
|
49
|
+
## Check, rotate, and disconnect
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npx glyphpay doctor
|
|
53
|
+
npx glyphpay doctor --json
|
|
54
|
+
npx glyphpay rotate
|
|
55
|
+
npx glyphpay disconnect
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`doctor` verifies the local env file, checks for credentials leaked into public-prefixed variables, validates the token with Glyph Pay, checks Stripe charge readiness and the registered webhook, and optionally sends a signed test event:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
npx glyphpay doctor --send-test-event
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The test event has type `webhook.test` and a zero-dollar `data` payload. Ignore event types you do not handle in `onEvent`; do not treat the test event as a real payment.
|
|
65
|
+
|
|
66
|
+
`rotate` re-runs browser approval and reuses the registered callback URL when the current token is still valid. `disconnect` revokes the credential immediately and removes `GLYPHPAY_TOKEN` and `GLYPHPAY_CALLBACK_SECRET` from the env file; pass `--yes` in scripts or `--keep-env` to leave the local values in place.
|
|
67
|
+
|
|
68
|
+
## Inspect and retry webhook deliveries
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
npx glyphpay deliveries
|
|
72
|
+
npx glyphpay deliveries --retry evt_...
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`deliveries` pages through callback outbox records for the credential's client and environment: event type, delivery state, attempt count, timestamp, and the last error. Filter with `--state pending|delivered|failed`, control the page with `--limit 1..100`, and pass the returned `--cursor` for the next page. A retry sends the exact stored signed event again and persists the result. Retry is one attempt; fix the endpoint first. The admin **Connections** tab exposes the same filtering, pagination, and retry operations.
|
|
76
|
+
|
|
77
|
+
## Scaffold a site
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
npx glyphpay init
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The command detects the project framework from `package.json`. It creates a server connector plus the framework-native checkout and webhook entry points, and appends placeholders to `.env.example` without changing existing values. Supported targets:
|
|
84
|
+
|
|
85
|
+
- **Next.js App Router** — `app/api/glyphpay/*` or `src/app/api/glyphpay/*` route handlers.
|
|
86
|
+
- **SvelteKit** — `src/routes/api/glyphpay/*/+server.ts` with `src/lib/server/glyphpay.ts`.
|
|
87
|
+
- **Astro** — `src/pages/api/glyphpay/*.ts`; use server/SSR output and an adapter.
|
|
88
|
+
- **Remix** — `app/routes/api.glyphpay.*.ts` with `app/lib/glyphpay.server.ts`.
|
|
89
|
+
- **Hono** — `src/glyphpay-routes.ts`; mount it with `app.route("/api/glyphpay", glyphpayRoutes)`.
|
|
90
|
+
- **Express** — `src/glyphpay-routes.ts`; mount it with `app.use("/api/glyphpay", glyphpayRoutes)`. The webhook route uses `express.raw` so signatures verify against the original bytes.
|
|
91
|
+
|
|
92
|
+
It refuses to overwrite existing files unless you pass `--force`; use `--dry-run` to preview. Then run `glyphpay connect` and wire `priceOrder()` and `onEvent()` to your order database.
|
|
93
|
+
|
|
94
|
+
## Manual setup: connect the client in Design Glyph admin
|
|
95
|
+
|
|
96
|
+
1. **Admin → Clients → (client)**: make sure Stripe onboarding is complete (charges enabled).
|
|
97
|
+
2. **Issue a service credential** (test first). Copy the `dgsvc_test_…` token. It is shown only once.
|
|
98
|
+
3. **Set the callback URL** to `https://<client-site>/api/glyphpay/webhook`. Copy the signing secret. It is also shown only once.
|
|
99
|
+
|
|
100
|
+
## Add to the client site
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
pnpm add glyphpay
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`.env` (server only, never `NEXT_PUBLIC_`):
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
GLYPHPAY_TOKEN=dgsvc_test_...
|
|
110
|
+
GLYPHPAY_CALLBACK_SECRET=...
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`lib/glyphpay.ts`
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { glyphPay, CheckoutError } from "glyphpay/server";
|
|
117
|
+
|
|
118
|
+
export const glyphpay = glyphPay({
|
|
119
|
+
token: process.env.GLYPHPAY_TOKEN,
|
|
120
|
+
callbackSecret: process.env.GLYPHPAY_CALLBACK_SECRET,
|
|
121
|
+
|
|
122
|
+
// Price the order from YOUR data. Never trust an amount sent by the browser.
|
|
123
|
+
async priceOrder(body) {
|
|
124
|
+
const cart = await db.carts.get((body as { cartId: string }).cartId);
|
|
125
|
+
if (!cart?.items.length) throw new CheckoutError("Your cart is empty.");
|
|
126
|
+
// orderId must change whenever the total changes, or GlyphPay returns 409.
|
|
127
|
+
return { orderId: `${cart.id}-v${cart.version}`, amountCents: cart.totalCents, description: `Order ${cart.id}` };
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// Signed and verified. At-least-once delivery: dedupe on event.id. Throw to have it retried.
|
|
131
|
+
async onEvent(event) {
|
|
132
|
+
if (event.type === "payment.succeeded") await db.orders.markPaid(event.data.orderId, event.id);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`app/api/glyphpay/checkout/route.ts`
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { glyphpay } from "@/lib/glyphpay";
|
|
141
|
+
export const POST = glyphpay.checkout;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`app/api/glyphpay/webhook/route.ts`
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { glyphpay } from "@/lib/glyphpay";
|
|
148
|
+
export const POST = glyphpay.webhook;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Checkout page:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { GlyphPayCheckout } from "glyphpay/react";
|
|
155
|
+
|
|
156
|
+
<GlyphPayCheckout order={{ cartId }} returnUrl={`/thank-you?cart=${cartId}`} appearance={{ theme: "stripe" }} />
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Style the button with `.glyphpay button { … }`. Errors appear in `.glyphpay [role=alert]`.
|
|
160
|
+
|
|
161
|
+
Without React:
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<div id="pay"></div>
|
|
165
|
+
<script type="module">
|
|
166
|
+
import { mountCheckout } from "glyphpay/browser";
|
|
167
|
+
mountCheckout(document.getElementById("pay"), { order: { cartId: "42" }, returnUrl: "/thank-you" });
|
|
168
|
+
</script>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The route handlers take a standard `Request` and return a `Response`, so the same two lines work in Remix, Astro, Hono, SvelteKit, and Cloudflare Workers. In Express, `glyphpay init` generates a small adapter that preserves the raw webhook body.
|
|
172
|
+
|
|
173
|
+
## How a payment flows
|
|
174
|
+
|
|
175
|
+
1. The component POSTs `order` to `/api/glyphpay/checkout`. `priceOrder` sets the amount, and the site's server calls GlyphPay with the token.
|
|
176
|
+
2. The browser gets back only `clientSecret`, `publishableKey`, and `stripeAccount`, then mounts Stripe's Payment Element.
|
|
177
|
+
3. The shopper pays, and Stripe redirects to `returnUrl`. **The redirect doesn't prove payment.**
|
|
178
|
+
4. GlyphPay POSTs `payment.succeeded` to `/api/glyphpay/webhook`, and `onEvent` marks the order paid. The thank-you page reads the order status from your database, or calls `glyphpay.getPayment(paymentId)`.
|
|
179
|
+
|
|
180
|
+
Refunds run server-side:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
await glyphpay.refund(paymentId, { idempotencyKey: `${orderId}-refund-1` });
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Develop
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
cd packages/glyphpay && npm install && npm test
|
|
190
|
+
```
|
package/dist/cli.d.ts
CHANGED
|
@@ -51,6 +51,12 @@ export interface CliOptions {
|
|
|
51
51
|
dir?: string;
|
|
52
52
|
/** Deliveries: replay one failed or delivered callback by id. */
|
|
53
53
|
retry?: string;
|
|
54
|
+
/** Deliveries: filter by delivery state. */
|
|
55
|
+
state?: "pending" | "delivered" | "failed";
|
|
56
|
+
/** Deliveries: opaque next-page cursor returned by the API. */
|
|
57
|
+
cursor?: string;
|
|
58
|
+
/** Deliveries: page size, 1-100. */
|
|
59
|
+
limit?: number;
|
|
54
60
|
}
|
|
55
61
|
export interface ConnectResult {
|
|
56
62
|
businessName: string;
|
|
@@ -79,6 +85,7 @@ export interface CallbackDeliveriesResponse {
|
|
|
79
85
|
businessName: string;
|
|
80
86
|
environment: "test" | "live";
|
|
81
87
|
deliveries: CallbackDeliveryView[];
|
|
88
|
+
nextCursor: string | null;
|
|
82
89
|
}
|
|
83
90
|
export interface CallbackReplayResponse {
|
|
84
91
|
outcome: "delivered" | "retry" | "failed";
|
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { access, chmod, mkdir, readFile, stat, writeFile } from "node:fs/promise
|
|
|
20
20
|
import { dirname, join, relative, resolve } from "node:path";
|
|
21
21
|
import { createInterface } from "node:readline/promises";
|
|
22
22
|
import { pathToFileURL } from "node:url";
|
|
23
|
-
const VERSION = "0.
|
|
23
|
+
const VERSION = "1.0.0";
|
|
24
24
|
const DEFAULT_BASE_URL = "https://designglyph.web.app";
|
|
25
25
|
const DEFAULT_ENV_FILE = ".env.local";
|
|
26
26
|
const DEFAULT_TIMEOUT_SECONDS = 600;
|
|
@@ -180,6 +180,40 @@ export function parseArgs(argv) {
|
|
|
180
180
|
}
|
|
181
181
|
continue;
|
|
182
182
|
}
|
|
183
|
+
if (flag === "--state") {
|
|
184
|
+
const raw = inlineValue ?? takeValue(argv, index, flag)[0];
|
|
185
|
+
if (inlineValue === undefined)
|
|
186
|
+
index += 1;
|
|
187
|
+
if (raw !== "pending" && raw !== "delivered" && raw !== "failed") {
|
|
188
|
+
throw new CliUsageError("--state must be pending, delivered, or failed.");
|
|
189
|
+
}
|
|
190
|
+
options.state = raw;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (flag === "--cursor") {
|
|
194
|
+
if (inlineValue !== undefined) {
|
|
195
|
+
if (!inlineValue)
|
|
196
|
+
throw new CliUsageError("--cursor needs a value.");
|
|
197
|
+
options.cursor = inlineValue;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const [value, next] = takeValue(argv, index, flag);
|
|
201
|
+
options.cursor = value;
|
|
202
|
+
index = next;
|
|
203
|
+
}
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (flag === "--limit") {
|
|
207
|
+
const raw = inlineValue ?? takeValue(argv, index, flag)[0];
|
|
208
|
+
if (inlineValue === undefined)
|
|
209
|
+
index += 1;
|
|
210
|
+
const parsed = Number(raw);
|
|
211
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) {
|
|
212
|
+
throw new CliUsageError("--limit must be an integer between 1 and 100.");
|
|
213
|
+
}
|
|
214
|
+
options.limit = parsed;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
183
217
|
if (flag === "--dir") {
|
|
184
218
|
if (inlineValue !== undefined) {
|
|
185
219
|
if (!inlineValue)
|
|
@@ -211,44 +245,47 @@ export function parseArgs(argv) {
|
|
|
211
245
|
return { command, options };
|
|
212
246
|
}
|
|
213
247
|
export function helpText() {
|
|
214
|
-
return `glyphpay - take payments on your site through Glyph Pay
|
|
215
|
-
|
|
216
|
-
Usage
|
|
217
|
-
glyphpay connect [options] Connect a site (alias: login)
|
|
218
|
-
glyphpay rotate [options] Re-pair and rotate the current credential
|
|
219
|
-
glyphpay disconnect [options] Revoke the credential and remove local keys
|
|
220
|
-
glyphpay doctor [options] Check env, token, Stripe and webhook setup
|
|
221
|
-
glyphpay deliveries [options] List recent webhook deliveries
|
|
222
|
-
glyphpay init [options] Scaffold an integration for your framework
|
|
223
|
-
glyphpay --version
|
|
224
|
-
|
|
225
|
-
Connect / rotate
|
|
226
|
-
--url <site-url> Site to connect. Adds ${WEBHOOK_PATH} when the URL
|
|
227
|
-
has no path. Or pass the exact callback URL.
|
|
228
|
-
--env <path> Env file to update (default: ${DEFAULT_ENV_FILE})
|
|
229
|
-
--print Print the credentials instead of writing a file
|
|
230
|
-
--no-open Do not open a browser automatically
|
|
231
|
-
--base-url <url> Glyph Pay API origin (default: ${DEFAULT_BASE_URL})
|
|
232
|
-
--timeout <seconds> Wait for approval (default: ${DEFAULT_TIMEOUT_SECONDS})
|
|
233
|
-
|
|
234
|
-
Doctor
|
|
235
|
-
--json Print a machine-readable report
|
|
236
|
-
--send-test-event POST a signed webhook.test event to the callback URL
|
|
237
|
-
|
|
238
|
-
Disconnect
|
|
239
|
-
--yes, -y Do not ask for confirmation
|
|
240
|
-
--keep-env Keep the local env values after revocation
|
|
241
|
-
|
|
242
|
-
Deliveries
|
|
243
|
-
--
|
|
244
|
-
--
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
--
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
return `glyphpay - take payments on your site through Glyph Pay
|
|
249
|
+
|
|
250
|
+
Usage
|
|
251
|
+
glyphpay connect [options] Connect a site (alias: login)
|
|
252
|
+
glyphpay rotate [options] Re-pair and rotate the current credential
|
|
253
|
+
glyphpay disconnect [options] Revoke the credential and remove local keys
|
|
254
|
+
glyphpay doctor [options] Check env, token, Stripe and webhook setup
|
|
255
|
+
glyphpay deliveries [options] List recent webhook deliveries
|
|
256
|
+
glyphpay init [options] Scaffold an integration for your framework
|
|
257
|
+
glyphpay --version
|
|
258
|
+
|
|
259
|
+
Connect / rotate
|
|
260
|
+
--url <site-url> Site to connect. Adds ${WEBHOOK_PATH} when the URL
|
|
261
|
+
has no path. Or pass the exact callback URL.
|
|
262
|
+
--env <path> Env file to update (default: ${DEFAULT_ENV_FILE})
|
|
263
|
+
--print Print the credentials instead of writing a file
|
|
264
|
+
--no-open Do not open a browser automatically
|
|
265
|
+
--base-url <url> Glyph Pay API origin (default: ${DEFAULT_BASE_URL})
|
|
266
|
+
--timeout <seconds> Wait for approval (default: ${DEFAULT_TIMEOUT_SECONDS})
|
|
267
|
+
|
|
268
|
+
Doctor
|
|
269
|
+
--json Print a machine-readable report
|
|
270
|
+
--send-test-event POST a signed webhook.test event to the callback URL
|
|
271
|
+
|
|
272
|
+
Disconnect
|
|
273
|
+
--yes, -y Do not ask for confirmation
|
|
274
|
+
--keep-env Keep the local env values after revocation
|
|
275
|
+
|
|
276
|
+
Deliveries
|
|
277
|
+
--state <state> pending, delivered, or failed
|
|
278
|
+
--limit <n> Page size, 1-100 (default: 50)
|
|
279
|
+
--cursor <cursor> Next-page cursor from the previous response
|
|
280
|
+
--retry <id> Send one stored webhook again (alias: webhooks)
|
|
281
|
+
--json Print a machine-readable response
|
|
282
|
+
|
|
283
|
+
Init
|
|
284
|
+
--dir <path> Project directory (default: current directory)
|
|
285
|
+
--dry-run Show planned files without writing them
|
|
286
|
+
--force Overwrite generated files
|
|
287
|
+
|
|
288
|
+
The browser step signs in to Glyph Pay, lets you pick the client account, and
|
|
252
289
|
approves this machine. The service credential goes only to this terminal.`;
|
|
253
290
|
}
|
|
254
291
|
/* ------------------------------------------------------------------ */
|
|
@@ -890,7 +927,15 @@ export async function runDeliveries(options = {}) {
|
|
|
890
927
|
}
|
|
891
928
|
return result;
|
|
892
929
|
}
|
|
893
|
-
const
|
|
930
|
+
const params = new URLSearchParams();
|
|
931
|
+
if (options.state)
|
|
932
|
+
params.set("state", options.state);
|
|
933
|
+
if (options.limit)
|
|
934
|
+
params.set("limit", String(options.limit));
|
|
935
|
+
if (options.cursor)
|
|
936
|
+
params.set("cursor", options.cursor);
|
|
937
|
+
const query = params.toString();
|
|
938
|
+
const result = await api(baseUrl, `/credential/deliveries${query ? `?${query}` : ""}`, undefined, token);
|
|
894
939
|
if (options.json) {
|
|
895
940
|
say(JSON.stringify(result, null, 2));
|
|
896
941
|
return result;
|
|
@@ -899,13 +944,22 @@ export async function runDeliveries(options = {}) {
|
|
|
899
944
|
say(style(1, `Webhook deliveries - ${result.businessName} (${result.environment} mode)`));
|
|
900
945
|
say();
|
|
901
946
|
if (result.deliveries.length === 0) {
|
|
902
|
-
say("No deliveries recorded yet.");
|
|
947
|
+
say(options.state ? `No ${options.state} deliveries recorded.` : "No deliveries recorded yet.");
|
|
903
948
|
}
|
|
904
949
|
else {
|
|
905
950
|
for (const delivery of result.deliveries)
|
|
906
951
|
printDelivery(delivery);
|
|
907
952
|
say();
|
|
908
953
|
say("Retry one with `glyphpay deliveries --retry <id>`.");
|
|
954
|
+
if (result.nextCursor) {
|
|
955
|
+
const next = ["glyphpay deliveries"];
|
|
956
|
+
if (options.state)
|
|
957
|
+
next.push("--state", options.state);
|
|
958
|
+
if (options.limit)
|
|
959
|
+
next.push("--limit", String(options.limit));
|
|
960
|
+
next.push("--cursor", result.nextCursor);
|
|
961
|
+
say(`Next page: ${next.join(" ")}`);
|
|
962
|
+
}
|
|
909
963
|
}
|
|
910
964
|
return result;
|
|
911
965
|
}
|
|
@@ -913,28 +967,28 @@ function libTemplate(ts) {
|
|
|
913
967
|
const cartId = ts
|
|
914
968
|
? `const cartId = (body as { cartId?: string } | null)?.cartId;`
|
|
915
969
|
: `const cartId = body?.cartId;`;
|
|
916
|
-
return `import { glyphPay, CheckoutError } from "glyphpay/server";
|
|
917
|
-
|
|
918
|
-
export const glyphpay = glyphPay({
|
|
919
|
-
token: process.env.GLYPHPAY_TOKEN,
|
|
920
|
-
callbackSecret: process.env.GLYPHPAY_CALLBACK_SECRET,
|
|
921
|
-
|
|
922
|
-
async priceOrder(body) {
|
|
923
|
-
// Load the cart/order from YOUR database and return its server-side price.
|
|
924
|
-
// Never trust an amount sent by the browser.
|
|
925
|
-
${cartId}
|
|
926
|
-
if (!cartId) throw new CheckoutError("Your cart is empty.");
|
|
927
|
-
throw new CheckoutError("Connect priceOrder() to your order database before accepting payments.");
|
|
928
|
-
},
|
|
929
|
-
|
|
930
|
-
async onEvent(event) {
|
|
931
|
-
// At-least-once delivery: dedupe on event.id before changing an order.
|
|
932
|
-
if (event.type === "payment.succeeded") {
|
|
933
|
-
// TODO: mark the order paid and store event.id.
|
|
934
|
-
console.log(\`GlyphPay payment succeeded for \${event.data.orderId}\`);
|
|
935
|
-
}
|
|
936
|
-
},
|
|
937
|
-
});
|
|
970
|
+
return `import { glyphPay, CheckoutError } from "glyphpay/server";
|
|
971
|
+
|
|
972
|
+
export const glyphpay = glyphPay({
|
|
973
|
+
token: process.env.GLYPHPAY_TOKEN,
|
|
974
|
+
callbackSecret: process.env.GLYPHPAY_CALLBACK_SECRET,
|
|
975
|
+
|
|
976
|
+
async priceOrder(body) {
|
|
977
|
+
// Load the cart/order from YOUR database and return its server-side price.
|
|
978
|
+
// Never trust an amount sent by the browser.
|
|
979
|
+
${cartId}
|
|
980
|
+
if (!cartId) throw new CheckoutError("Your cart is empty.");
|
|
981
|
+
throw new CheckoutError("Connect priceOrder() to your order database before accepting payments.");
|
|
982
|
+
},
|
|
983
|
+
|
|
984
|
+
async onEvent(event) {
|
|
985
|
+
// At-least-once delivery: dedupe on event.id before changing an order.
|
|
986
|
+
if (event.type === "payment.succeeded") {
|
|
987
|
+
// TODO: mark the order paid and store event.id.
|
|
988
|
+
console.log(\`GlyphPay payment succeeded for \${event.data.orderId}\`);
|
|
989
|
+
}
|
|
990
|
+
},
|
|
991
|
+
});
|
|
938
992
|
`;
|
|
939
993
|
}
|
|
940
994
|
function routeTemplate(libImport) {
|
package/package.json
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "glyphpay",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Drop-in GlyphPay checkout for websites, with a CLI for setup, diagnostics, and credential lifecycle.",
|
|
5
|
-
"homepage": "https://glyphpay.net",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"glyphpay",
|
|
8
|
-
"payments",
|
|
9
|
-
"checkout",
|
|
10
|
-
"stripe",
|
|
11
|
-
"react",
|
|
12
|
-
"nextjs"
|
|
13
|
-
],
|
|
14
|
-
"license": "UNLICENSED",
|
|
15
|
-
"type": "module",
|
|
16
|
-
"bin": {
|
|
17
|
-
"glyphpay": "dist/cli.js"
|
|
18
|
-
},
|
|
19
|
-
"files": [
|
|
20
|
-
"dist"
|
|
21
|
-
],
|
|
22
|
-
"engines": {
|
|
23
|
-
"node": ">=18"
|
|
24
|
-
},
|
|
25
|
-
"exports": {
|
|
26
|
-
"./server": {
|
|
27
|
-
"types": "./dist/server.d.ts",
|
|
28
|
-
"default": "./dist/server.js"
|
|
29
|
-
},
|
|
30
|
-
"./browser": {
|
|
31
|
-
"types": "./dist/browser.d.ts",
|
|
32
|
-
"default": "./dist/browser.js"
|
|
33
|
-
},
|
|
34
|
-
"./react": {
|
|
35
|
-
"types": "./dist/react.d.ts",
|
|
36
|
-
"default": "./dist/react.js"
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "tsc",
|
|
41
|
-
"prepare": "tsc",
|
|
42
|
-
"test": "tsc && node --test test/*.test.mjs"
|
|
43
|
-
},
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"@stripe/stripe-js": "^10.2.1"
|
|
46
|
-
},
|
|
47
|
-
"peerDependencies": {
|
|
48
|
-
"react": ">=18"
|
|
49
|
-
},
|
|
50
|
-
"peerDependenciesMeta": {
|
|
51
|
-
"react": {
|
|
52
|
-
"optional": true
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@types/node": "^22.20.5",
|
|
57
|
-
"@types/react": "^19",
|
|
58
|
-
"typescript": "^5"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "glyphpay",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Drop-in GlyphPay checkout for websites, with a CLI for setup, diagnostics, and credential lifecycle.",
|
|
5
|
+
"homepage": "https://glyphpay.net",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"glyphpay",
|
|
8
|
+
"payments",
|
|
9
|
+
"checkout",
|
|
10
|
+
"stripe",
|
|
11
|
+
"react",
|
|
12
|
+
"nextjs"
|
|
13
|
+
],
|
|
14
|
+
"license": "UNLICENSED",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"glyphpay": "dist/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
"./server": {
|
|
27
|
+
"types": "./dist/server.d.ts",
|
|
28
|
+
"default": "./dist/server.js"
|
|
29
|
+
},
|
|
30
|
+
"./browser": {
|
|
31
|
+
"types": "./dist/browser.d.ts",
|
|
32
|
+
"default": "./dist/browser.js"
|
|
33
|
+
},
|
|
34
|
+
"./react": {
|
|
35
|
+
"types": "./dist/react.d.ts",
|
|
36
|
+
"default": "./dist/react.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc",
|
|
41
|
+
"prepare": "tsc",
|
|
42
|
+
"test": "tsc && node --test test/*.test.mjs"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@stripe/stripe-js": "^10.2.1"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"peerDependenciesMeta": {
|
|
51
|
+
"react": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.20.5",
|
|
57
|
+
"@types/react": "^19",
|
|
58
|
+
"typescript": "^5"
|
|
59
|
+
}
|
|
60
|
+
}
|