bodega-checkout 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/dist/chunk-RLJQ62ON.js +150 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +18 -0
- package/dist/react/use-bodega.d.ts +1 -0
- package/dist/react/use-bodega.js +6 -0
- package/dist/use-bodega-DMW30INZ.d.ts +76 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# bodega-checkout
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for [Bodega](https://bodega.dev) — crypto commerce checkout.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install bodega-checkout
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createClient } from "bodega-checkout";
|
|
15
|
+
|
|
16
|
+
const bodega = createClient({ apiKey: "sk_live_..." });
|
|
17
|
+
|
|
18
|
+
// Create a checkout session
|
|
19
|
+
const session = await bodega.checkout.create({
|
|
20
|
+
productId: "prod_abc",
|
|
21
|
+
successUrl: "https://myapp.com/success",
|
|
22
|
+
cancelUrl: "https://myapp.com/cancel",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Check access
|
|
26
|
+
const access = await bodega.access.check("0x123...", "prod_abc");
|
|
27
|
+
console.log(access.hasAccess);
|
|
28
|
+
|
|
29
|
+
// List products
|
|
30
|
+
const products = await bodega.products.list();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Popup Checkout (Browser)
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { createClient, openPopup } from "bodega-checkout";
|
|
37
|
+
|
|
38
|
+
const bodega = createClient({ apiKey: "sk_live_..." });
|
|
39
|
+
const session = await bodega.checkout.create({ ... });
|
|
40
|
+
|
|
41
|
+
openPopup(session.url, {
|
|
42
|
+
onSuccess: (orderId, txHash) => console.log("Paid!", txHash),
|
|
43
|
+
onError: (error) => console.error(error),
|
|
44
|
+
onClose: () => console.log("Closed"),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## React Hook
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { useBodegaCheckout } from "bodega-checkout/react";
|
|
52
|
+
|
|
53
|
+
function BuyButton({ productId }: { productId: string }) {
|
|
54
|
+
const { checkout, isLoading, error } = useBodegaCheckout({
|
|
55
|
+
apiKey: "sk_live_...",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<button
|
|
60
|
+
disabled={isLoading}
|
|
61
|
+
onClick={() =>
|
|
62
|
+
checkout(productId, {
|
|
63
|
+
onSuccess: (e) => console.log("Paid!", e.txHash),
|
|
64
|
+
onError: (err) => console.error(err),
|
|
65
|
+
onClose: () => console.log("Closed"),
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
{isLoading ? "Loading..." : "Buy Now"}
|
|
70
|
+
</button>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
| Option | Default | Description |
|
|
78
|
+
| --------- | -------------------- | ------------------- |
|
|
79
|
+
| `apiKey` | — | Your Bodega API key |
|
|
80
|
+
| `baseUrl` | `https://bodega.dev` | API base URL |
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// src/react/use-bodega.ts
|
|
2
|
+
import { useState, useCallback, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
// src/client.ts
|
|
5
|
+
var DEFAULT_BASE_URL = "https://bodega.dev";
|
|
6
|
+
var BodegaClient = class {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.checkout = {
|
|
9
|
+
create: (opts) => this.request("/api/v1/checkout/sessions", {
|
|
10
|
+
method: "POST",
|
|
11
|
+
body: JSON.stringify(opts)
|
|
12
|
+
})
|
|
13
|
+
};
|
|
14
|
+
this.access = {
|
|
15
|
+
check: (wallet, productId) => this.request(
|
|
16
|
+
`/api/v1/access/check?wallet=${encodeURIComponent(wallet)}&productId=${encodeURIComponent(productId)}`
|
|
17
|
+
)
|
|
18
|
+
};
|
|
19
|
+
this.products = {
|
|
20
|
+
list: () => this.request("/api/v1/products")
|
|
21
|
+
};
|
|
22
|
+
this.apiKey = config.apiKey;
|
|
23
|
+
this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
24
|
+
}
|
|
25
|
+
async request(path, init) {
|
|
26
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
27
|
+
...init,
|
|
28
|
+
headers: {
|
|
29
|
+
"Content-Type": "application/json",
|
|
30
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
31
|
+
...init?.headers
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
const body = await res.text().catch(() => "");
|
|
36
|
+
throw new Error(`Bodega API error ${res.status}: ${body}`);
|
|
37
|
+
}
|
|
38
|
+
return res.json();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/popup.ts
|
|
43
|
+
var checkoutWindow = null;
|
|
44
|
+
function openPopup(checkoutUrl, callbacks = {}) {
|
|
45
|
+
closePopup();
|
|
46
|
+
checkoutWindow = window.open(checkoutUrl, "_blank");
|
|
47
|
+
if (!checkoutWindow) {
|
|
48
|
+
callbacks.onError?.("Popup blocked. Please allow popups for this site.");
|
|
49
|
+
return () => {
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
callbacks.onReady?.();
|
|
53
|
+
const pollTimer = setInterval(() => {
|
|
54
|
+
if (checkoutWindow?.closed) {
|
|
55
|
+
clearInterval(pollTimer);
|
|
56
|
+
window.removeEventListener("message", handleMessage);
|
|
57
|
+
checkoutWindow = null;
|
|
58
|
+
callbacks.onClose?.();
|
|
59
|
+
}
|
|
60
|
+
}, 500);
|
|
61
|
+
function handleMessage(e) {
|
|
62
|
+
const data = e.data;
|
|
63
|
+
if (!data || typeof data.type !== "string" || !data.type.startsWith("bodega:"))
|
|
64
|
+
return;
|
|
65
|
+
switch (data.type) {
|
|
66
|
+
case "bodega:ready":
|
|
67
|
+
callbacks.onReady?.();
|
|
68
|
+
break;
|
|
69
|
+
case "bodega:success":
|
|
70
|
+
callbacks.onSuccess?.(data.orderId, data.txHash);
|
|
71
|
+
cleanup();
|
|
72
|
+
break;
|
|
73
|
+
case "bodega:error":
|
|
74
|
+
callbacks.onError?.(data.error);
|
|
75
|
+
break;
|
|
76
|
+
case "bodega:close":
|
|
77
|
+
cleanup();
|
|
78
|
+
callbacks.onClose?.();
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
window.addEventListener("message", handleMessage);
|
|
83
|
+
function cleanup() {
|
|
84
|
+
clearInterval(pollTimer);
|
|
85
|
+
window.removeEventListener("message", handleMessage);
|
|
86
|
+
closePopup();
|
|
87
|
+
}
|
|
88
|
+
return cleanup;
|
|
89
|
+
}
|
|
90
|
+
function closePopup() {
|
|
91
|
+
if (checkoutWindow && !checkoutWindow.closed) {
|
|
92
|
+
checkoutWindow.close();
|
|
93
|
+
}
|
|
94
|
+
checkoutWindow = null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/react/use-bodega.ts
|
|
98
|
+
function useBodegaCheckout(config) {
|
|
99
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
100
|
+
const [error, setError] = useState(null);
|
|
101
|
+
const cleanupRef = useRef(null);
|
|
102
|
+
const clientRef = useRef(null);
|
|
103
|
+
if (!clientRef.current) {
|
|
104
|
+
clientRef.current = new BodegaClient(config);
|
|
105
|
+
}
|
|
106
|
+
const checkout = useCallback(
|
|
107
|
+
async (productId, opts = {}) => {
|
|
108
|
+
setIsLoading(true);
|
|
109
|
+
setError(null);
|
|
110
|
+
try {
|
|
111
|
+
const session = await clientRef.current.checkout.create({
|
|
112
|
+
productId,
|
|
113
|
+
successUrl: opts.successUrl ?? window.location.href,
|
|
114
|
+
cancelUrl: opts.cancelUrl ?? window.location.href,
|
|
115
|
+
metadata: opts.metadata
|
|
116
|
+
});
|
|
117
|
+
cleanupRef.current?.();
|
|
118
|
+
cleanupRef.current = openPopup(session.url, {
|
|
119
|
+
onSuccess: (orderId, txHash) => {
|
|
120
|
+
setIsLoading(false);
|
|
121
|
+
opts.onSuccess?.({ type: "bodega:success", orderId, txHash });
|
|
122
|
+
},
|
|
123
|
+
onError: (err) => {
|
|
124
|
+
setError(err);
|
|
125
|
+
setIsLoading(false);
|
|
126
|
+
opts.onError?.(err);
|
|
127
|
+
},
|
|
128
|
+
onClose: () => {
|
|
129
|
+
setIsLoading(false);
|
|
130
|
+
opts.onClose?.();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const msg = err instanceof Error ? err.message : "Checkout failed";
|
|
135
|
+
setError(msg);
|
|
136
|
+
setIsLoading(false);
|
|
137
|
+
opts.onError?.(msg);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
[]
|
|
141
|
+
);
|
|
142
|
+
return { checkout, isLoading, error };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export {
|
|
146
|
+
BodegaClient,
|
|
147
|
+
openPopup,
|
|
148
|
+
closePopup,
|
|
149
|
+
useBodegaCheckout
|
|
150
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { B as BodegaConfig, C as CheckoutOptions, a as CheckoutSession, A as AccessRecord, P as Product } from './use-bodega-DMW30INZ.js';
|
|
2
|
+
export { b as BodegaCloseEvent, c as BodegaErrorEvent, d as BodegaEvent, e as BodegaReadyEvent, f as BodegaSuccessEvent, O as Order, U as UseCheckoutOptions, g as UseCheckoutReturn, u as useBodegaCheckout } from './use-bodega-DMW30INZ.js';
|
|
3
|
+
|
|
4
|
+
declare class BodegaClient {
|
|
5
|
+
private apiKey;
|
|
6
|
+
private baseUrl;
|
|
7
|
+
constructor(config: BodegaConfig);
|
|
8
|
+
private request;
|
|
9
|
+
checkout: {
|
|
10
|
+
create: (opts: CheckoutOptions) => Promise<CheckoutSession>;
|
|
11
|
+
};
|
|
12
|
+
access: {
|
|
13
|
+
check: (wallet: string, productId: string) => Promise<AccessRecord>;
|
|
14
|
+
};
|
|
15
|
+
products: {
|
|
16
|
+
list: () => Promise<Product[]>;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PopupCallbacks {
|
|
21
|
+
onReady?: () => void;
|
|
22
|
+
onSuccess?: (orderId: string, txHash: string) => void;
|
|
23
|
+
onError?: (error: string) => void;
|
|
24
|
+
onClose?: () => void;
|
|
25
|
+
}
|
|
26
|
+
declare function openPopup(checkoutUrl: string, callbacks?: PopupCallbacks): () => void;
|
|
27
|
+
declare function closePopup(): void;
|
|
28
|
+
|
|
29
|
+
declare function createClient(config: BodegaConfig): BodegaClient;
|
|
30
|
+
|
|
31
|
+
export { AccessRecord, BodegaClient, BodegaConfig, CheckoutOptions, CheckoutSession, Product, closePopup, createClient, openPopup };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BodegaClient,
|
|
3
|
+
closePopup,
|
|
4
|
+
openPopup,
|
|
5
|
+
useBodegaCheckout
|
|
6
|
+
} from "./chunk-RLJQ62ON.js";
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
function createClient(config) {
|
|
10
|
+
return new BodegaClient(config);
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
BodegaClient,
|
|
14
|
+
closePopup,
|
|
15
|
+
createClient,
|
|
16
|
+
openPopup,
|
|
17
|
+
useBodegaCheckout
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { u as useBodegaCheckout } from '../use-bodega-DMW30INZ.js';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
interface Product {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
price: number;
|
|
6
|
+
currency: string;
|
|
7
|
+
imageUrl?: string;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
}
|
|
10
|
+
interface Order {
|
|
11
|
+
id: string;
|
|
12
|
+
productId: string;
|
|
13
|
+
wallet: string;
|
|
14
|
+
txHash: string;
|
|
15
|
+
status: "pending" | "completed" | "failed";
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
18
|
+
interface CheckoutSession {
|
|
19
|
+
id: string;
|
|
20
|
+
url: string;
|
|
21
|
+
productId: string;
|
|
22
|
+
successUrl: string;
|
|
23
|
+
cancelUrl: string;
|
|
24
|
+
metadata?: Record<string, string>;
|
|
25
|
+
expiresAt: string;
|
|
26
|
+
}
|
|
27
|
+
interface AccessRecord {
|
|
28
|
+
hasAccess: boolean;
|
|
29
|
+
wallet: string;
|
|
30
|
+
productId: string;
|
|
31
|
+
orderId?: string;
|
|
32
|
+
grantedAt?: string;
|
|
33
|
+
}
|
|
34
|
+
interface BodegaConfig {
|
|
35
|
+
apiKey: string;
|
|
36
|
+
baseUrl?: string;
|
|
37
|
+
}
|
|
38
|
+
interface CheckoutOptions {
|
|
39
|
+
productId: string;
|
|
40
|
+
successUrl: string;
|
|
41
|
+
cancelUrl: string;
|
|
42
|
+
metadata?: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
interface BodegaReadyEvent {
|
|
45
|
+
type: "bodega:ready";
|
|
46
|
+
}
|
|
47
|
+
interface BodegaSuccessEvent {
|
|
48
|
+
type: "bodega:success";
|
|
49
|
+
orderId: string;
|
|
50
|
+
txHash: string;
|
|
51
|
+
}
|
|
52
|
+
interface BodegaErrorEvent {
|
|
53
|
+
type: "bodega:error";
|
|
54
|
+
error: string;
|
|
55
|
+
}
|
|
56
|
+
interface BodegaCloseEvent {
|
|
57
|
+
type: "bodega:close";
|
|
58
|
+
}
|
|
59
|
+
type BodegaEvent = BodegaReadyEvent | BodegaSuccessEvent | BodegaErrorEvent | BodegaCloseEvent;
|
|
60
|
+
interface UseCheckoutOptions {
|
|
61
|
+
onSuccess?: (event: BodegaSuccessEvent) => void;
|
|
62
|
+
onError?: (error: string) => void;
|
|
63
|
+
onClose?: () => void;
|
|
64
|
+
successUrl?: string;
|
|
65
|
+
cancelUrl?: string;
|
|
66
|
+
metadata?: Record<string, string>;
|
|
67
|
+
}
|
|
68
|
+
interface UseCheckoutReturn {
|
|
69
|
+
checkout: (productId: string, opts?: UseCheckoutOptions) => Promise<void>;
|
|
70
|
+
isLoading: boolean;
|
|
71
|
+
error: string | null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
declare function useBodegaCheckout(config: BodegaConfig): UseCheckoutReturn;
|
|
75
|
+
|
|
76
|
+
export { type AccessRecord as A, type BodegaConfig as B, type CheckoutOptions as C, type Order as O, type Product as P, type UseCheckoutOptions as U, type CheckoutSession as a, type BodegaCloseEvent as b, type BodegaErrorEvent as c, type BodegaEvent as d, type BodegaReadyEvent as e, type BodegaSuccessEvent as f, type UseCheckoutReturn as g, useBodegaCheckout as u };
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bodega-checkout",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for Bodega — crypto commerce checkout. USDC payments on Base.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./react": {
|
|
16
|
+
"import": "./dist/react/use-bodega.js",
|
|
17
|
+
"types": "./dist/react/use-bodega.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"bodega",
|
|
25
|
+
"crypto",
|
|
26
|
+
"payments",
|
|
27
|
+
"usdc",
|
|
28
|
+
"base",
|
|
29
|
+
"checkout",
|
|
30
|
+
"web3"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsup",
|
|
34
|
+
"dev": "tsup --watch",
|
|
35
|
+
"prepublishOnly": "tsup"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"react": ">=18.0.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"react": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"tsup": "^8.0.0",
|
|
47
|
+
"typescript": "^5.3.0",
|
|
48
|
+
"@types/react": "^18.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|