bitsnap-react 1.14.0 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/AGENTS.md +246 -0
  2. package/README.md +99 -51
  3. package/dist/CartProvider-2RNAKU3U.js +2 -0
  4. package/dist/CartProvider-2RNAKU3U.js.map +1 -0
  5. package/dist/{CartProvider-GLUQHAG3.mjs → CartProvider-OSGG573S.mjs} +2 -2
  6. package/dist/analytics.js +1 -1
  7. package/dist/analytics.js.map +1 -1
  8. package/dist/analytics.mjs +1 -1
  9. package/dist/catalog.js +1 -1
  10. package/dist/catalog.js.map +1 -1
  11. package/dist/catalog.mjs +1 -1
  12. package/dist/{chunk-R2M6W4LD.js → chunk-2OOQPXCQ.js} +3 -3
  13. package/dist/chunk-2OOQPXCQ.js.map +1 -0
  14. package/dist/{chunk-JXVAT2ZE.js → chunk-4MU2YAQT.js} +2 -2
  15. package/dist/chunk-4MU2YAQT.js.map +1 -0
  16. package/dist/{chunk-CYLZ5GYW.mjs → chunk-4TZAGNEU.mjs} +2 -2
  17. package/dist/{chunk-5DDLYVBG.js → chunk-5DBLZ3VL.js} +2 -2
  18. package/dist/chunk-5DBLZ3VL.js.map +1 -0
  19. package/dist/{chunk-U5SAO2SH.mjs → chunk-7WJSK6SJ.mjs} +2 -2
  20. package/dist/{chunk-U5SAO2SH.mjs.map → chunk-7WJSK6SJ.mjs.map} +1 -1
  21. package/dist/{chunk-AWG5OAK7.js → chunk-EJRI366W.js} +2 -2
  22. package/dist/chunk-EJRI366W.js.map +1 -0
  23. package/dist/{chunk-SKFE6TUP.mjs → chunk-FIWADPQX.mjs} +2 -2
  24. package/dist/{chunk-FLDI3BPY.js → chunk-GTOZ5BZX.js} +2 -2
  25. package/dist/chunk-GTOZ5BZX.js.map +1 -0
  26. package/dist/chunk-JSDGZ4NY.js.map +1 -1
  27. package/dist/{chunk-3OEKRPWV.mjs → chunk-JZBU5SZJ.mjs} +2 -2
  28. package/dist/{chunk-3OEKRPWV.mjs.map → chunk-JZBU5SZJ.mjs.map} +1 -1
  29. package/dist/{chunk-PLS5RR27.js → chunk-KKOIDDOW.js} +2 -2
  30. package/dist/chunk-KKOIDDOW.js.map +1 -0
  31. package/dist/chunk-KOYBZORL.js.map +1 -1
  32. package/dist/{chunk-MVOJMCZG.mjs → chunk-L7LZG6MR.mjs} +2 -2
  33. package/dist/{chunk-VY7RNSFP.mjs → chunk-ONTCJSSD.mjs} +2 -2
  34. package/dist/{chunk-3N2VK426.js → chunk-PVJ2I46C.js} +2 -2
  35. package/dist/chunk-PVJ2I46C.js.map +1 -0
  36. package/dist/{chunk-7SYNGA3R.js → chunk-QQDZIVWY.js} +2 -2
  37. package/dist/chunk-QQDZIVWY.js.map +1 -0
  38. package/dist/{chunk-GKFET34B.mjs → chunk-TBYY3RL2.mjs} +2 -2
  39. package/dist/chunk-W3RGQYPD.js.map +1 -1
  40. package/dist/{chunk-VYYUTJZB.mjs → chunk-WJGC6EHC.mjs} +2 -2
  41. package/dist/consent.js.map +1 -1
  42. package/dist/course.js +1 -1
  43. package/dist/course.js.map +1 -1
  44. package/dist/course.mjs +1 -1
  45. package/dist/embed/analytics.js +1 -1
  46. package/dist/errors.js.map +1 -1
  47. package/dist/index.d.mts +15 -0
  48. package/dist/index.d.ts +15 -0
  49. package/dist/index.js +1 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/index.mjs +1 -1
  52. package/dist/internal.js.map +1 -1
  53. package/dist/methods-4WVTFI3Q.mjs +2 -0
  54. package/dist/methods-W5JTLIIX.js +2 -0
  55. package/dist/methods-W5JTLIIX.js.map +1 -0
  56. package/dist/models.js.map +1 -1
  57. package/dist/returns.js +1 -1
  58. package/dist/returns.js.map +1 -1
  59. package/dist/returns.mjs +1 -1
  60. package/package.json +3 -2
  61. package/dist/CartProvider-6D5WE2HA.js +0 -2
  62. package/dist/CartProvider-6D5WE2HA.js.map +0 -1
  63. package/dist/chunk-3N2VK426.js.map +0 -1
  64. package/dist/chunk-5DDLYVBG.js.map +0 -1
  65. package/dist/chunk-7SYNGA3R.js.map +0 -1
  66. package/dist/chunk-AWG5OAK7.js.map +0 -1
  67. package/dist/chunk-FLDI3BPY.js.map +0 -1
  68. package/dist/chunk-JXVAT2ZE.js.map +0 -1
  69. package/dist/chunk-PLS5RR27.js.map +0 -1
  70. package/dist/chunk-R2M6W4LD.js.map +0 -1
  71. package/dist/methods-N35A2OGE.mjs +0 -2
  72. package/dist/methods-WSICR2JH.js +0 -2
  73. package/dist/methods-WSICR2JH.js.map +0 -1
  74. /package/dist/{CartProvider-GLUQHAG3.mjs.map → CartProvider-OSGG573S.mjs.map} +0 -0
  75. /package/dist/{chunk-CYLZ5GYW.mjs.map → chunk-4TZAGNEU.mjs.map} +0 -0
  76. /package/dist/{chunk-SKFE6TUP.mjs.map → chunk-FIWADPQX.mjs.map} +0 -0
  77. /package/dist/{chunk-MVOJMCZG.mjs.map → chunk-L7LZG6MR.mjs.map} +0 -0
  78. /package/dist/{chunk-VY7RNSFP.mjs.map → chunk-ONTCJSSD.mjs.map} +0 -0
  79. /package/dist/{chunk-GKFET34B.mjs.map → chunk-TBYY3RL2.mjs.map} +0 -0
  80. /package/dist/{chunk-VYYUTJZB.mjs.map → chunk-WJGC6EHC.mjs.map} +0 -0
  81. /package/dist/{methods-N35A2OGE.mjs.map → methods-4WVTFI3Q.mjs.map} +0 -0
