@thebes/cadmea-plugin-ecommerce-square 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/LICENSE +21 -0
- package/README.md +68 -0
- package/dist/client.cjs +50 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +28 -0
- package/dist/client.d.cts.map +1 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +49 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +213 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +24 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +212 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BowenLabs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @thebes/cadmea-plugin-ecommerce-square
|
|
2
|
+
|
|
3
|
+
Square's [`PaymentProvider`](https://www.npmjs.com/package/@thebes/cadmea-plugin-ecommerce)
|
|
4
|
+
implementation for [Cadmea](https://github.com/bowenlabs/project-thebes)'s
|
|
5
|
+
ecommerce extension — raw `fetch()` against Square's REST API +
|
|
6
|
+
`crypto.subtle` for webhook signature verification. **No Square Node SDK,
|
|
7
|
+
ever** — it's Node-targeted and never runs in a V8 isolate.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @thebes/cadmea-plugin-ecommerce-square @thebes/cadmea-plugin-ecommerce
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Server-side
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createSquarePaymentProvider } from "@thebes/cadmea-plugin-ecommerce-square";
|
|
17
|
+
|
|
18
|
+
const provider = createSquarePaymentProvider({
|
|
19
|
+
accessToken: env.SQUARE_ACCESS_TOKEN,
|
|
20
|
+
locationId: env.SQUARE_LOCATION_ID, // or string[] for multi-location
|
|
21
|
+
environment: "sandbox", // or "production"
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Pass `provider` to `@thebes/cadmea-plugin-ecommerce`'s `createCheckoutHandler`/
|
|
26
|
+
`createWebhookHandler`. Webhook signature verification follows Square's
|
|
27
|
+
documented algorithm exactly: `HMAC-SHA256(notificationUrl + rawBody,
|
|
28
|
+
signatureKey)`, base64-encoded, checked against the
|
|
29
|
+
`x-square-hmacsha256-signature` header — fails closed (returns `false`,
|
|
30
|
+
never throws) if no `notificationUrl` is configured, since Square's scheme
|
|
31
|
+
genuinely needs one.
|
|
32
|
+
|
|
33
|
+
**Not implemented in this first cut:** `catalogSync` and `subscriptions` —
|
|
34
|
+
Square's loyalty/recurring-order model doesn't map cleanly onto either
|
|
35
|
+
optional `PaymentProvider` capability without further design. Use
|
|
36
|
+
`@thebes/cadmea-plugin-ecommerce-stripe` if you need native subscriptions.
|
|
37
|
+
|
|
38
|
+
## Client-side tokenization
|
|
39
|
+
|
|
40
|
+
A separate `/client` subpath — card data must never reach the Worker, so
|
|
41
|
+
tokenization happens entirely in the browser via Square's Web Payments SDK
|
|
42
|
+
(loaded dynamically, no bundler config needed). This is **not a SolidJS
|
|
43
|
+
component** — it's a thin, framework-agnostic helper, the same precedent
|
|
44
|
+
this codebase already set for Phosphor icons and TipTap (no official Solid
|
|
45
|
+
binding exists for Square's SDK either, so the vendor's own
|
|
46
|
+
framework-agnostic build is used directly):
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { createSquareCardField } from "@thebes/cadmea-plugin-ecommerce-square/client";
|
|
50
|
+
|
|
51
|
+
const card = await createSquareCardField(containerEl, {
|
|
52
|
+
applicationId: PUBLIC_SQUARE_APPLICATION_ID,
|
|
53
|
+
locationId: PUBLIC_SQUARE_LOCATION_ID,
|
|
54
|
+
environment: "sandbox",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// on checkout submit:
|
|
58
|
+
const paymentSourceToken = await card.tokenize();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`@thebes/cadmea-plugin-ecommerce-stripe/client`'s `createStripeCardField`
|
|
62
|
+
shares the exact same `{ tokenize(), destroy() }` shape — swapping
|
|
63
|
+
providers means swapping which helper you call, not rewriting your
|
|
64
|
+
checkout UI.
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT © BowenLabs
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/client.ts
|
|
3
|
+
function sdkUrl(environment) {
|
|
4
|
+
return environment === "production" ? "https://web.squarecdn.com/v1/square.js" : "https://sandbox.web.squarecdn.com/v1/square.js";
|
|
5
|
+
}
|
|
6
|
+
let sdkLoadPromise;
|
|
7
|
+
function loadSquareSdk(environment) {
|
|
8
|
+
if (window.Square) return Promise.resolve(window.Square);
|
|
9
|
+
if (sdkLoadPromise) return sdkLoadPromise;
|
|
10
|
+
sdkLoadPromise = new Promise((resolve, reject) => {
|
|
11
|
+
const script = document.createElement("script");
|
|
12
|
+
script.src = sdkUrl(environment);
|
|
13
|
+
script.onload = () => resolve(window.Square);
|
|
14
|
+
script.onerror = () => reject(/* @__PURE__ */ new Error(`Failed to load Square Web Payments SDK from ${script.src}`));
|
|
15
|
+
document.head.appendChild(script);
|
|
16
|
+
});
|
|
17
|
+
return sdkLoadPromise;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Loads the Square Web Payments SDK (if not already present), initializes
|
|
21
|
+
* a card field, and attaches it to `container`. The returned `tokenize()`
|
|
22
|
+
* produces a one-time payment source token suitable for
|
|
23
|
+
* `PaymentProvider.checkout`'s `paymentSourceToken` — raw card data never
|
|
24
|
+
* leaves the browser.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* const card = await createSquareCardField(containerEl, { applicationId, locationId });
|
|
28
|
+
* // ...on checkout submit:
|
|
29
|
+
* const token = await card.tokenize();
|
|
30
|
+
* await fetch("/api/checkout", { method: "POST", body: JSON.stringify({ paymentSourceToken: token, ... }) });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
async function createSquareCardField(container, options) {
|
|
34
|
+
const card = await (await loadSquareSdk(options.environment)).payments(options.applicationId, options.locationId).card();
|
|
35
|
+
await card.attach(container);
|
|
36
|
+
return {
|
|
37
|
+
async tokenize() {
|
|
38
|
+
const result = await card.tokenize();
|
|
39
|
+
if (result.status !== "OK") throw new Error(`Square card tokenization failed: ${result.status}${result.errors ? ` — ${JSON.stringify(result.errors)}` : ""}`);
|
|
40
|
+
return result.token;
|
|
41
|
+
},
|
|
42
|
+
async destroy() {
|
|
43
|
+
await card.destroy();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
exports.createSquareCardField = createSquareCardField;
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=client.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.cjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// MIT licensed. See LICENSE in the repo root.\n//\n// Browser-only client-side tokenization helper — a separate subpath\n// (`@thebes/cadmea-plugin-ecommerce-square/client`) so the server-side\n// provider entry never pulls in anything DOM-shaped, and vice versa.\n//\n// Square's Web Payments SDK is already vanilla, framework-agnostic,\n// DOM-attach JS with no official Solid binding — same shape of gap as\n// Phosphor icons and TipTap (both resolved in this codebase's DECISIONS.md\n// 2026-06-19 entry by using the vendor's framework-agnostic build\n// directly, not an unofficial wrapper). This file follows that precedent:\n// it is NOT a Solid component, has no JSX — just script-tag loading + SDK\n// init + card.attach()/card.tokenize() boilerplate, so every consumer\n// doesn't hand-roll the same dance. Card data never reaches the Worker —\n// tokenization happens entirely in the browser, here.\n\nexport interface SquareCardFieldOptions {\n applicationId: string;\n locationId: string;\n environment?: \"sandbox\" | \"production\";\n}\n\nexport interface CardField {\n tokenize(): Promise<string>;\n destroy(): Promise<void>;\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: the Square Web Payments SDK ships no official TypeScript types for the `window.Square` global\ntype SquareGlobal = any;\n\nfunction sdkUrl(environment: SquareCardFieldOptions[\"environment\"]): string {\n return environment === \"production\"\n ? \"https://web.squarecdn.com/v1/square.js\"\n : \"https://sandbox.web.squarecdn.com/v1/square.js\";\n}\n\nlet sdkLoadPromise: Promise<SquareGlobal> | undefined;\n\nfunction loadSquareSdk(\n environment: SquareCardFieldOptions[\"environment\"],\n): Promise<SquareGlobal> {\n if ((window as { Square?: SquareGlobal }).Square) {\n return Promise.resolve((window as { Square?: SquareGlobal }).Square);\n }\n if (sdkLoadPromise) return sdkLoadPromise;\n\n sdkLoadPromise = new Promise((resolve, reject) => {\n const script = document.createElement(\"script\");\n script.src = sdkUrl(environment);\n script.onload = () => resolve((window as { Square?: SquareGlobal }).Square);\n script.onerror = () =>\n reject(\n new Error(`Failed to load Square Web Payments SDK from ${script.src}`),\n );\n document.head.appendChild(script);\n });\n return sdkLoadPromise;\n}\n\n/**\n * Loads the Square Web Payments SDK (if not already present), initializes\n * a card field, and attaches it to `container`. The returned `tokenize()`\n * produces a one-time payment source token suitable for\n * `PaymentProvider.checkout`'s `paymentSourceToken` — raw card data never\n * leaves the browser.\n *\n * ```ts\n * const card = await createSquareCardField(containerEl, { applicationId, locationId });\n * // ...on checkout submit:\n * const token = await card.tokenize();\n * await fetch(\"/api/checkout\", { method: \"POST\", body: JSON.stringify({ paymentSourceToken: token, ... }) });\n * ```\n */\nexport async function createSquareCardField(\n container: HTMLElement | string,\n options: SquareCardFieldOptions,\n): Promise<CardField> {\n const Square = await loadSquareSdk(options.environment);\n const payments = Square.payments(options.applicationId, options.locationId);\n const card = await payments.card();\n await card.attach(container);\n\n return {\n async tokenize() {\n const result = await card.tokenize();\n if (result.status !== \"OK\") {\n throw new Error(\n `Square card tokenization failed: ${result.status}${\n result.errors ? ` — ${JSON.stringify(result.errors)}` : \"\"\n }`,\n );\n }\n return result.token as string;\n },\n async destroy() {\n await card.destroy();\n },\n };\n}\n"],"mappings":";;AA+BA,SAAS,OAAO,aAA4D;CAC1E,OAAO,gBAAgB,eACnB,2CACA;AACN;AAEA,IAAI;AAEJ,SAAS,cACP,aACuB;CACvB,IAAK,OAAqC,QACxC,OAAO,QAAQ,QAAS,OAAqC,MAAM;CAErE,IAAI,gBAAgB,OAAO;CAE3B,iBAAiB,IAAI,SAAS,SAAS,WAAW;EAChD,MAAM,SAAS,SAAS,cAAc,QAAQ;EAC9C,OAAO,MAAM,OAAO,WAAW;EAC/B,OAAO,eAAe,QAAS,OAAqC,MAAM;EAC1E,OAAO,gBACL,uBACE,IAAI,MAAM,+CAA+C,OAAO,KAAK,CACvE;EACF,SAAS,KAAK,YAAY,MAAM;CAClC,CAAC;CACD,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,eAAsB,sBACpB,WACA,SACoB;CAGpB,MAAM,OAAO,OADI,MADI,cAAc,QAAQ,WAAW,EAAA,CAC9B,SAAS,QAAQ,eAAe,QAAQ,UACtC,CAAC,CAAC,KAAK;CACjC,MAAM,KAAK,OAAO,SAAS;CAE3B,OAAO;EACL,MAAM,WAAW;GACf,MAAM,SAAS,MAAM,KAAK,SAAS;GACnC,IAAI,OAAO,WAAW,MACpB,MAAM,IAAI,MACR,oCAAoC,OAAO,SACzC,OAAO,SAAS,MAAM,KAAK,UAAU,OAAO,MAAM,MAAM,IAE5D;GAEF,OAAO,OAAO;EAChB;EACA,MAAM,UAAU;GACd,MAAM,KAAK,QAAQ;EACrB;CACF;AACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/client.d.ts
|
|
2
|
+
interface SquareCardFieldOptions {
|
|
3
|
+
applicationId: string;
|
|
4
|
+
locationId: string;
|
|
5
|
+
environment?: "sandbox" | "production";
|
|
6
|
+
}
|
|
7
|
+
interface CardField {
|
|
8
|
+
tokenize(): Promise<string>;
|
|
9
|
+
destroy(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Loads the Square Web Payments SDK (if not already present), initializes
|
|
13
|
+
* a card field, and attaches it to `container`. The returned `tokenize()`
|
|
14
|
+
* produces a one-time payment source token suitable for
|
|
15
|
+
* `PaymentProvider.checkout`'s `paymentSourceToken` — raw card data never
|
|
16
|
+
* leaves the browser.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const card = await createSquareCardField(containerEl, { applicationId, locationId });
|
|
20
|
+
* // ...on checkout submit:
|
|
21
|
+
* const token = await card.tokenize();
|
|
22
|
+
* await fetch("/api/checkout", { method: "POST", body: JSON.stringify({ paymentSourceToken: token, ... }) });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function createSquareCardField(container: HTMLElement | string, options: SquareCardFieldOptions): Promise<CardField>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { CardField, SquareCardFieldOptions, createSquareCardField };
|
|
28
|
+
//# sourceMappingURL=client.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.cts","names":[],"sources":["../src/client.ts"],"mappings":";UAiBiB,sBAAA;EACf,aAAA;EACA,UAAA;EACA,WAAA;AAAA;AAAA,UAGe,SAAA;EACf,QAAA,IAAY,OAAA;EACZ,OAAA,IAAW,OAAO;AAAA;AALP;AAGb;;;;;;;;;AAEoB;AAiDpB;;;AAtDa,iBAsDS,qBAAA,CACpB,SAAA,EAAW,WAAA,WACX,OAAA,EAAS,sBAAA,GACR,OAAA,CAAQ,SAAA"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/client.d.ts
|
|
2
|
+
interface SquareCardFieldOptions {
|
|
3
|
+
applicationId: string;
|
|
4
|
+
locationId: string;
|
|
5
|
+
environment?: "sandbox" | "production";
|
|
6
|
+
}
|
|
7
|
+
interface CardField {
|
|
8
|
+
tokenize(): Promise<string>;
|
|
9
|
+
destroy(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Loads the Square Web Payments SDK (if not already present), initializes
|
|
13
|
+
* a card field, and attaches it to `container`. The returned `tokenize()`
|
|
14
|
+
* produces a one-time payment source token suitable for
|
|
15
|
+
* `PaymentProvider.checkout`'s `paymentSourceToken` — raw card data never
|
|
16
|
+
* leaves the browser.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const card = await createSquareCardField(containerEl, { applicationId, locationId });
|
|
20
|
+
* // ...on checkout submit:
|
|
21
|
+
* const token = await card.tokenize();
|
|
22
|
+
* await fetch("/api/checkout", { method: "POST", body: JSON.stringify({ paymentSourceToken: token, ... }) });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
declare function createSquareCardField(container: HTMLElement | string, options: SquareCardFieldOptions): Promise<CardField>;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { CardField, SquareCardFieldOptions, createSquareCardField };
|
|
28
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";UAiBiB,sBAAA;EACf,aAAA;EACA,UAAA;EACA,WAAA;AAAA;AAAA,UAGe,SAAA;EACf,QAAA,IAAY,OAAA;EACZ,OAAA,IAAW,OAAO;AAAA;AALP;AAGb;;;;;;;;;AAEoB;AAiDpB;;;AAtDa,iBAsDS,qBAAA,CACpB,SAAA,EAAW,WAAA,WACX,OAAA,EAAS,sBAAA,GACR,OAAA,CAAQ,SAAA"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/client.ts
|
|
2
|
+
function sdkUrl(environment) {
|
|
3
|
+
return environment === "production" ? "https://web.squarecdn.com/v1/square.js" : "https://sandbox.web.squarecdn.com/v1/square.js";
|
|
4
|
+
}
|
|
5
|
+
let sdkLoadPromise;
|
|
6
|
+
function loadSquareSdk(environment) {
|
|
7
|
+
if (window.Square) return Promise.resolve(window.Square);
|
|
8
|
+
if (sdkLoadPromise) return sdkLoadPromise;
|
|
9
|
+
sdkLoadPromise = new Promise((resolve, reject) => {
|
|
10
|
+
const script = document.createElement("script");
|
|
11
|
+
script.src = sdkUrl(environment);
|
|
12
|
+
script.onload = () => resolve(window.Square);
|
|
13
|
+
script.onerror = () => reject(/* @__PURE__ */ new Error(`Failed to load Square Web Payments SDK from ${script.src}`));
|
|
14
|
+
document.head.appendChild(script);
|
|
15
|
+
});
|
|
16
|
+
return sdkLoadPromise;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Loads the Square Web Payments SDK (if not already present), initializes
|
|
20
|
+
* a card field, and attaches it to `container`. The returned `tokenize()`
|
|
21
|
+
* produces a one-time payment source token suitable for
|
|
22
|
+
* `PaymentProvider.checkout`'s `paymentSourceToken` — raw card data never
|
|
23
|
+
* leaves the browser.
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const card = await createSquareCardField(containerEl, { applicationId, locationId });
|
|
27
|
+
* // ...on checkout submit:
|
|
28
|
+
* const token = await card.tokenize();
|
|
29
|
+
* await fetch("/api/checkout", { method: "POST", body: JSON.stringify({ paymentSourceToken: token, ... }) });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
async function createSquareCardField(container, options) {
|
|
33
|
+
const card = await (await loadSquareSdk(options.environment)).payments(options.applicationId, options.locationId).card();
|
|
34
|
+
await card.attach(container);
|
|
35
|
+
return {
|
|
36
|
+
async tokenize() {
|
|
37
|
+
const result = await card.tokenize();
|
|
38
|
+
if (result.status !== "OK") throw new Error(`Square card tokenization failed: ${result.status}${result.errors ? ` — ${JSON.stringify(result.errors)}` : ""}`);
|
|
39
|
+
return result.token;
|
|
40
|
+
},
|
|
41
|
+
async destroy() {
|
|
42
|
+
await card.destroy();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { createSquareCardField };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// MIT licensed. See LICENSE in the repo root.\n//\n// Browser-only client-side tokenization helper — a separate subpath\n// (`@thebes/cadmea-plugin-ecommerce-square/client`) so the server-side\n// provider entry never pulls in anything DOM-shaped, and vice versa.\n//\n// Square's Web Payments SDK is already vanilla, framework-agnostic,\n// DOM-attach JS with no official Solid binding — same shape of gap as\n// Phosphor icons and TipTap (both resolved in this codebase's DECISIONS.md\n// 2026-06-19 entry by using the vendor's framework-agnostic build\n// directly, not an unofficial wrapper). This file follows that precedent:\n// it is NOT a Solid component, has no JSX — just script-tag loading + SDK\n// init + card.attach()/card.tokenize() boilerplate, so every consumer\n// doesn't hand-roll the same dance. Card data never reaches the Worker —\n// tokenization happens entirely in the browser, here.\n\nexport interface SquareCardFieldOptions {\n applicationId: string;\n locationId: string;\n environment?: \"sandbox\" | \"production\";\n}\n\nexport interface CardField {\n tokenize(): Promise<string>;\n destroy(): Promise<void>;\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: the Square Web Payments SDK ships no official TypeScript types for the `window.Square` global\ntype SquareGlobal = any;\n\nfunction sdkUrl(environment: SquareCardFieldOptions[\"environment\"]): string {\n return environment === \"production\"\n ? \"https://web.squarecdn.com/v1/square.js\"\n : \"https://sandbox.web.squarecdn.com/v1/square.js\";\n}\n\nlet sdkLoadPromise: Promise<SquareGlobal> | undefined;\n\nfunction loadSquareSdk(\n environment: SquareCardFieldOptions[\"environment\"],\n): Promise<SquareGlobal> {\n if ((window as { Square?: SquareGlobal }).Square) {\n return Promise.resolve((window as { Square?: SquareGlobal }).Square);\n }\n if (sdkLoadPromise) return sdkLoadPromise;\n\n sdkLoadPromise = new Promise((resolve, reject) => {\n const script = document.createElement(\"script\");\n script.src = sdkUrl(environment);\n script.onload = () => resolve((window as { Square?: SquareGlobal }).Square);\n script.onerror = () =>\n reject(\n new Error(`Failed to load Square Web Payments SDK from ${script.src}`),\n );\n document.head.appendChild(script);\n });\n return sdkLoadPromise;\n}\n\n/**\n * Loads the Square Web Payments SDK (if not already present), initializes\n * a card field, and attaches it to `container`. The returned `tokenize()`\n * produces a one-time payment source token suitable for\n * `PaymentProvider.checkout`'s `paymentSourceToken` — raw card data never\n * leaves the browser.\n *\n * ```ts\n * const card = await createSquareCardField(containerEl, { applicationId, locationId });\n * // ...on checkout submit:\n * const token = await card.tokenize();\n * await fetch(\"/api/checkout\", { method: \"POST\", body: JSON.stringify({ paymentSourceToken: token, ... }) });\n * ```\n */\nexport async function createSquareCardField(\n container: HTMLElement | string,\n options: SquareCardFieldOptions,\n): Promise<CardField> {\n const Square = await loadSquareSdk(options.environment);\n const payments = Square.payments(options.applicationId, options.locationId);\n const card = await payments.card();\n await card.attach(container);\n\n return {\n async tokenize() {\n const result = await card.tokenize();\n if (result.status !== \"OK\") {\n throw new Error(\n `Square card tokenization failed: ${result.status}${\n result.errors ? ` — ${JSON.stringify(result.errors)}` : \"\"\n }`,\n );\n }\n return result.token as string;\n },\n async destroy() {\n await card.destroy();\n },\n };\n}\n"],"mappings":";AA+BA,SAAS,OAAO,aAA4D;CAC1E,OAAO,gBAAgB,eACnB,2CACA;AACN;AAEA,IAAI;AAEJ,SAAS,cACP,aACuB;CACvB,IAAK,OAAqC,QACxC,OAAO,QAAQ,QAAS,OAAqC,MAAM;CAErE,IAAI,gBAAgB,OAAO;CAE3B,iBAAiB,IAAI,SAAS,SAAS,WAAW;EAChD,MAAM,SAAS,SAAS,cAAc,QAAQ;EAC9C,OAAO,MAAM,OAAO,WAAW;EAC/B,OAAO,eAAe,QAAS,OAAqC,MAAM;EAC1E,OAAO,gBACL,uBACE,IAAI,MAAM,+CAA+C,OAAO,KAAK,CACvE;EACF,SAAS,KAAK,YAAY,MAAM;CAClC,CAAC;CACD,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,eAAsB,sBACpB,WACA,SACoB;CAGpB,MAAM,OAAO,OADI,MADI,cAAc,QAAQ,WAAW,EAAA,CAC9B,SAAS,QAAQ,eAAe,QAAQ,UACtC,CAAC,CAAC,KAAK;CACjC,MAAM,KAAK,OAAO,SAAS;CAE3B,OAAO;EACL,MAAM,WAAW;GACf,MAAM,SAAS,MAAM,KAAK,SAAS;GACnC,IAAI,OAAO,WAAW,MACpB,MAAM,IAAI,MACR,oCAAoC,OAAO,SACzC,OAAO,SAAS,MAAM,KAAK,UAAU,OAAO,MAAM,MAAM,IAE5D;GAEF,OAAO,OAAO;EAChB;EACA,MAAM,UAAU;GACd,MAAM,KAAK,QAAQ;EACrB;CACF;AACF"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/provider.ts
|
|
3
|
+
const DEFAULT_API_VERSION = "2025-01-23";
|
|
4
|
+
function baseUrl(environment) {
|
|
5
|
+
return environment === "production" ? "https://connect.squareup.com" : "https://connect.squareupsandbox.com";
|
|
6
|
+
}
|
|
7
|
+
function primaryLocation(locationId) {
|
|
8
|
+
return Array.isArray(locationId) ? locationId[0] : locationId;
|
|
9
|
+
}
|
|
10
|
+
function allLocations(locationId) {
|
|
11
|
+
return Array.isArray(locationId) ? locationId : [locationId];
|
|
12
|
+
}
|
|
13
|
+
var SquareApiError = class extends Error {
|
|
14
|
+
status;
|
|
15
|
+
body;
|
|
16
|
+
constructor(message, status, body) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.body = body;
|
|
20
|
+
this.name = "SquareApiError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
async function squareFetch(config, path, init) {
|
|
24
|
+
const response = await fetch(`${baseUrl(config.environment)}${path}`, {
|
|
25
|
+
method: init.method,
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${config.accessToken}`,
|
|
28
|
+
"Content-Type": "application/json",
|
|
29
|
+
"Square-Version": config.apiVersion ?? DEFAULT_API_VERSION
|
|
30
|
+
},
|
|
31
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
32
|
+
});
|
|
33
|
+
const text = await response.text();
|
|
34
|
+
const parsed = text ? JSON.parse(text) : {};
|
|
35
|
+
if (!response.ok) throw new SquareApiError(`Square API request to "${path}" failed with status ${response.status}`, response.status, parsed);
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
async function hmacSha256Base64(message, secret) {
|
|
39
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {
|
|
40
|
+
name: "HMAC",
|
|
41
|
+
hash: "SHA-256"
|
|
42
|
+
}, false, ["sign"]);
|
|
43
|
+
const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message));
|
|
44
|
+
return btoa(String.fromCharCode(...new Uint8Array(signature)));
|
|
45
|
+
}
|
|
46
|
+
function timingSafeEqual(a, b) {
|
|
47
|
+
if (a.length !== b.length) return false;
|
|
48
|
+
let mismatch = 0;
|
|
49
|
+
for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
50
|
+
return mismatch === 0;
|
|
51
|
+
}
|
|
52
|
+
async function checkCatalogPrices(config, refs) {
|
|
53
|
+
if (refs.length === 0) return [];
|
|
54
|
+
const objects = (await squareFetch(config, "/v2/catalog/batch-retrieve", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
body: { object_ids: refs }
|
|
57
|
+
})).objects ?? [];
|
|
58
|
+
const priceByRef = new Map(objects.map((object) => [object.id, object.item_variation_data?.price_money]));
|
|
59
|
+
const counts = (await squareFetch(config, "/v2/inventory/batch-retrieve-counts", {
|
|
60
|
+
method: "POST",
|
|
61
|
+
body: {
|
|
62
|
+
catalog_object_ids: refs,
|
|
63
|
+
location_ids: allLocations(config.locationId)
|
|
64
|
+
}
|
|
65
|
+
})).counts ?? [];
|
|
66
|
+
const quantityByRef = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const count of counts) {
|
|
68
|
+
const existing = quantityByRef.get(count.catalog_object_id) ?? 0;
|
|
69
|
+
quantityByRef.set(count.catalog_object_id, existing + Number.parseFloat(count.quantity));
|
|
70
|
+
}
|
|
71
|
+
return refs.map((catalogRef) => {
|
|
72
|
+
const price = priceByRef.get(catalogRef);
|
|
73
|
+
return {
|
|
74
|
+
catalogRef,
|
|
75
|
+
serverUnitPrice: {
|
|
76
|
+
amount: price?.amount ?? 0,
|
|
77
|
+
currency: price?.currency ?? "USD"
|
|
78
|
+
},
|
|
79
|
+
availableQuantity: quantityByRef.get(catalogRef)
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function findOrCreateCustomer(config, email, idempotencyKey) {
|
|
84
|
+
const existing = (await squareFetch(config, "/v2/customers/search", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
body: { query: { filter: { email_address: { exact: email } } } }
|
|
87
|
+
})).customers?.[0];
|
|
88
|
+
if (existing) return existing.id;
|
|
89
|
+
return (await squareFetch(config, "/v2/customers", {
|
|
90
|
+
method: "POST",
|
|
91
|
+
body: {
|
|
92
|
+
idempotency_key: idempotencyKey,
|
|
93
|
+
email_address: email
|
|
94
|
+
}
|
|
95
|
+
})).customer.id;
|
|
96
|
+
}
|
|
97
|
+
function mapSquarePaymentStatus(status) {
|
|
98
|
+
if (status === "COMPLETED" || status === "APPROVED") return "succeeded";
|
|
99
|
+
if (status === "PENDING") return "requires_action";
|
|
100
|
+
return "failed";
|
|
101
|
+
}
|
|
102
|
+
async function checkout(config, request) {
|
|
103
|
+
const order = (await squareFetch(config, "/v2/orders", {
|
|
104
|
+
method: "POST",
|
|
105
|
+
body: {
|
|
106
|
+
idempotency_key: request.idempotencyKey,
|
|
107
|
+
order: {
|
|
108
|
+
location_id: primaryLocation(config.locationId),
|
|
109
|
+
line_items: request.lineItems.map((item) => ({
|
|
110
|
+
catalog_object_id: item.catalogRef,
|
|
111
|
+
quantity: String(item.quantity)
|
|
112
|
+
}))
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
})).order;
|
|
116
|
+
const paymentResponse = await squareFetch(config, "/v2/payments", {
|
|
117
|
+
method: "POST",
|
|
118
|
+
body: {
|
|
119
|
+
idempotency_key: crypto.randomUUID(),
|
|
120
|
+
source_id: request.paymentSourceToken,
|
|
121
|
+
amount_money: order.total_money,
|
|
122
|
+
order_id: order.id,
|
|
123
|
+
location_id: primaryLocation(config.locationId)
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const payment = paymentResponse.payment;
|
|
127
|
+
return {
|
|
128
|
+
providerOrderRef: order.id,
|
|
129
|
+
providerPaymentRef: payment.id,
|
|
130
|
+
status: mapSquarePaymentStatus(payment.status),
|
|
131
|
+
amount: {
|
|
132
|
+
amount: payment.amount_money.amount,
|
|
133
|
+
currency: payment.amount_money.currency
|
|
134
|
+
},
|
|
135
|
+
raw: paymentResponse
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function verifyWebhookSignature(args) {
|
|
139
|
+
if (!args.notificationUrl) return false;
|
|
140
|
+
const signatureHeader = args.headers.get("x-square-hmacsha256-signature");
|
|
141
|
+
if (!signatureHeader) return false;
|
|
142
|
+
return timingSafeEqual(await hmacSha256Base64(args.notificationUrl + args.rawBody, args.secret), signatureHeader);
|
|
143
|
+
}
|
|
144
|
+
function parseWebhookEvent(rawBody) {
|
|
145
|
+
const payload = JSON.parse(rawBody);
|
|
146
|
+
const eventId = payload.event_id;
|
|
147
|
+
if (payload.type === "payment.updated") {
|
|
148
|
+
const payment = payload.data.object.payment;
|
|
149
|
+
if (payment) return {
|
|
150
|
+
eventId,
|
|
151
|
+
event: {
|
|
152
|
+
kind: "payment.updated",
|
|
153
|
+
providerPaymentRef: payment.id,
|
|
154
|
+
status: mapSquarePaymentStatus(payment.status) === "succeeded" ? "succeeded" : "failed"
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (payload.type === "order.updated") {
|
|
159
|
+
const orderUpdated = payload.data.object.order_updated;
|
|
160
|
+
if (orderUpdated) {
|
|
161
|
+
const status = orderUpdated.state === "COMPLETED" ? "paid" : orderUpdated.state === "CANCELED" ? "canceled" : "failed";
|
|
162
|
+
return {
|
|
163
|
+
eventId,
|
|
164
|
+
event: {
|
|
165
|
+
kind: "order.updated",
|
|
166
|
+
providerOrderRef: orderUpdated.order_id,
|
|
167
|
+
status
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (payload.type === "subscription.updated") {
|
|
173
|
+
const subscription = payload.data.object.subscription;
|
|
174
|
+
if (subscription) return {
|
|
175
|
+
eventId,
|
|
176
|
+
event: {
|
|
177
|
+
kind: "subscription.updated",
|
|
178
|
+
providerSubscriptionRef: subscription.id,
|
|
179
|
+
status: subscription.status
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
eventId,
|
|
185
|
+
event: {
|
|
186
|
+
kind: "unhandled",
|
|
187
|
+
rawType: payload.type
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Creates a `PaymentProvider` backed by Square's REST API — raw `fetch()`
|
|
193
|
+
* + `crypto.subtle`, no Square Node SDK. Implements the required
|
|
194
|
+
* `checkCatalogPrices`/`findOrCreateCustomer`/`checkout`/
|
|
195
|
+
* `verifyWebhookSignature`/`parseWebhookEvent`; omits `catalogSync` and
|
|
196
|
+
* `subscriptions` in this first cut (Square's loyalty/recurring-order
|
|
197
|
+
* model doesn't map cleanly onto either optional capability without
|
|
198
|
+
* further design — see the package README for the gap).
|
|
199
|
+
*/
|
|
200
|
+
function createSquarePaymentProvider(config) {
|
|
201
|
+
return {
|
|
202
|
+
name: "square",
|
|
203
|
+
checkCatalogPrices: (refs) => checkCatalogPrices(config, refs),
|
|
204
|
+
findOrCreateCustomer: (email, idempotencyKey) => findOrCreateCustomer(config, email, idempotencyKey),
|
|
205
|
+
checkout: (request) => checkout(config, request),
|
|
206
|
+
verifyWebhookSignature,
|
|
207
|
+
parseWebhookEvent
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
exports.createSquarePaymentProvider = createSquarePaymentProvider;
|
|
212
|
+
|
|
213
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/provider.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// MIT licensed. See LICENSE in the repo root.\n//\n// Square's REST API directly via fetch() — never the `square` npm SDK\n// (Node-targeted, BigInt-typed monetary amounts the raw REST API doesn't\n// actually return — its JSON responses use plain numbers; BigInt is purely\n// an artifact of the SDK's own typed wrapper). Webhook signature\n// verification uses crypto.subtle, mirroring the exact HMAC idiom already\n// in @thebes/cadmus/cms's webhooks.ts (compute-and-compare instead of\n// sign-and-attach).\n\nimport type {\n CatalogPriceCheck,\n CheckoutRequest,\n CheckoutResult,\n PaymentProvider,\n} from \"@thebes/cadmea-plugin-ecommerce\";\n\nexport interface SquareProviderConfig {\n accessToken: string;\n /** First entry is used as the primary location for orders/payments; all entries are queried for inventory. */\n locationId: string | string[];\n environment?: \"sandbox\" | \"production\";\n /** Square's API version header. Default: a fixed, tested version — bump deliberately, not implicitly. */\n apiVersion?: string;\n}\n\nconst DEFAULT_API_VERSION = \"2025-01-23\";\n\nfunction baseUrl(environment: SquareProviderConfig[\"environment\"]): string {\n return environment === \"production\"\n ? \"https://connect.squareup.com\"\n : \"https://connect.squareupsandbox.com\";\n}\n\nfunction primaryLocation(\n locationId: SquareProviderConfig[\"locationId\"],\n): string {\n return Array.isArray(locationId) ? locationId[0] : locationId;\n}\n\nfunction allLocations(\n locationId: SquareProviderConfig[\"locationId\"],\n): string[] {\n return Array.isArray(locationId) ? locationId : [locationId];\n}\n\nclass SquareApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message);\n this.name = \"SquareApiError\";\n }\n}\n\nasync function squareFetch(\n config: SquareProviderConfig,\n path: string,\n init: { method: string; body?: unknown },\n): Promise<Record<string, unknown>> {\n const response = await fetch(`${baseUrl(config.environment)}${path}`, {\n method: init.method,\n headers: {\n Authorization: `Bearer ${config.accessToken}`,\n \"Content-Type\": \"application/json\",\n \"Square-Version\": config.apiVersion ?? DEFAULT_API_VERSION,\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n });\n const text = await response.text();\n const parsed = text ? JSON.parse(text) : {};\n if (!response.ok) {\n throw new SquareApiError(\n `Square API request to \"${path}\" failed with status ${response.status}`,\n response.status,\n parsed,\n );\n }\n return parsed;\n}\n\nasync function hmacSha256Base64(\n message: string,\n secret: string,\n): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(message),\n );\n return btoa(String.fromCharCode(...new Uint8Array(signature)));\n}\n\n// Constant-time string comparison — a plain `===` on a signature leaks,\n// via early-exit timing, how many leading bytes matched, which is enough\n// to forge a valid HMAC byte-by-byte. Mirrors the Stripe provider's helper.\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let mismatch = 0;\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return mismatch === 0;\n}\n\ninterface SquareCatalogObject {\n id: string;\n type: string;\n item_variation_data?: {\n price_money?: { amount: number; currency: string };\n };\n}\n\ninterface SquareInventoryCount {\n catalog_object_id: string;\n quantity: string;\n}\n\nasync function checkCatalogPrices(\n config: SquareProviderConfig,\n refs: string[],\n): Promise<CatalogPriceCheck[]> {\n if (refs.length === 0) return [];\n\n const catalogResponse = await squareFetch(\n config,\n \"/v2/catalog/batch-retrieve\",\n {\n method: \"POST\",\n body: { object_ids: refs },\n },\n );\n const objects = (catalogResponse.objects ?? []) as SquareCatalogObject[];\n const priceByRef = new Map(\n objects.map((object) => [\n object.id,\n object.item_variation_data?.price_money,\n ]),\n );\n\n const inventoryResponse = await squareFetch(\n config,\n \"/v2/inventory/batch-retrieve-counts\",\n {\n method: \"POST\",\n body: {\n catalog_object_ids: refs,\n location_ids: allLocations(config.locationId),\n },\n },\n );\n const counts = (inventoryResponse.counts ?? []) as SquareInventoryCount[];\n const quantityByRef = new Map<string, number>();\n for (const count of counts) {\n const existing = quantityByRef.get(count.catalog_object_id) ?? 0;\n quantityByRef.set(\n count.catalog_object_id,\n existing + Number.parseFloat(count.quantity),\n );\n }\n\n return refs.map((catalogRef) => {\n const price = priceByRef.get(catalogRef);\n return {\n catalogRef,\n serverUnitPrice: {\n amount: price?.amount ?? 0,\n currency: price?.currency ?? \"USD\",\n },\n availableQuantity: quantityByRef.get(catalogRef),\n };\n });\n}\n\nasync function findOrCreateCustomer(\n config: SquareProviderConfig,\n email: string,\n idempotencyKey: string,\n): Promise<string> {\n const searchResponse = await squareFetch(config, \"/v2/customers/search\", {\n method: \"POST\",\n body: { query: { filter: { email_address: { exact: email } } } },\n });\n const existing = (\n searchResponse.customers as Array<{ id: string }> | undefined\n )?.[0];\n if (existing) return existing.id;\n\n const createResponse = await squareFetch(config, \"/v2/customers\", {\n method: \"POST\",\n body: { idempotency_key: idempotencyKey, email_address: email },\n });\n return (createResponse.customer as { id: string }).id;\n}\n\nfunction mapSquarePaymentStatus(\n status: string | undefined,\n): CheckoutResult[\"status\"] {\n if (status === \"COMPLETED\" || status === \"APPROVED\") return \"succeeded\";\n if (status === \"PENDING\") return \"requires_action\";\n return \"failed\";\n}\n\nasync function checkout(\n config: SquareProviderConfig,\n request: CheckoutRequest,\n): Promise<CheckoutResult> {\n const orderResponse = await squareFetch(config, \"/v2/orders\", {\n method: \"POST\",\n body: {\n idempotency_key: request.idempotencyKey,\n order: {\n location_id: primaryLocation(config.locationId),\n line_items: request.lineItems.map((item) => ({\n catalog_object_id: item.catalogRef,\n quantity: String(item.quantity),\n })),\n },\n },\n });\n const order = orderResponse.order as {\n id: string;\n total_money: { amount: number; currency: string };\n };\n\n const paymentResponse = await squareFetch(config, \"/v2/payments\", {\n method: \"POST\",\n body: {\n idempotency_key: crypto.randomUUID(),\n source_id: request.paymentSourceToken,\n amount_money: order.total_money,\n order_id: order.id,\n location_id: primaryLocation(config.locationId),\n },\n });\n const payment = paymentResponse.payment as {\n id: string;\n status: string;\n amount_money: { amount: number; currency: string };\n };\n\n return {\n providerOrderRef: order.id,\n providerPaymentRef: payment.id,\n status: mapSquarePaymentStatus(payment.status),\n amount: {\n amount: payment.amount_money.amount,\n currency: payment.amount_money.currency,\n },\n raw: paymentResponse as Record<string, unknown>,\n };\n}\n\nasync function verifyWebhookSignature(args: {\n rawBody: string;\n headers: Headers;\n secret: string;\n notificationUrl?: string;\n}): Promise<boolean> {\n // Square signs `notificationUrl + rawBody` (in that order) with the\n // webhook signature key, base64-encoded, checked against the\n // `x-square-hmacsha256-signature` header — without a notification URL\n // there's nothing correct to verify against, so this fails closed.\n if (!args.notificationUrl) return false;\n const signatureHeader = args.headers.get(\"x-square-hmacsha256-signature\");\n if (!signatureHeader) return false;\n const expected = await hmacSha256Base64(\n args.notificationUrl + args.rawBody,\n args.secret,\n );\n return timingSafeEqual(expected, signatureHeader);\n}\n\ninterface SquareWebhookPayload {\n event_id: string;\n type: string;\n data: {\n object: Record<string, unknown>;\n };\n}\n\nfunction parseWebhookEvent(rawBody: string) {\n const payload = JSON.parse(rawBody) as SquareWebhookPayload;\n const eventId = payload.event_id;\n\n if (payload.type === \"payment.updated\") {\n const payment = payload.data.object.payment as\n | { id: string; status: string }\n | undefined;\n if (payment) {\n return {\n eventId,\n event: {\n kind: \"payment.updated\" as const,\n providerPaymentRef: payment.id,\n status:\n mapSquarePaymentStatus(payment.status) === \"succeeded\"\n ? (\"succeeded\" as const)\n : (\"failed\" as const),\n },\n };\n }\n }\n\n if (payload.type === \"order.updated\") {\n const orderUpdated = payload.data.object.order_updated as\n | { order_id: string; state: string }\n | undefined;\n if (orderUpdated) {\n const status =\n orderUpdated.state === \"COMPLETED\"\n ? (\"paid\" as const)\n : orderUpdated.state === \"CANCELED\"\n ? (\"canceled\" as const)\n : (\"failed\" as const);\n return {\n eventId,\n event: {\n kind: \"order.updated\" as const,\n providerOrderRef: orderUpdated.order_id,\n status,\n },\n };\n }\n }\n\n if (payload.type === \"subscription.updated\") {\n const subscription = payload.data.object.subscription as\n | { id: string; status: string }\n | undefined;\n if (subscription) {\n return {\n eventId,\n event: {\n kind: \"subscription.updated\" as const,\n providerSubscriptionRef: subscription.id,\n status: subscription.status,\n },\n };\n }\n }\n\n return {\n eventId,\n event: { kind: \"unhandled\" as const, rawType: payload.type },\n };\n}\n\n/**\n * Creates a `PaymentProvider` backed by Square's REST API — raw `fetch()`\n * + `crypto.subtle`, no Square Node SDK. Implements the required\n * `checkCatalogPrices`/`findOrCreateCustomer`/`checkout`/\n * `verifyWebhookSignature`/`parseWebhookEvent`; omits `catalogSync` and\n * `subscriptions` in this first cut (Square's loyalty/recurring-order\n * model doesn't map cleanly onto either optional capability without\n * further design — see the package README for the gap).\n */\nexport function createSquarePaymentProvider(\n config: SquareProviderConfig,\n): PaymentProvider {\n return {\n name: \"square\",\n checkCatalogPrices: (refs) => checkCatalogPrices(config, refs),\n findOrCreateCustomer: (email, idempotencyKey) =>\n findOrCreateCustomer(config, email, idempotencyKey),\n checkout: (request) => checkout(config, request),\n verifyWebhookSignature,\n parseWebhookEvent,\n };\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PaymentProvider } from "@thebes/cadmea-plugin-ecommerce";
|
|
2
|
+
|
|
3
|
+
//#region src/provider.d.ts
|
|
4
|
+
interface SquareProviderConfig {
|
|
5
|
+
accessToken: string;
|
|
6
|
+
/** First entry is used as the primary location for orders/payments; all entries are queried for inventory. */
|
|
7
|
+
locationId: string | string[];
|
|
8
|
+
environment?: "sandbox" | "production";
|
|
9
|
+
/** Square's API version header. Default: a fixed, tested version — bump deliberately, not implicitly. */
|
|
10
|
+
apiVersion?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a `PaymentProvider` backed by Square's REST API — raw `fetch()`
|
|
14
|
+
* + `crypto.subtle`, no Square Node SDK. Implements the required
|
|
15
|
+
* `checkCatalogPrices`/`findOrCreateCustomer`/`checkout`/
|
|
16
|
+
* `verifyWebhookSignature`/`parseWebhookEvent`; omits `catalogSync` and
|
|
17
|
+
* `subscriptions` in this first cut (Square's loyalty/recurring-order
|
|
18
|
+
* model doesn't map cleanly onto either optional capability without
|
|
19
|
+
* further design — see the package README for the gap).
|
|
20
|
+
*/
|
|
21
|
+
declare function createSquarePaymentProvider(config: SquareProviderConfig): PaymentProvider;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { SquareProviderConfig, createSquarePaymentProvider };
|
|
24
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/provider.ts"],"mappings":";;;UAkBiB,oBAAA;EACf,WAAA;EADe;EAGf,UAAA;EACA,WAAA;EAJmC;EAMnC,UAAA;AAAA;;;;AAAU;AAuVZ;;;;;iBAAgB,2BAAA,CACd,MAAA,EAAQ,oBAAA,GACP,eAAe"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PaymentProvider } from "@thebes/cadmea-plugin-ecommerce";
|
|
2
|
+
|
|
3
|
+
//#region src/provider.d.ts
|
|
4
|
+
interface SquareProviderConfig {
|
|
5
|
+
accessToken: string;
|
|
6
|
+
/** First entry is used as the primary location for orders/payments; all entries are queried for inventory. */
|
|
7
|
+
locationId: string | string[];
|
|
8
|
+
environment?: "sandbox" | "production";
|
|
9
|
+
/** Square's API version header. Default: a fixed, tested version — bump deliberately, not implicitly. */
|
|
10
|
+
apiVersion?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a `PaymentProvider` backed by Square's REST API — raw `fetch()`
|
|
14
|
+
* + `crypto.subtle`, no Square Node SDK. Implements the required
|
|
15
|
+
* `checkCatalogPrices`/`findOrCreateCustomer`/`checkout`/
|
|
16
|
+
* `verifyWebhookSignature`/`parseWebhookEvent`; omits `catalogSync` and
|
|
17
|
+
* `subscriptions` in this first cut (Square's loyalty/recurring-order
|
|
18
|
+
* model doesn't map cleanly onto either optional capability without
|
|
19
|
+
* further design — see the package README for the gap).
|
|
20
|
+
*/
|
|
21
|
+
declare function createSquarePaymentProvider(config: SquareProviderConfig): PaymentProvider;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { SquareProviderConfig, createSquarePaymentProvider };
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/provider.ts"],"mappings":";;;UAkBiB,oBAAA;EACf,WAAA;EADe;EAGf,UAAA;EACA,WAAA;EAJmC;EAMnC,UAAA;AAAA;;;;AAAU;AAuVZ;;;;;iBAAgB,2BAAA,CACd,MAAA,EAAQ,oBAAA,GACP,eAAe"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
//#region src/provider.ts
|
|
2
|
+
const DEFAULT_API_VERSION = "2025-01-23";
|
|
3
|
+
function baseUrl(environment) {
|
|
4
|
+
return environment === "production" ? "https://connect.squareup.com" : "https://connect.squareupsandbox.com";
|
|
5
|
+
}
|
|
6
|
+
function primaryLocation(locationId) {
|
|
7
|
+
return Array.isArray(locationId) ? locationId[0] : locationId;
|
|
8
|
+
}
|
|
9
|
+
function allLocations(locationId) {
|
|
10
|
+
return Array.isArray(locationId) ? locationId : [locationId];
|
|
11
|
+
}
|
|
12
|
+
var SquareApiError = class extends Error {
|
|
13
|
+
status;
|
|
14
|
+
body;
|
|
15
|
+
constructor(message, status, body) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.body = body;
|
|
19
|
+
this.name = "SquareApiError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
async function squareFetch(config, path, init) {
|
|
23
|
+
const response = await fetch(`${baseUrl(config.environment)}${path}`, {
|
|
24
|
+
method: init.method,
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${config.accessToken}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
"Square-Version": config.apiVersion ?? DEFAULT_API_VERSION
|
|
29
|
+
},
|
|
30
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
31
|
+
});
|
|
32
|
+
const text = await response.text();
|
|
33
|
+
const parsed = text ? JSON.parse(text) : {};
|
|
34
|
+
if (!response.ok) throw new SquareApiError(`Square API request to "${path}" failed with status ${response.status}`, response.status, parsed);
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
async function hmacSha256Base64(message, secret) {
|
|
38
|
+
const key = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), {
|
|
39
|
+
name: "HMAC",
|
|
40
|
+
hash: "SHA-256"
|
|
41
|
+
}, false, ["sign"]);
|
|
42
|
+
const signature = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(message));
|
|
43
|
+
return btoa(String.fromCharCode(...new Uint8Array(signature)));
|
|
44
|
+
}
|
|
45
|
+
function timingSafeEqual(a, b) {
|
|
46
|
+
if (a.length !== b.length) return false;
|
|
47
|
+
let mismatch = 0;
|
|
48
|
+
for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
49
|
+
return mismatch === 0;
|
|
50
|
+
}
|
|
51
|
+
async function checkCatalogPrices(config, refs) {
|
|
52
|
+
if (refs.length === 0) return [];
|
|
53
|
+
const objects = (await squareFetch(config, "/v2/catalog/batch-retrieve", {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: { object_ids: refs }
|
|
56
|
+
})).objects ?? [];
|
|
57
|
+
const priceByRef = new Map(objects.map((object) => [object.id, object.item_variation_data?.price_money]));
|
|
58
|
+
const counts = (await squareFetch(config, "/v2/inventory/batch-retrieve-counts", {
|
|
59
|
+
method: "POST",
|
|
60
|
+
body: {
|
|
61
|
+
catalog_object_ids: refs,
|
|
62
|
+
location_ids: allLocations(config.locationId)
|
|
63
|
+
}
|
|
64
|
+
})).counts ?? [];
|
|
65
|
+
const quantityByRef = /* @__PURE__ */ new Map();
|
|
66
|
+
for (const count of counts) {
|
|
67
|
+
const existing = quantityByRef.get(count.catalog_object_id) ?? 0;
|
|
68
|
+
quantityByRef.set(count.catalog_object_id, existing + Number.parseFloat(count.quantity));
|
|
69
|
+
}
|
|
70
|
+
return refs.map((catalogRef) => {
|
|
71
|
+
const price = priceByRef.get(catalogRef);
|
|
72
|
+
return {
|
|
73
|
+
catalogRef,
|
|
74
|
+
serverUnitPrice: {
|
|
75
|
+
amount: price?.amount ?? 0,
|
|
76
|
+
currency: price?.currency ?? "USD"
|
|
77
|
+
},
|
|
78
|
+
availableQuantity: quantityByRef.get(catalogRef)
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function findOrCreateCustomer(config, email, idempotencyKey) {
|
|
83
|
+
const existing = (await squareFetch(config, "/v2/customers/search", {
|
|
84
|
+
method: "POST",
|
|
85
|
+
body: { query: { filter: { email_address: { exact: email } } } }
|
|
86
|
+
})).customers?.[0];
|
|
87
|
+
if (existing) return existing.id;
|
|
88
|
+
return (await squareFetch(config, "/v2/customers", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: {
|
|
91
|
+
idempotency_key: idempotencyKey,
|
|
92
|
+
email_address: email
|
|
93
|
+
}
|
|
94
|
+
})).customer.id;
|
|
95
|
+
}
|
|
96
|
+
function mapSquarePaymentStatus(status) {
|
|
97
|
+
if (status === "COMPLETED" || status === "APPROVED") return "succeeded";
|
|
98
|
+
if (status === "PENDING") return "requires_action";
|
|
99
|
+
return "failed";
|
|
100
|
+
}
|
|
101
|
+
async function checkout(config, request) {
|
|
102
|
+
const order = (await squareFetch(config, "/v2/orders", {
|
|
103
|
+
method: "POST",
|
|
104
|
+
body: {
|
|
105
|
+
idempotency_key: request.idempotencyKey,
|
|
106
|
+
order: {
|
|
107
|
+
location_id: primaryLocation(config.locationId),
|
|
108
|
+
line_items: request.lineItems.map((item) => ({
|
|
109
|
+
catalog_object_id: item.catalogRef,
|
|
110
|
+
quantity: String(item.quantity)
|
|
111
|
+
}))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})).order;
|
|
115
|
+
const paymentResponse = await squareFetch(config, "/v2/payments", {
|
|
116
|
+
method: "POST",
|
|
117
|
+
body: {
|
|
118
|
+
idempotency_key: crypto.randomUUID(),
|
|
119
|
+
source_id: request.paymentSourceToken,
|
|
120
|
+
amount_money: order.total_money,
|
|
121
|
+
order_id: order.id,
|
|
122
|
+
location_id: primaryLocation(config.locationId)
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const payment = paymentResponse.payment;
|
|
126
|
+
return {
|
|
127
|
+
providerOrderRef: order.id,
|
|
128
|
+
providerPaymentRef: payment.id,
|
|
129
|
+
status: mapSquarePaymentStatus(payment.status),
|
|
130
|
+
amount: {
|
|
131
|
+
amount: payment.amount_money.amount,
|
|
132
|
+
currency: payment.amount_money.currency
|
|
133
|
+
},
|
|
134
|
+
raw: paymentResponse
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async function verifyWebhookSignature(args) {
|
|
138
|
+
if (!args.notificationUrl) return false;
|
|
139
|
+
const signatureHeader = args.headers.get("x-square-hmacsha256-signature");
|
|
140
|
+
if (!signatureHeader) return false;
|
|
141
|
+
return timingSafeEqual(await hmacSha256Base64(args.notificationUrl + args.rawBody, args.secret), signatureHeader);
|
|
142
|
+
}
|
|
143
|
+
function parseWebhookEvent(rawBody) {
|
|
144
|
+
const payload = JSON.parse(rawBody);
|
|
145
|
+
const eventId = payload.event_id;
|
|
146
|
+
if (payload.type === "payment.updated") {
|
|
147
|
+
const payment = payload.data.object.payment;
|
|
148
|
+
if (payment) return {
|
|
149
|
+
eventId,
|
|
150
|
+
event: {
|
|
151
|
+
kind: "payment.updated",
|
|
152
|
+
providerPaymentRef: payment.id,
|
|
153
|
+
status: mapSquarePaymentStatus(payment.status) === "succeeded" ? "succeeded" : "failed"
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (payload.type === "order.updated") {
|
|
158
|
+
const orderUpdated = payload.data.object.order_updated;
|
|
159
|
+
if (orderUpdated) {
|
|
160
|
+
const status = orderUpdated.state === "COMPLETED" ? "paid" : orderUpdated.state === "CANCELED" ? "canceled" : "failed";
|
|
161
|
+
return {
|
|
162
|
+
eventId,
|
|
163
|
+
event: {
|
|
164
|
+
kind: "order.updated",
|
|
165
|
+
providerOrderRef: orderUpdated.order_id,
|
|
166
|
+
status
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (payload.type === "subscription.updated") {
|
|
172
|
+
const subscription = payload.data.object.subscription;
|
|
173
|
+
if (subscription) return {
|
|
174
|
+
eventId,
|
|
175
|
+
event: {
|
|
176
|
+
kind: "subscription.updated",
|
|
177
|
+
providerSubscriptionRef: subscription.id,
|
|
178
|
+
status: subscription.status
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
eventId,
|
|
184
|
+
event: {
|
|
185
|
+
kind: "unhandled",
|
|
186
|
+
rawType: payload.type
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Creates a `PaymentProvider` backed by Square's REST API — raw `fetch()`
|
|
192
|
+
* + `crypto.subtle`, no Square Node SDK. Implements the required
|
|
193
|
+
* `checkCatalogPrices`/`findOrCreateCustomer`/`checkout`/
|
|
194
|
+
* `verifyWebhookSignature`/`parseWebhookEvent`; omits `catalogSync` and
|
|
195
|
+
* `subscriptions` in this first cut (Square's loyalty/recurring-order
|
|
196
|
+
* model doesn't map cleanly onto either optional capability without
|
|
197
|
+
* further design — see the package README for the gap).
|
|
198
|
+
*/
|
|
199
|
+
function createSquarePaymentProvider(config) {
|
|
200
|
+
return {
|
|
201
|
+
name: "square",
|
|
202
|
+
checkCatalogPrices: (refs) => checkCatalogPrices(config, refs),
|
|
203
|
+
findOrCreateCustomer: (email, idempotencyKey) => findOrCreateCustomer(config, email, idempotencyKey),
|
|
204
|
+
checkout: (request) => checkout(config, request),
|
|
205
|
+
verifyWebhookSignature,
|
|
206
|
+
parseWebhookEvent
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
export { createSquarePaymentProvider };
|
|
211
|
+
|
|
212
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/provider.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// MIT licensed. See LICENSE in the repo root.\n//\n// Square's REST API directly via fetch() — never the `square` npm SDK\n// (Node-targeted, BigInt-typed monetary amounts the raw REST API doesn't\n// actually return — its JSON responses use plain numbers; BigInt is purely\n// an artifact of the SDK's own typed wrapper). Webhook signature\n// verification uses crypto.subtle, mirroring the exact HMAC idiom already\n// in @thebes/cadmus/cms's webhooks.ts (compute-and-compare instead of\n// sign-and-attach).\n\nimport type {\n CatalogPriceCheck,\n CheckoutRequest,\n CheckoutResult,\n PaymentProvider,\n} from \"@thebes/cadmea-plugin-ecommerce\";\n\nexport interface SquareProviderConfig {\n accessToken: string;\n /** First entry is used as the primary location for orders/payments; all entries are queried for inventory. */\n locationId: string | string[];\n environment?: \"sandbox\" | \"production\";\n /** Square's API version header. Default: a fixed, tested version — bump deliberately, not implicitly. */\n apiVersion?: string;\n}\n\nconst DEFAULT_API_VERSION = \"2025-01-23\";\n\nfunction baseUrl(environment: SquareProviderConfig[\"environment\"]): string {\n return environment === \"production\"\n ? \"https://connect.squareup.com\"\n : \"https://connect.squareupsandbox.com\";\n}\n\nfunction primaryLocation(\n locationId: SquareProviderConfig[\"locationId\"],\n): string {\n return Array.isArray(locationId) ? locationId[0] : locationId;\n}\n\nfunction allLocations(\n locationId: SquareProviderConfig[\"locationId\"],\n): string[] {\n return Array.isArray(locationId) ? locationId : [locationId];\n}\n\nclass SquareApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message);\n this.name = \"SquareApiError\";\n }\n}\n\nasync function squareFetch(\n config: SquareProviderConfig,\n path: string,\n init: { method: string; body?: unknown },\n): Promise<Record<string, unknown>> {\n const response = await fetch(`${baseUrl(config.environment)}${path}`, {\n method: init.method,\n headers: {\n Authorization: `Bearer ${config.accessToken}`,\n \"Content-Type\": \"application/json\",\n \"Square-Version\": config.apiVersion ?? DEFAULT_API_VERSION,\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n });\n const text = await response.text();\n const parsed = text ? JSON.parse(text) : {};\n if (!response.ok) {\n throw new SquareApiError(\n `Square API request to \"${path}\" failed with status ${response.status}`,\n response.status,\n parsed,\n );\n }\n return parsed;\n}\n\nasync function hmacSha256Base64(\n message: string,\n secret: string,\n): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(message),\n );\n return btoa(String.fromCharCode(...new Uint8Array(signature)));\n}\n\n// Constant-time string comparison — a plain `===` on a signature leaks,\n// via early-exit timing, how many leading bytes matched, which is enough\n// to forge a valid HMAC byte-by-byte. Mirrors the Stripe provider's helper.\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let mismatch = 0;\n for (let i = 0; i < a.length; i++) {\n mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n return mismatch === 0;\n}\n\ninterface SquareCatalogObject {\n id: string;\n type: string;\n item_variation_data?: {\n price_money?: { amount: number; currency: string };\n };\n}\n\ninterface SquareInventoryCount {\n catalog_object_id: string;\n quantity: string;\n}\n\nasync function checkCatalogPrices(\n config: SquareProviderConfig,\n refs: string[],\n): Promise<CatalogPriceCheck[]> {\n if (refs.length === 0) return [];\n\n const catalogResponse = await squareFetch(\n config,\n \"/v2/catalog/batch-retrieve\",\n {\n method: \"POST\",\n body: { object_ids: refs },\n },\n );\n const objects = (catalogResponse.objects ?? []) as SquareCatalogObject[];\n const priceByRef = new Map(\n objects.map((object) => [\n object.id,\n object.item_variation_data?.price_money,\n ]),\n );\n\n const inventoryResponse = await squareFetch(\n config,\n \"/v2/inventory/batch-retrieve-counts\",\n {\n method: \"POST\",\n body: {\n catalog_object_ids: refs,\n location_ids: allLocations(config.locationId),\n },\n },\n );\n const counts = (inventoryResponse.counts ?? []) as SquareInventoryCount[];\n const quantityByRef = new Map<string, number>();\n for (const count of counts) {\n const existing = quantityByRef.get(count.catalog_object_id) ?? 0;\n quantityByRef.set(\n count.catalog_object_id,\n existing + Number.parseFloat(count.quantity),\n );\n }\n\n return refs.map((catalogRef) => {\n const price = priceByRef.get(catalogRef);\n return {\n catalogRef,\n serverUnitPrice: {\n amount: price?.amount ?? 0,\n currency: price?.currency ?? \"USD\",\n },\n availableQuantity: quantityByRef.get(catalogRef),\n };\n });\n}\n\nasync function findOrCreateCustomer(\n config: SquareProviderConfig,\n email: string,\n idempotencyKey: string,\n): Promise<string> {\n const searchResponse = await squareFetch(config, \"/v2/customers/search\", {\n method: \"POST\",\n body: { query: { filter: { email_address: { exact: email } } } },\n });\n const existing = (\n searchResponse.customers as Array<{ id: string }> | undefined\n )?.[0];\n if (existing) return existing.id;\n\n const createResponse = await squareFetch(config, \"/v2/customers\", {\n method: \"POST\",\n body: { idempotency_key: idempotencyKey, email_address: email },\n });\n return (createResponse.customer as { id: string }).id;\n}\n\nfunction mapSquarePaymentStatus(\n status: string | undefined,\n): CheckoutResult[\"status\"] {\n if (status === \"COMPLETED\" || status === \"APPROVED\") return \"succeeded\";\n if (status === \"PENDING\") return \"requires_action\";\n return \"failed\";\n}\n\nasync function checkout(\n config: SquareProviderConfig,\n request: CheckoutRequest,\n): Promise<CheckoutResult> {\n const orderResponse = await squareFetch(config, \"/v2/orders\", {\n method: \"POST\",\n body: {\n idempotency_key: request.idempotencyKey,\n order: {\n location_id: primaryLocation(config.locationId),\n line_items: request.lineItems.map((item) => ({\n catalog_object_id: item.catalogRef,\n quantity: String(item.quantity),\n })),\n },\n },\n });\n const order = orderResponse.order as {\n id: string;\n total_money: { amount: number; currency: string };\n };\n\n const paymentResponse = await squareFetch(config, \"/v2/payments\", {\n method: \"POST\",\n body: {\n idempotency_key: crypto.randomUUID(),\n source_id: request.paymentSourceToken,\n amount_money: order.total_money,\n order_id: order.id,\n location_id: primaryLocation(config.locationId),\n },\n });\n const payment = paymentResponse.payment as {\n id: string;\n status: string;\n amount_money: { amount: number; currency: string };\n };\n\n return {\n providerOrderRef: order.id,\n providerPaymentRef: payment.id,\n status: mapSquarePaymentStatus(payment.status),\n amount: {\n amount: payment.amount_money.amount,\n currency: payment.amount_money.currency,\n },\n raw: paymentResponse as Record<string, unknown>,\n };\n}\n\nasync function verifyWebhookSignature(args: {\n rawBody: string;\n headers: Headers;\n secret: string;\n notificationUrl?: string;\n}): Promise<boolean> {\n // Square signs `notificationUrl + rawBody` (in that order) with the\n // webhook signature key, base64-encoded, checked against the\n // `x-square-hmacsha256-signature` header — without a notification URL\n // there's nothing correct to verify against, so this fails closed.\n if (!args.notificationUrl) return false;\n const signatureHeader = args.headers.get(\"x-square-hmacsha256-signature\");\n if (!signatureHeader) return false;\n const expected = await hmacSha256Base64(\n args.notificationUrl + args.rawBody,\n args.secret,\n );\n return timingSafeEqual(expected, signatureHeader);\n}\n\ninterface SquareWebhookPayload {\n event_id: string;\n type: string;\n data: {\n object: Record<string, unknown>;\n };\n}\n\nfunction parseWebhookEvent(rawBody: string) {\n const payload = JSON.parse(rawBody) as SquareWebhookPayload;\n const eventId = payload.event_id;\n\n if (payload.type === \"payment.updated\") {\n const payment = payload.data.object.payment as\n | { id: string; status: string }\n | undefined;\n if (payment) {\n return {\n eventId,\n event: {\n kind: \"payment.updated\" as const,\n providerPaymentRef: payment.id,\n status:\n mapSquarePaymentStatus(payment.status) === \"succeeded\"\n ? (\"succeeded\" as const)\n : (\"failed\" as const),\n },\n };\n }\n }\n\n if (payload.type === \"order.updated\") {\n const orderUpdated = payload.data.object.order_updated as\n | { order_id: string; state: string }\n | undefined;\n if (orderUpdated) {\n const status =\n orderUpdated.state === \"COMPLETED\"\n ? (\"paid\" as const)\n : orderUpdated.state === \"CANCELED\"\n ? (\"canceled\" as const)\n : (\"failed\" as const);\n return {\n eventId,\n event: {\n kind: \"order.updated\" as const,\n providerOrderRef: orderUpdated.order_id,\n status,\n },\n };\n }\n }\n\n if (payload.type === \"subscription.updated\") {\n const subscription = payload.data.object.subscription as\n | { id: string; status: string }\n | undefined;\n if (subscription) {\n return {\n eventId,\n event: {\n kind: \"subscription.updated\" as const,\n providerSubscriptionRef: subscription.id,\n status: subscription.status,\n },\n };\n }\n }\n\n return {\n eventId,\n event: { kind: \"unhandled\" as const, rawType: payload.type },\n };\n}\n\n/**\n * Creates a `PaymentProvider` backed by Square's REST API — raw `fetch()`\n * + `crypto.subtle`, no Square Node SDK. Implements the required\n * `checkCatalogPrices`/`findOrCreateCustomer`/`checkout`/\n * `verifyWebhookSignature`/`parseWebhookEvent`; omits `catalogSync` and\n * `subscriptions` in this first cut (Square's loyalty/recurring-order\n * model doesn't map cleanly onto either optional capability without\n * further design — see the package README for the gap).\n */\nexport function createSquarePaymentProvider(\n config: SquareProviderConfig,\n): PaymentProvider {\n return {\n name: \"square\",\n checkCatalogPrices: (refs) => checkCatalogPrices(config, refs),\n findOrCreateCustomer: (email, idempotencyKey) =>\n findOrCreateCustomer(config, email, idempotencyKey),\n checkout: (request) => checkout(config, request),\n verifyWebhookSignature,\n parseWebhookEvent,\n };\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thebes/cadmea-plugin-ecommerce-square",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Square PaymentProvider for @thebes/cadmea-plugin-ecommerce — raw fetch() + crypto.subtle, no Square Node SDK",
|
|
5
|
+
"author": "BowenLabs <hello@bowenlabs.io>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/bowenlabs/project-thebes",
|
|
10
|
+
"directory": "packages/cadmea-plugin-ecommerce-square"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/bowenlabs/project-thebes/tree/main/packages/cadmea-plugin-ecommerce-square#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/bowenlabs/project-thebes/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"cadmea",
|
|
18
|
+
"cadmus",
|
|
19
|
+
"cms",
|
|
20
|
+
"ecommerce",
|
|
21
|
+
"square",
|
|
22
|
+
"payments",
|
|
23
|
+
"cloudflare"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./client": {
|
|
32
|
+
"types": "./dist/client.d.ts",
|
|
33
|
+
"default": "./dist/client.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@thebes/cadmea-plugin-ecommerce": "^1.0.0",
|
|
38
|
+
"@thebes/cadmus": "^0.2.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "latest",
|
|
42
|
+
"vite-plus": "latest",
|
|
43
|
+
"vitest": "latest",
|
|
44
|
+
"@thebes/cadmus": "^0.2.1",
|
|
45
|
+
"@thebes/cadmea-plugin-ecommerce": "^1.0.0"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "vp pack",
|
|
54
|
+
"dev": "vp pack --watch",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest"
|
|
57
|
+
}
|
|
58
|
+
}
|