package/AGENTS.md ADDED
@@ -0,0 +1,246 @@
1
+ # Integrate bitsnap-react
2
+
3
+ `bitsnap-react` provides checkout, Apple Pay, returns and the customer panel.
4
+ Use a Next.js client component or an Astro React island. Use `bitsnap-js` on the
5
+ server for products, secret checkout operations and webhooks.
6
+
7
+ ## Install and configure
8
+
9
+ ```sh
10
+ npm install bitsnap-js bitsnap-react
11
+ # Astro also needs @astrojs/react: npx astro add react
12
+ ```
13
+
14
+ Use these application env names; the SDK does not load them:
15
+
16
+ - `BITSNAP_PROJECT_ID`: public. Pass it from the server as `projectID`.
17
+ If using a public env var, only the project ID may use `NEXT_PUBLIC_`/`PUBLIC_`.
18
+ - `BITSNAP_API_KEY`: secret, server-only. Never `NEXT_PUBLIC_*`/`PUBLIC_*`.
19
+ - `BITSNAP_WEBHOOK_SECRET`: separate webhook secret, also server-only.
20
+
21
+ Store secrets in ignored `.env.local` (Next.js), `.env` (Astro) or deployment
22
+ server variables. Normal checkout needs no API key. Never pass keys in props,
23
+ HTML, browser checkout calls or agent chat.
24
+
25
+ ## Minimal checkout
26
+
27
+ ```tsx
28
+ "use client";
29
+
30
+ import { Bitsnap, BitsnapCheckout, setProjectID } from "bitsnap-react";
31
+ import "bitsnap-react/dist/index.css";
32
+
33
+ type Product = { id: string; name: string };
34
+
35
+ export default function Checkout({ projectID, products }: {
36
+ projectID: string;
37
+ products: Product[];
38
+ }) {
39
+ async function buy(id: string) {
40
+ setProjectID(projectID);
41
+ const error = await Bitsnap.addProductToCart(id, 1);
42
+ if (error) {
43
+ alert("Could not add this product. Try again.");
44
+ return;
45
+ }
46
+ Bitsnap.showCart();
47
+ }
48
+
49
+ return (
50
+ <>
51
+ {products.map((product) => (
52
+ <button key={product.id} onClick={() => buy(product.id)}>
53
+ Buy {product.name}
54
+ </button>
55
+ ))}
56
+ <BitsnapCheckout projectID={projectID} locale="pl" />
57
+ </>
58
+ );
59
+ }
60
+ ```
61
+
62
+ Fetch products with `BitsnapBackend` on the server and pass only public fields.
63
+ Import the exported CSS. Configure the project before calling cart methods.
64
+ For Astro, omit `"use client"`, and mount with `client:load` or `client:visible`.
65
+
66
+ ## Other components
67
+
68
+ - `ApplePayButton` requires `items` with `id`, `name`, `price` (minor units) and
69
+ `quantity`. It has no `projectID` prop. Call `setProjectID(projectID)` on the
70
+ client before mounting. Use an effect and a ready flag for standalone buttons.
71
+ Configure Apple Pay and test on an eligible device over HTTPS.
72
+ - `BitsnapReturns` takes `projectID` and optional `locale`, `appearance`, `texts`.
73
+ - For the panel, configure `setProjectID` and `setLoginURL("/panel/login")` on
74
+ the client before mounting `<PanelProvider><Panel /></PanelProvider>`.
75
+ Mount `<PanelProvider><PanelLogin /></PanelProvider>` at that login URL.
76
+
77
+ ## Avoid these mistakes
78
+
79
+ - Neither checkout UI component has a test API key prop. For test purchases, use
80
+ `Bitsnap.createCheckout(projectID, { items, apiKey, testMode: true })` from
81
+ `bitsnap-js` on the server. Do not pass keys to React's `createCheckout`.
82
+ - `bitsnap-react`'s own `createCheckout` sends `apiKey` as an `Authorization:
83
+ Bearer` header and is server-side only. Never call it from a client component,
84
+ island or browser bundle. A normal checkout needs no key; omit `apiKey`.
85
+ - `bitsnap-react` does not export `handleWebhook`. Import it from `bitsnap-js`
86
+ only in a server route. Pass `request.text()` unchanged, full URL and headers.
87
+ Verify `X-Content-Signature` or `sig` before processing or fulfilment.
88
+ - In Next.js use a Node route at `app/api/bitsnap/webhook/route.ts`. In Astro use
89
+ `src/pages/api/bitsnap/webhook.ts`, `prerender = false` and a Node adapter;
90
+ check `output`. A static build alone cannot receive webhook POSTs.
91
+ - Check the error object returned by `addProductToCart`. Do not show a successful
92
+ cart state when adding the item failed.
93
+ - Mount one cart per page/project. Project configuration is global.
94
+
95
+ ## Verify
96
+
97
+ 1. Configure a test gateway and make a test purchase with a project API key used
98
+ for server test checkout. Current keys have no distinct test/live prefix.
99
+ 2. Confirm a Test webhook arrives and HMAC verifies. Reject tampered bodies and
100
+ wrong projects. Deduplicate events and separate test/production fulfilment.
101
+ 3. Build and grep actual secret values in the client output and source maps.
102
+ Set nonempty variables privately; quiet grep avoids printing keys.
103
+
104
+ ```sh
105
+ grep -rFq -- "$BITSNAP_API_KEY" .next/static
106
+ grep -rFq -- "$BITSNAP_WEBHOOK_SECRET" .next/static
107
+ # Astro with Node: use dist/client/_astro instead of .next/static.
108
+ ```
109
+
110
+ Exit 0 means a leak, 1 means no match, 2 means a search error. Check the adapter's
111
+ client output path. Fix leaks and rotate exposed keys before deploying.
112
+
113
+ ## Full recipes
114
+
115
+ - [Next.js](https://docs.bitsnap.pl/en/guides/nextjs/)
116
+ - [Astro](https://docs.bitsnap.pl/en/guides/astro/)
117
+ - [SDK reference](https://docs.bitsnap.pl/en/reference/example/)
118
+ - [MCP](https://docs.bitsnap.pl/en/guides/mcp/)
119
+ - [bitsnap-integrate skill](https://docs.bitsnap.pl/skills/bitsnap-integrate/SKILL.md)
120
+
121
+ ## Internals (for contributors)
122
+
123
+ ## Product list (`ProductList`, `useProducts`)
124
+
125
+ Renders a shop's product catalog. Used by the Bitsnap custom-domain home page
126
+ and meant for embedding on merchants' own sites. Import from `bitsnap-react` or
127
+ from `bitsnap-react/catalog` (list and hook only; the checkout is loaded lazily
128
+ on the first "Add to cart" click). Load `bitsnap-react/dist/index.css` once.
129
+
130
+ Minimal embed:
131
+
132
+ ```tsx
133
+ import { ProductList } from "bitsnap-react/catalog";
134
+ import "bitsnap-react/dist/index.css";
135
+
136
+ export function Shop() {
137
+ return (
138
+ <ProductList
139
+ projectID="PROJECT_ID"
140
+ locale="pl-PL"
141
+ productHref={(p) => `https://shop.example.com/p/${p.uniqueSlug}`}
142
+ onAddToCart={(p) => myCart.add(p.cartItemId ?? p.id)} // optional, default is Bitsnap.addProductToCart
143
+ />
144
+ );
145
+ }
146
+ ```
147
+
148
+ Rules:
149
+
150
+ - Endpoint: `GET {host}/api/integrations/{projectID}/public-commerce/product-list?limit&offset&locale&search`
151
+ (`limit` default 24, max 48). `host` defaults to `https://bitsnap.pl`.
152
+ - Prices are minor units (grosze, cents).
153
+ - Pass `initialData` (`{ items, totalCount }`, the first page) to render the
154
+ products in the server HTML. Without it the server renders a skeleton and the
155
+ browser fetches.
156
+ - `productHref` defaults to the relative `/p/{uniqueSlug}`. On any site other
157
+ than the shop's own domain, return the full URL.
158
+ - CORS: the embedding site's domain must be in the project's site, success or
159
+ cancel URLs, or in the allowed domains. Otherwise the browser blocks the
160
+ request. Custom domains need nothing.
161
+ - Add to cart: products with `addToCart: true` get a button calling
162
+ `onAddToCart(product)`, or by default `Bitsnap.addProductToCart(id, 1)` and
163
+ then `Bitsnap.showCart()` (mount `BitsnapCheckout` on the page). It adds
164
+ `cartItemId ?? id` (the variant ID for single-variant products). The default
165
+ action sets the cart's global project ID/host from the list props only when
166
+ the page has not set them; if the page cart belongs to another project or
167
+ host it changes nothing, warns and shows the error state, so such a page
168
+ must pass `onAddToCart`.
169
+ - Cart links: `Bitsnap.addProductToCart(id, qty, metadata, { url })` stores the
170
+ product page with the cart line (localStorage), and the cart links the
171
+ item's image and name to it. Relative URLs are resolved against the page
172
+ that adds the item; only `http`/`https` are kept (`cartItemURL`). The
173
+ `ADD_TO_CART` postMessage accepts `url` too. `ProductList`'s default action
174
+ passes `productHref(product)`.
175
+ - Styling: `appearance={{ theme, tokens, elements }}`, scoped to
176
+ `[data-bitsnap-products]`. Texts: `texts`, Polish and English defaults by
177
+ `locale`.
178
+ - Do not use React Query, `setProjectID` or `setCustomHost` for the list
179
+ itself; `useProducts` takes everything as arguments.
180
+
181
+ ## Cart checkout (`BitsnapCheckout`)
182
+
183
+ "Next step" posts the cart to `{host}/api/integrations/{projectID}/public-commerce/buy`
184
+ (`createPaymentURL` in `src/components/checkout/methods.ts`, body built by
185
+ `cartLinkRequest` in `CartProvider.tsx`) and redirects to the returned `url`.
186
+
187
+ - Payment method choice: while the drawer shows a cart with a country,
188
+ `payment-methods` gets the same body (react-query, keyed by the cart lines,
189
+ country and locale). A non-null `choice` renders `PaymentMethodChoice`
190
+ (online, transfer / BLIK to the seller's phone, `cod` cash on delivery; first
191
+ of `methods` selected) and `/buy` gets `paymentMethod`. `cod` refused by the
192
+ shop comes back as `gateway-not-configured` and shows
193
+ `paymentMethodCodUnavailable`. Anything else (null, error, 404 of an old backend, 5 s
194
+ timeout) is "no choice": plain `/buy`, no `paymentMethod`. A click before the
195
+ answer waits for it and, if a choice arrives, shows it instead of buying.
196
+ While a new country or cart is asked for, the last choice stays as
197
+ `placeholderData` without `cod` (`withoutCod`); a click then fetches the new
198
+ answer and buys only if it still offers the selected method.
199
+ - Pending order (`pending-order.ts`): the cart is kept until a
200
+ `/payment/common/...` order is paid, or `placed` (a `cod` order the buyer
201
+ accepted on the order's details form; it is paid to the courier). A
202
+ `/payment/manualPayment/<project>/<order>` page is a placed order paid
203
+ outside Bitsnap, so the cart is cleared at once.
204
+ - Express buttons (Apple Pay, Google Pay) are separate online flows.
205
+ - Tests mock `fetch` by URL: `cart-buy-error.test.tsx`, `cart-payment-method.test.tsx`.
206
+
207
+ ## Course view (`PanelCourse`)
208
+
209
+ The customer panel opens a purchased course inside the panel (no redirect to
210
+ kursy.bitsnap.pl). Import `PanelCourse` from `bitsnap-react/course`; the panel
211
+ itself loads it with a dynamic import, so the video player (hls.js), DOMPurify
212
+ and the tests are not in the main ESM bundle.
213
+
214
+ ```tsx
215
+ import { PanelCourse } from "bitsnap-react/course";
216
+
217
+ <PanelCourse accessId={product.accessId} onBack={() => history.back()} />
218
+ ```
219
+
220
+ Rules:
221
+
222
+ - Data goes through `CourseDataSource` (`src/course/types.ts`). The default is
223
+ `createRpcCourseDataSource({ accessId })`: `UserPanelCourseService` on
224
+ `{host}/api/rpc` with the panel session token (`accessId` + `accessToken`
225
+ like `userPanelGetCourseDetails`; no project or course id in requests).
226
+ Pass `dataSource` to feed another source (the creator preview of the
227
+ dashboard) and `preview` to show the "Podgląd" badge and skip progress saving.
228
+ - Deep links: the panel keeps the course in the URL, `?state=course&course=<accessId>&item=<lessonOrTestId>`
229
+ (values are JSON-encoded by `useUrlSearchParams` like the other params; plain
230
+ values work too). Reload keeps the lesson; browser back/forward move between
231
+ lessons. `PanelProductGrid` opens courses directly; `PanelProductDetails` of a
232
+ course product shows an "open the course" button.
233
+ - Lesson HTML is always rendered through `sanitizeLessonHtml` (DOMPurify; no
234
+ iframes, forms, inline styles). It returns `""` without a DOM, so lesson HTML
235
+ never appears in server-rendered output.
236
+ - Video: `hls.js`, native HLS on Safari. Position is saved every ~15 s while
237
+ playing and on pause/end/leaving; a lesson completes at 80 %, as in the LMS (`logic.ts`).
238
+ - Texts: `texts` prop over Polish/English tables (`src/course/texts.ts`).
239
+ - The contract is drafted in `proto-draft/` (see its README); generated code in
240
+ `src/gen/proto/public/v1/user_panel_course_*` comes from
241
+ `./proto-course-draft.sh`.
242
+
243
+ Tests: `npm test` (unit), `pnpm test:catalog-ssr` (builds, then renders the
244
+ list on the server from `initialData`), `pnpm test:course-ssr` (builds, then
245
+ renders the course view on the server). Never run `pnpm build:publish` unless
246
+ asked to release: it publishes to npm.
package/README.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Bitsnap Checkout
2
2
 
3
+ ## Instructions for coding agents
4
+
5
+ Read [AGENTS.md](./AGENTS.md) before integrating this package. Follow the
6
+ [Next.js](https://docs.bitsnap.pl/en/guides/nextjs/) or
7
+ [Astro](https://docs.bitsnap.pl/en/guides/astro/) recipe. Keep API keys and webhook
8
+ secrets on the server.
9
+
3
10
  The library is designed to provide a complete e-commerce checkout solution that can be easily integrated into web applications, with focus on:
4
11
 
5
12
  - User experience
@@ -782,19 +789,35 @@ async function legacyHandleCart() {
782
789
  }
783
790
  ```
784
791
 
785
- 5. Creating Checkout/Payment:
792
+ 5. Creating Checkout/Payment (server-side only):
786
793
 
787
- ```tsx
788
- import { createCheckout, LinkRequest } from "bitsnap-react";
789
-
790
- async function handleCheckout() {
791
- const request: LinkRequest = {
792
- items: [
793
- {
794
- id: "product-id",
795
- quantity: 1,
796
- },
797
- ],
794
+ `createCheckout` sends an `Authorization: Bearer` header, so a browser key would
795
+ be visible to anyone loading your page. Create the checkout in a server route or
796
+ server action and redirect the browser to the returned URL:
797
+
798
+ ```ts
799
+ // app/api/bitsnap/checkout/route.ts
800
+ import { Bitsnap, type LinkRequest } from "bitsnap-js";
801
+
802
+ export const runtime = "nodejs";
803
+
804
+ export async function POST(request: Request) {
805
+ const projectID = process.env.BITSNAP_PROJECT_ID;
806
+ const apiKey = process.env.BITSNAP_API_KEY;
807
+ if (!projectID || !apiKey) {
808
+ return new Response("Not configured", { status: 500 });
809
+ }
810
+
811
+ // Pick the fields you accept. Do not forward a raw client body to the API.
812
+ const { items } = (await request.json()) as {
813
+ items?: Array<{ id: string; quantity: number }>;
814
+ };
815
+ if (!items?.length) {
816
+ return new Response("No items", { status: 400 });
817
+ }
818
+
819
+ const linkRequest: LinkRequest = {
820
+ items,
798
821
  details: {
799
822
  email: "customer@example.com",
800
823
  name: "John Doe",
@@ -807,20 +830,27 @@ async function handleCheckout() {
807
830
  },
808
831
  askForAddress: true,
809
832
  askForPhone: true,
833
+ apiKey,
834
+ testMode: true,
810
835
  };
811
836
 
812
- const result = await createCheckout({
813
- ...request,
814
- apiKey: "your-api-key",
815
- testMode: true,
816
- });
837
+ const result = await Bitsnap.createCheckout(projectID, linkRequest);
817
838
 
818
- if (result.status === "ok") {
819
- window.location.href = result.redirectURL;
839
+ // The SDK does not check the HTTP status. Check that a URL came back.
840
+ if (!result.redirectURL) {
841
+ return new Response("Checkout failed", { status: 502 });
820
842
  }
843
+ return Response.json({ url: result.redirectURL });
821
844
  }
822
845
  ```
823
846
 
847
+ `bitsnap-react` also exports its own `createCheckout(request)`. It takes the same
848
+ request and sends `apiKey` as an `Authorization: Bearer` header, so it is
849
+ **server-side only**: calling it from a client component, an island or any
850
+ browser bundle would expose the project API key to every visitor. A normal
851
+ checkout needs no key at all. For a keyed server checkout use
852
+ `Bitsnap.createCheckout(projectID, request)` from `bitsnap-js`.
853
+
824
854
  On success `createCheckout` returns `{ status: "ok", redirectURL }`. A failed
825
855
  response no longer throws and no longer yields an undefined `redirectURL`: it
826
856
  returns an `Err` with `status: "error"` instead (`result.ERR === true`), typed
@@ -855,32 +885,56 @@ When the backend lists unavailable products (`itemIds`) under
855
885
  `item-ids-not-found` and `data` is the array of ids. With any other code the
856
886
  code is kept and the ids are in `data.itemIds`.
857
887
 
858
- 6. Webhook Handler:
859
-
860
888
  ```tsx
861
- import { handleWebhook } from "bitsnap-react";
889
+ // Client component: no key, only the returned URL.
890
+ const { url } = await fetch("/api/bitsnap/checkout", {
891
+ method: "POST",
892
+ headers: { "Content-Type": "application/json" },
893
+ body: JSON.stringify({ items: [{ id: "product-id", quantity: 1 }] }),
894
+ }).then((r) => r.json());
895
+ window.location.href = url;
896
+ ```
897
+
898
+ Without a key, the browser can still create a normal checkout for a public
899
+ product by adding it to the cart and calling `Bitsnap.showCart()`. Only server
900
+ test checkout needs `apiKey`.
901
+
902
+ 6. Webhook Handler (server-side only):
903
+
904
+ `handleWebhook` is exported by `bitsnap-js`, not by `bitsnap-react`. Install
905
+ `bitsnap-js` and run this in a server route.
906
+
907
+ ```ts
908
+ // app/api/bitsnap/webhook/route.ts
909
+ import { Event, handleWebhook } from "bitsnap-js";
910
+
911
+ export const runtime = "nodejs";
862
912
 
863
- async function processWebhook(req: Request) {
913
+ export async function processWebhook(req: Request) {
914
+ const webhookSecret = process.env.BITSNAP_WEBHOOK_SECRET;
915
+ if (!webhookSecret) return new Response("Not configured", { status: 500 });
916
+
917
+ // Pass the raw body and the full URL. Never call req.json() first.
864
918
  const payload = await req.text();
865
- const url = req.url;
866
919
  const headers = Object.fromEntries(req.headers);
867
- const webhookSecret = "your-webhook-secret";
868
920
 
869
- const result = await handleWebhook(payload, url, headers, webhookSecret);
921
+ const result = await handleWebhook(payload, req.url, headers, webhookSecret);
870
922
 
871
923
  if (result.isErr) {
872
924
  console.error("Webhook error:", result.error);
873
- return;
925
+ return new Response("Invalid signature", { status: 401 });
874
926
  }
875
927
 
876
928
  // Process the webhook event
877
929
  const { projectId, environment, eventData } = result;
878
930
 
931
+ // Parsed enums are numeric. Compare against the exported Event enum,
932
+ // not against the string "TRANSACTION_SUCCESS".
879
933
  switch (eventData?.event) {
880
- case "TRANSACTION_SUCCESS":
934
+ case Event.TRANSACTION_SUCCESS:
881
935
  // Handle successful transaction
882
936
  break;
883
- case "TRANSACTION_FAILURE":
937
+ case Event.TRANSACTION_FAILURE:
884
938
  // Handle failed transaction
885
939
  break;
886
940
  // Handle other events...
@@ -891,14 +945,13 @@ async function processWebhook(req: Request) {
891
945
  Complete Example:
892
946
 
893
947
  ```tsx
948
+ "use client";
949
+
894
950
  import {
895
951
  BitsnapCheckout,
896
952
  setProjectID,
897
953
  setCustomHost,
898
954
  Bitsnap,
899
- createCheckout,
900
- handleWebhook,
901
- type LinkRequest,
902
955
  } from "bitsnap-react";
903
956
 
904
957
  // Configure globally
@@ -914,30 +967,22 @@ function Store() {
914
967
  Bitsnap.showCart();
915
968
  }
916
969
 
970
+ // A hosted checkout link without an API key: create it in a server route
971
+ // (see "Creating Checkout/Payment" above) and follow the returned URL.
917
972
  async function processCheckout(
918
973
  items: Array<{ id: string; quantity: number }>,
919
974
  ) {
920
- const checkoutRequest: LinkRequest = {
921
- items,
922
- askForAddress: true,
923
- askForPhone: true,
924
- details: {
925
- email: "customer@example.com",
926
- },
927
- redirect: {
928
- successURL: "/success",
929
- cancelURL: "/cancel",
930
- },
931
- };
932
-
933
- const result = await createCheckout({
934
- ...checkoutRequest,
935
- testMode: true,
975
+ const response = await fetch("/api/bitsnap/checkout", {
976
+ method: "POST",
977
+ headers: { "Content-Type": "application/json" },
978
+ body: JSON.stringify({ items }),
936
979
  });
937
-
938
- if (result.status === "ok") {
939
- window.location.href = result.redirectURL;
980
+ if (!response.ok) {
981
+ console.error("Checkout failed");
982
+ return;
940
983
  }
984
+ const { url } = await response.json();
985
+ window.location.href = url;
941
986
  }
942
987
 
943
988
  return (
@@ -955,6 +1000,9 @@ function Store() {
955
1000
  }
956
1001
  ```
957
1002
 
1003
+ `createCheckout` and `handleWebhook` are not part of this client bundle: the
1004
+ keyed checkout and webhook verification belong to `bitsnap-js` on the server.
1005
+
958
1006
  ---
959
1007
 
960
1008
  ## Capabilities
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunk2OOQPXCQjs = require('./chunk-2OOQPXCQ.js');require('./chunk-GTOZ5BZX.js');require('./chunk-KOYBZORL.js');require('./chunk-5DBLZ3VL.js');require('./chunk-QQDZIVWY.js');var _chunkEJRI366Wjs = require('./chunk-EJRI366W.js');require('./chunk-PVJ2I46C.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.MARKETING_AGREEMENT_ID = _chunk2OOQPXCQjs.L; exports.bitsnapProjectID = _chunk2OOQPXCQjs.M; exports.default = _chunk2OOQPXCQjs.T; exports.getCartLines = _chunk2OOQPXCQjs.Q; exports.getCheckoutMethods = _chunk2OOQPXCQjs.U; exports.getCheckoutSelection = _chunk2OOQPXCQjs.P; exports.getLocale = _chunkEJRI366Wjs.m; exports.getProjectID = _chunk2OOQPXCQjs.O; exports.reconcileOrderOfLastPayment = _chunk2OOQPXCQjs.R; exports.setLocale = _chunkEJRI366Wjs.l; exports.setProjectID = _chunk2OOQPXCQjs.N; exports.useCartProvider = _chunk2OOQPXCQjs.S;
2
+ //# sourceMappingURL=CartProvider-2RNAKU3U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/dx/dist/CartProvider-2RNAKU3U.js"],"names":[],"mappings":"AAAA,+HAAsG,+BAA4B,+BAA4B,+BAA4B,+BAA4B,sDAA+C,+BAA4B,+BAA4B,+BAA4B,siBAAmQ","file":"/home/emems/.t3/worktrees/bitsnap-react/dx/dist/CartProvider-2RNAKU3U.js"}
@@ -1,2 +1,2 @@
1
- import{L as c,M as d,N as e,O as f,P as g,Q as h,R as i,S as j,T as k,U as l}from"./chunk-U5SAO2SH.mjs";import"./chunk-CYLZ5GYW.mjs";import"./chunk-KOVKUJAW.mjs";import"./chunk-SKFE6TUP.mjs";import"./chunk-MVOJMCZG.mjs";import{l as a,m as b}from"./chunk-GKFET34B.mjs";import"./chunk-3OEKRPWV.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{c as MARKETING_AGREEMENT_ID,d as bitsnapProjectID,k as default,h as getCartLines,l as getCheckoutMethods,g as getCheckoutSelection,b as getLocale,f as getProjectID,i as reconcileOrderOfLastPayment,a as setLocale,e as setProjectID,j as useCartProvider};
2
- //# sourceMappingURL=CartProvider-GLUQHAG3.mjs.map
1
+ import{L as c,M as d,N as e,O as f,P as g,Q as h,R as i,S as j,T as k,U as l}from"./chunk-7WJSK6SJ.mjs";import"./chunk-4TZAGNEU.mjs";import"./chunk-KOVKUJAW.mjs";import"./chunk-FIWADPQX.mjs";import"./chunk-L7LZG6MR.mjs";import{l as a,m as b}from"./chunk-TBYY3RL2.mjs";import"./chunk-JZBU5SZJ.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{c as MARKETING_AGREEMENT_ID,d as bitsnapProjectID,k as default,h as getCartLines,l as getCheckoutMethods,g as getCheckoutSelection,b as getLocale,f as getProjectID,i as reconcileOrderOfLastPayment,a as setLocale,e as setProjectID,j as useCartProvider};
2
+ //# sourceMappingURL=CartProvider-OSGG573S.mjs.map
package/dist/analytics.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunk3N2VK426js = require('./chunk-3N2VK426.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.BitsnapAnalyticsProvider = _chunk3N2VK426js.i; exports.DEFAULT_ENDPOINT = _chunk3N2VK426js.c; exports.InstrumentProvider = _chunk3N2VK426js.l; exports.createTracker = _chunk3N2VK426js.d; exports.getTracker = _chunk3N2VK426js.f; exports.initTracker = _chunk3N2VK426js.e; exports.noopInstrument = _chunk3N2VK426js.k; exports.resetTracker = _chunk3N2VK426js.g; exports.scoreBucket = _chunk3N2VK426js.r; exports.startAutoPageViews = _chunk3N2VK426js.b; exports.useAnalytics = _chunk3N2VK426js.j; exports.useInstrument = _chunk3N2VK426js.n; exports.useInstrumentContext = _chunk3N2VK426js.m;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkPVJ2I46Cjs = require('./chunk-PVJ2I46C.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.BitsnapAnalyticsProvider = _chunkPVJ2I46Cjs.i; exports.DEFAULT_ENDPOINT = _chunkPVJ2I46Cjs.c; exports.InstrumentProvider = _chunkPVJ2I46Cjs.l; exports.createTracker = _chunkPVJ2I46Cjs.d; exports.getTracker = _chunkPVJ2I46Cjs.f; exports.initTracker = _chunkPVJ2I46Cjs.e; exports.noopInstrument = _chunkPVJ2I46Cjs.k; exports.resetTracker = _chunkPVJ2I46Cjs.g; exports.scoreBucket = _chunkPVJ2I46Cjs.r; exports.startAutoPageViews = _chunkPVJ2I46Cjs.b; exports.useAnalytics = _chunkPVJ2I46Cjs.j; exports.useInstrument = _chunkPVJ2I46Cjs.n; exports.useInstrumentContext = _chunkPVJ2I46Cjs.m;
2
2
  //# sourceMappingURL=analytics.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/consent-shown/dist/analytics.js"],"names":[],"mappings":"AAAA,+HAAsH,+BAA4B,+BAA4B,klBAAsR","file":"/home/emems/.t3/worktrees/bitsnap-react/consent-shown/dist/analytics.js"}
1
+ {"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/dx/dist/analytics.js"],"names":[],"mappings":"AAAA,+HAAsH,+BAA4B,+BAA4B,klBAAsR","file":"/home/emems/.t3/worktrees/bitsnap-react/dx/dist/analytics.js"}
@@ -1,2 +1,2 @@
1
- import{b as e,c as t,d as r,e as n,f as o,g as a,i as s,j as p,k as c,l as i,m,n as u,r as v}from"./chunk-3OEKRPWV.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{s as BitsnapAnalyticsProvider,t as DEFAULT_ENDPOINT,i as InstrumentProvider,r as createTracker,o as getTracker,n as initTracker,c as noopInstrument,a as resetTracker,v as scoreBucket,e as startAutoPageViews,p as useAnalytics,u as useInstrument,m as useInstrumentContext};
1
+ import{b as e,c as t,d as r,e as n,f as o,g as a,i as s,j as p,k as c,l as i,m,n as u,r as v}from"./chunk-JZBU5SZJ.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{s as BitsnapAnalyticsProvider,t as DEFAULT_ENDPOINT,i as InstrumentProvider,r as createTracker,o as getTracker,n as initTracker,c as noopInstrument,a as resetTracker,v as scoreBucket,e as startAutoPageViews,p as useAnalytics,u as useInstrument,m as useInstrumentContext};
2
2
  //# sourceMappingURL=analytics.mjs.map
package/dist/catalog.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkJXVAT2ZEjs = require('./chunk-JXVAT2ZE.js');require('./chunk-KOYBZORL.js');require('./chunk-AWG5OAK7.js');require('./chunk-3N2VK426.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.PRODUCT_LIST_SLOT_NAMES = _chunkJXVAT2ZEjs.a; exports.ProductList = _chunkJXVAT2ZEjs.d; exports.formatProductPrice = _chunkJXVAT2ZEjs.b; exports.useProducts = _chunkJXVAT2ZEjs.c;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunk4MU2YAQTjs = require('./chunk-4MU2YAQT.js');require('./chunk-KOYBZORL.js');require('./chunk-EJRI366W.js');require('./chunk-PVJ2I46C.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.PRODUCT_LIST_SLOT_NAMES = _chunk4MU2YAQTjs.a; exports.ProductList = _chunk4MU2YAQTjs.d; exports.formatProductPrice = _chunk4MU2YAQTjs.b; exports.useProducts = _chunk4MU2YAQTjs.c;
2
2
  //# sourceMappingURL=catalog.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/consent-shown/dist/catalog.js"],"names":[],"mappings":"AAAA,+HAA4D,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,0LAA+F","file":"/home/emems/.t3/worktrees/bitsnap-react/consent-shown/dist/catalog.js"}
1
+ {"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/dx/dist/catalog.js"],"names":[],"mappings":"AAAA,+HAA4D,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,0LAA+F","file":"/home/emems/.t3/worktrees/bitsnap-react/dx/dist/catalog.js"}
package/dist/catalog.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{a as o,b as r,c as e,d as f}from"./chunk-VYYUTJZB.mjs";import"./chunk-KOVKUJAW.mjs";import"./chunk-GKFET34B.mjs";import"./chunk-3OEKRPWV.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{o as PRODUCT_LIST_SLOT_NAMES,f as ProductList,r as formatProductPrice,e as useProducts};
1
+ import{a as o,b as r,c as e,d as f}from"./chunk-WJGC6EHC.mjs";import"./chunk-KOVKUJAW.mjs";import"./chunk-TBYY3RL2.mjs";import"./chunk-JZBU5SZJ.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{o as PRODUCT_LIST_SLOT_NAMES,f as ProductList,r as formatProductPrice,e as useProducts};
2
2
  //# sourceMappingURL=catalog.mjs.map