@libreapps/checkout 2.0.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 (42) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +24 -0
  3. package/LICENSE.md +21 -0
  4. package/README.md +211 -0
  5. package/client.ts +318 -0
  6. package/dist/client.d.ts +80 -0
  7. package/dist/client.js +229 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/elements/checkout-form.d.ts +14 -0
  10. package/dist/elements/checkout-form.js +112 -0
  11. package/dist/elements/checkout-form.js.map +1 -0
  12. package/dist/elements/index.d.ts +7 -0
  13. package/dist/elements/index.js +7 -0
  14. package/dist/elements/index.js.map +1 -0
  15. package/dist/embed/index.d.ts +6 -0
  16. package/dist/embed/index.js +7 -0
  17. package/dist/embed/index.js.map +1 -0
  18. package/dist/embed/libreapps-checkout.d.ts +32 -0
  19. package/dist/embed/libreapps-checkout.js +131 -0
  20. package/dist/embed/libreapps-checkout.js.map +1 -0
  21. package/dist/hooks/index.d.ts +5 -0
  22. package/dist/hooks/index.js +5 -0
  23. package/dist/hooks/index.js.map +1 -0
  24. package/dist/hooks/use-checkout.d.ts +42 -0
  25. package/dist/hooks/use-checkout.js +168 -0
  26. package/dist/hooks/use-checkout.js.map +1 -0
  27. package/dist/index.d.ts +48 -0
  28. package/dist/index.js +50 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/types.d.ts +219 -0
  31. package/dist/types.js +7 -0
  32. package/dist/types.js.map +1 -0
  33. package/elements/checkout-form.tsx +543 -0
  34. package/elements/index.ts +8 -0
  35. package/embed/index.ts +7 -0
  36. package/embed/libreapps-checkout.ts +172 -0
  37. package/hooks/index.ts +6 -0
  38. package/hooks/use-checkout.tsx +244 -0
  39. package/index.ts +70 -0
  40. package/package.json +57 -0
  41. package/tsconfig.json +15 -0
  42. package/types.ts +301 -0
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @libreapps/checkout - Embed Script
3
+ *
4
+ * Drop-in checkout button/widget for any website
5
+ * Similar to Stripe's checkout.js
6
+ *
7
+ * Usage:
8
+ * <script src="https://js.libreapps.com/checkout.js"></script>
9
+ * <button data-libreapps-checkout data-libreapps-key="pk_live_xxx" data-libreapps-amount="1999" data-libreapps-currency="usd">
10
+ * Pay $19.99
11
+ * </button>
12
+ */
13
+
14
+ import { LibreAppsCheckout } from '../client'
15
+ import type { CheckoutOptions, CreateSessionParams } from '../types'
16
+
17
+ interface LibreAppsCheckoutEmbed {
18
+ /** Initialize with API key */
19
+ init: (apiKey: string, options?: Partial<CheckoutOptions>) => void
20
+
21
+ /** Create checkout session and redirect */
22
+ checkout: (params: CreateSessionParams) => Promise<void>
23
+
24
+ /** Open checkout in popup */
25
+ popup: (params: CreateSessionParams) => Promise<Window | null>
26
+
27
+ /** Create checkout client */
28
+ createClient: (options: CheckoutOptions) => LibreAppsCheckout
29
+ }
30
+
31
+ declare global {
32
+ interface Window {
33
+ LibreAppsCheckout: LibreAppsCheckoutEmbed
34
+ }
35
+ }
36
+
37
+ // Global state
38
+ let globalClient: LibreAppsCheckout | null = null
39
+ let globalApiKey: string | null = null
40
+ let globalOptions: Partial<CheckoutOptions> = {}
41
+
42
+ const LibreAppsCheckoutEmbed: LibreAppsCheckoutEmbed = {
43
+ init(apiKey: string, options: Partial<CheckoutOptions> = {}) {
44
+ globalApiKey = apiKey
45
+ globalOptions = options
46
+ globalClient = new LibreAppsCheckout({ apiKey, ...options })
47
+
48
+ // Auto-bind buttons with data attributes
49
+ if (typeof document !== 'undefined') {
50
+ document.addEventListener('DOMContentLoaded', bindCheckoutButtons)
51
+ // Also bind immediately if DOM is ready
52
+ if (document.readyState !== 'loading') {
53
+ bindCheckoutButtons()
54
+ }
55
+ }
56
+ },
57
+
58
+ async checkout(params: CreateSessionParams) {
59
+ if (!globalClient) {
60
+ throw new Error('LibreAppsCheckout not initialized. Call LibreAppsCheckout.init(apiKey) first.')
61
+ }
62
+
63
+ const session = await globalClient.createSession(params)
64
+ globalClient.redirectToCheckout(session.id)
65
+ },
66
+
67
+ async popup(params: CreateSessionParams) {
68
+ if (!globalClient) {
69
+ throw new Error('LibreAppsCheckout not initialized. Call LibreAppsCheckout.init(apiKey) first.')
70
+ }
71
+
72
+ const session = await globalClient.createSession(params)
73
+ return globalClient.openPopup(session.id)
74
+ },
75
+
76
+ createClient(options: CheckoutOptions) {
77
+ return new LibreAppsCheckout(options)
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Bind click handlers to elements with data-libreapps-checkout attribute
83
+ */
84
+ function bindCheckoutButtons() {
85
+ const buttons = document.querySelectorAll('[data-libreapps-checkout]')
86
+
87
+ buttons.forEach(button => {
88
+ if (button.getAttribute('data-libreapps-bound')) return
89
+ button.setAttribute('data-libreapps-bound', 'true')
90
+
91
+ button.addEventListener('click', async (e) => {
92
+ e.preventDefault()
93
+
94
+ const el = e.currentTarget as HTMLElement
95
+
96
+ // Get config from data attributes
97
+ const apiKey = el.getAttribute('data-libreapps-key') || globalApiKey
98
+ if (!apiKey) {
99
+ console.error('LibreAppsCheckout: No API key provided')
100
+ return
101
+ }
102
+
103
+ // Parse line items from data attributes
104
+ const amount = parseInt(el.getAttribute('data-libreapps-amount') || '0', 10)
105
+ const currency = el.getAttribute('data-libreapps-currency') || 'usd'
106
+ const name = el.getAttribute('data-libreapps-name') || 'Purchase'
107
+ const description = el.getAttribute('data-libreapps-description') || undefined
108
+ const imageUrl = el.getAttribute('data-libreapps-image') || undefined
109
+ const productId = el.getAttribute('data-libreapps-product') || undefined
110
+ const sku = el.getAttribute('data-libreapps-sku') || undefined
111
+ const quantity = parseInt(el.getAttribute('data-libreapps-quantity') || '1', 10)
112
+
113
+ // Success/cancel URLs
114
+ const successUrl = el.getAttribute('data-libreapps-success-url') || undefined
115
+ const cancelUrl = el.getAttribute('data-libreapps-cancel-url') || undefined
116
+
117
+ // Popup mode
118
+ const usePopup = el.hasAttribute('data-libreapps-popup')
119
+
120
+ // Create or use existing client
121
+ const client = globalClient || new LibreAppsCheckout({
122
+ apiKey,
123
+ ...globalOptions
124
+ })
125
+
126
+ try {
127
+ // Disable button
128
+ el.setAttribute('disabled', 'true')
129
+ const originalText = el.textContent
130
+ el.textContent = 'Loading...'
131
+
132
+ const session = await client.createSession({
133
+ lineItems: [{
134
+ productId,
135
+ sku,
136
+ name,
137
+ description,
138
+ imageUrl,
139
+ quantity,
140
+ unitPrice: amount,
141
+ currency
142
+ }],
143
+ successUrl,
144
+ cancelUrl,
145
+ currency
146
+ })
147
+
148
+ if (usePopup) {
149
+ client.openPopup(session.id)
150
+ el.removeAttribute('disabled')
151
+ el.textContent = originalText
152
+ } else {
153
+ client.redirectToCheckout(session.id)
154
+ }
155
+ } catch (error) {
156
+ console.error('LibreAppsCheckout error:', error)
157
+ el.removeAttribute('disabled')
158
+ el.textContent = 'Error - Try Again'
159
+ }
160
+ })
161
+ })
162
+ }
163
+
164
+ // Export for bundler usage
165
+ export { LibreAppsCheckoutEmbed }
166
+
167
+ // Attach to window for script tag usage
168
+ if (typeof window !== 'undefined') {
169
+ window.LibreAppsCheckout = LibreAppsCheckoutEmbed
170
+ }
171
+
172
+ export default LibreAppsCheckoutEmbed
package/hooks/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @libreapps/checkout/hooks
3
+ */
4
+
5
+ export { CheckoutProvider, useCheckout } from './use-checkout'
6
+ export type { CheckoutProviderProps } from './use-checkout'
@@ -0,0 +1,244 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * @libreapps/checkout - useCheckout hook
5
+ *
6
+ * React hook for managing checkout state
7
+ */
8
+
9
+ import { useState, useCallback, useEffect, createContext, useContext } from 'react'
10
+ import type { ReactNode } from 'react'
11
+ import { LibreAppsCheckout } from '../client'
12
+ import type {
13
+ CheckoutOptions,
14
+ CheckoutSession,
15
+ CheckoutState,
16
+ CheckoutStep,
17
+ CheckoutAddress,
18
+ CheckoutResult,
19
+ CheckoutError,
20
+ CreateSessionParams
21
+ } from '../types'
22
+
23
+ interface CheckoutContextValue extends CheckoutState {
24
+ /** Checkout client instance */
25
+ checkout: LibreAppsCheckout | null
26
+
27
+ /** Create a new session */
28
+ createSession: (params: CreateSessionParams) => Promise<CheckoutSession>
29
+
30
+ /** Load existing session */
31
+ loadSession: (sessionId: string) => Promise<CheckoutSession>
32
+
33
+ /** Update shipping address */
34
+ updateShipping: (address: CheckoutAddress) => Promise<void>
35
+
36
+ /** Update billing address */
37
+ updateBilling: (address: CheckoutAddress) => Promise<void>
38
+
39
+ /** Apply promo code */
40
+ applyPromo: (code: string) => Promise<void>
41
+
42
+ /** Confirm payment */
43
+ confirmPayment: (paymentMethod: Parameters<LibreAppsCheckout['confirmPayment']>[1]) => Promise<CheckoutResult>
44
+
45
+ /** Cancel checkout */
46
+ cancel: () => Promise<void>
47
+
48
+ /** Go to step */
49
+ goToStep: (step: CheckoutStep) => void
50
+
51
+ /** Go to next step */
52
+ nextStep: () => void
53
+
54
+ /** Go to previous step */
55
+ prevStep: () => void
56
+
57
+ /** Redirect to hosted checkout */
58
+ redirectToHosted: () => void
59
+
60
+ /** Open popup checkout */
61
+ openPopup: () => Window | null
62
+ }
63
+
64
+ const CheckoutContext = createContext<CheckoutContextValue | null>(null)
65
+
66
+ const STEP_ORDER: CheckoutStep[] = ['cart', 'shipping', 'payment', 'confirmation']
67
+
68
+ export interface CheckoutProviderProps {
69
+ /** Checkout options */
70
+ options: CheckoutOptions
71
+
72
+ /** Initial session ID to load */
73
+ sessionId?: string
74
+
75
+ /** Children */
76
+ children: ReactNode
77
+ }
78
+
79
+ export function CheckoutProvider({ options, sessionId, children }: CheckoutProviderProps) {
80
+ const [checkout] = useState(() => new LibreAppsCheckout(options))
81
+ const [state, setState] = useState<CheckoutState>({
82
+ step: 'cart',
83
+ session: null,
84
+ isLoading: false,
85
+ error: null,
86
+ isProcessing: false
87
+ })
88
+
89
+ // Load session on mount if sessionId provided
90
+ useEffect(() => {
91
+ if (sessionId) {
92
+ loadSession(sessionId)
93
+ }
94
+ }, [sessionId])
95
+
96
+ const createSession = useCallback(async (params: CreateSessionParams) => {
97
+ setState(s => ({ ...s, isLoading: true, error: null }))
98
+ try {
99
+ const session = await checkout.createSession(params)
100
+ setState(s => ({ ...s, session, isLoading: false }))
101
+ return session
102
+ } catch (err) {
103
+ const error = err as CheckoutError
104
+ setState(s => ({ ...s, error, isLoading: false }))
105
+ throw error
106
+ }
107
+ }, [checkout])
108
+
109
+ const loadSession = useCallback(async (id: string) => {
110
+ setState(s => ({ ...s, isLoading: true, error: null }))
111
+ try {
112
+ const session = await checkout.retrieveSession(id)
113
+ setState(s => ({ ...s, session, isLoading: false }))
114
+ return session
115
+ } catch (err) {
116
+ const error = err as CheckoutError
117
+ setState(s => ({ ...s, error, isLoading: false }))
118
+ throw error
119
+ }
120
+ }, [checkout])
121
+
122
+ const updateShipping = useCallback(async (address: CheckoutAddress) => {
123
+ if (!state.session) throw new Error('No session')
124
+ setState(s => ({ ...s, isLoading: true, error: null }))
125
+ try {
126
+ const session = await checkout.updateShipping(state.session.id, address)
127
+ setState(s => ({ ...s, session, isLoading: false }))
128
+ } catch (err) {
129
+ const error = err as CheckoutError
130
+ setState(s => ({ ...s, error, isLoading: false }))
131
+ throw error
132
+ }
133
+ }, [checkout, state.session])
134
+
135
+ const updateBilling = useCallback(async (address: CheckoutAddress) => {
136
+ if (!state.session) throw new Error('No session')
137
+ setState(s => ({ ...s, isLoading: true, error: null }))
138
+ try {
139
+ const session = await checkout.updateBilling(state.session.id, address)
140
+ setState(s => ({ ...s, session, isLoading: false }))
141
+ } catch (err) {
142
+ const error = err as CheckoutError
143
+ setState(s => ({ ...s, error, isLoading: false }))
144
+ throw error
145
+ }
146
+ }, [checkout, state.session])
147
+
148
+ const applyPromo = useCallback(async (code: string) => {
149
+ if (!state.session) throw new Error('No session')
150
+ setState(s => ({ ...s, isLoading: true, error: null }))
151
+ try {
152
+ const session = await checkout.applyPromoCode(state.session.id, code)
153
+ setState(s => ({ ...s, session, isLoading: false }))
154
+ } catch (err) {
155
+ const error = err as CheckoutError
156
+ setState(s => ({ ...s, error, isLoading: false }))
157
+ throw error
158
+ }
159
+ }, [checkout, state.session])
160
+
161
+ const confirmPayment = useCallback(async (paymentMethod: Parameters<LibreAppsCheckout['confirmPayment']>[1]) => {
162
+ if (!state.session) throw new Error('No session')
163
+ setState(s => ({ ...s, isProcessing: true, error: null }))
164
+ try {
165
+ const result = await checkout.confirmPayment(state.session.id, paymentMethod)
166
+ setState(s => ({ ...s, isProcessing: false, step: 'confirmation' }))
167
+ return result
168
+ } catch (err) {
169
+ const error = err as CheckoutError
170
+ setState(s => ({ ...s, error, isProcessing: false }))
171
+ throw error
172
+ }
173
+ }, [checkout, state.session])
174
+
175
+ const cancel = useCallback(async () => {
176
+ if (!state.session) return
177
+ await checkout.cancelSession(state.session.id)
178
+ setState(s => ({ ...s, session: null, step: 'cart' }))
179
+ }, [checkout, state.session])
180
+
181
+ const goToStep = useCallback((step: CheckoutStep) => {
182
+ setState(s => ({ ...s, step }))
183
+ }, [])
184
+
185
+ const nextStep = useCallback(() => {
186
+ const currentIndex = STEP_ORDER.indexOf(state.step)
187
+ if (currentIndex < STEP_ORDER.length - 1) {
188
+ setState(s => ({ ...s, step: STEP_ORDER[currentIndex + 1] }))
189
+ }
190
+ }, [state.step])
191
+
192
+ const prevStep = useCallback(() => {
193
+ const currentIndex = STEP_ORDER.indexOf(state.step)
194
+ if (currentIndex > 0) {
195
+ setState(s => ({ ...s, step: STEP_ORDER[currentIndex - 1] }))
196
+ }
197
+ }, [state.step])
198
+
199
+ const redirectToHosted = useCallback(() => {
200
+ if (state.session) {
201
+ checkout.redirectToCheckout(state.session.id)
202
+ }
203
+ }, [checkout, state.session])
204
+
205
+ const openPopup = useCallback(() => {
206
+ if (state.session) {
207
+ return checkout.openPopup(state.session.id)
208
+ }
209
+ return null
210
+ }, [checkout, state.session])
211
+
212
+ const value: CheckoutContextValue = {
213
+ ...state,
214
+ checkout,
215
+ createSession,
216
+ loadSession,
217
+ updateShipping,
218
+ updateBilling,
219
+ applyPromo,
220
+ confirmPayment,
221
+ cancel,
222
+ goToStep,
223
+ nextStep,
224
+ prevStep,
225
+ redirectToHosted,
226
+ openPopup
227
+ }
228
+
229
+ return (
230
+ <CheckoutContext.Provider value={value}>
231
+ {children}
232
+ </CheckoutContext.Provider>
233
+ )
234
+ }
235
+
236
+ export function useCheckout(): CheckoutContextValue {
237
+ const context = useContext(CheckoutContext)
238
+ if (!context) {
239
+ throw new Error('useCheckout must be used within a CheckoutProvider')
240
+ }
241
+ return context
242
+ }
243
+
244
+ export default useCheckout
package/index.ts ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @libreapps/checkout
3
+ *
4
+ * Embeddable checkout widget for LibreApps Commerce
5
+ * Similar to Stripe Checkout
6
+ *
7
+ * Usage:
8
+ *
9
+ * 1. React Integration:
10
+ * ```tsx
11
+ * import { CheckoutProvider, CheckoutForm, useCheckout } from '@libreapps/checkout'
12
+ *
13
+ * function App() {
14
+ * return (
15
+ * <CheckoutProvider options={{ apiKey: 'pk_live_xxx' }}>
16
+ * <CheckoutForm />
17
+ * </CheckoutProvider>
18
+ * )
19
+ * }
20
+ * ```
21
+ *
22
+ * 2. Vanilla JS / Redirect:
23
+ * ```ts
24
+ * import { createCheckout } from '@libreapps/checkout'
25
+ *
26
+ * const checkout = createCheckout({ apiKey: 'pk_live_xxx' })
27
+ * const session = await checkout.createSession({
28
+ * lineItems: [{ name: 'Product', unitPrice: 1999, quantity: 1 }]
29
+ * })
30
+ * checkout.redirectToCheckout(session.id)
31
+ * ```
32
+ *
33
+ * 3. Embed Script (no build step):
34
+ * ```html
35
+ * <script src="https://js.libreapps.com/checkout.js"></script>
36
+ * <button data-libreapps-checkout data-libreapps-key="pk_live_xxx" data-libreapps-amount="1999">
37
+ * Pay $19.99
38
+ * </button>
39
+ * <script>
40
+ * LibreAppsCheckout.init('pk_live_xxx')
41
+ * </script>
42
+ * ```
43
+ */
44
+
45
+ // Types
46
+ export type {
47
+ CheckoutOptions,
48
+ CheckoutAppearance,
49
+ CheckoutSession,
50
+ CheckoutLineItem,
51
+ CheckoutCustomer,
52
+ CheckoutAddress,
53
+ CheckoutPaymentMethod,
54
+ CheckoutResult,
55
+ CheckoutError,
56
+ CreateSessionParams,
57
+ ElementsOptions,
58
+ CheckoutStep,
59
+ CheckoutState
60
+ } from './types'
61
+
62
+ // Client
63
+ export { LibreAppsCheckout, createCheckout } from './client'
64
+
65
+ // Hooks
66
+ export { CheckoutProvider, useCheckout } from './hooks'
67
+ export type { CheckoutProviderProps } from './hooks'
68
+
69
+ // Elements
70
+ export { CheckoutForm } from './elements'
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@libreapps/checkout",
3
+ "version": "2.0.1",
4
+ "description": "Embeddable checkout widget for LibreApps Commerce - Stripe-like checkout experience",
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org/",
7
+ "access": "public",
8
+ "scope": "@libreapps"
9
+ },
10
+ "author": "LibreApps Contributors",
11
+ "license": "BSD-3-Clause",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/libre-apps/UI.git",
15
+ "directory": "pkg/checkout"
16
+ },
17
+ "keywords": [
18
+ "checkout",
19
+ "payments",
20
+ "commerce",
21
+ "ecommerce",
22
+ "libreapps",
23
+ "libreapps",
24
+ "stripe",
25
+ "widget"
26
+ ],
27
+ "main": "index.ts",
28
+ "exports": {
29
+ ".": "./index.ts",
30
+ "./embed": "./embed/index.ts",
31
+ "./elements": "./elements/index.ts",
32
+ "./hooks": "./hooks/index.ts"
33
+ },
34
+ "dependencies": {},
35
+ "peerDependencies": {
36
+ "lucide-react": ">=0.456.0",
37
+ "mobx": "^6.12.3",
38
+ "mobx-react-lite": "^4.0.7",
39
+ "next": ">=14.2.16",
40
+ "react": "^18.0.0 || ^19.0.0",
41
+ "react-dom": "^18.0.0 || ^19.0.0",
42
+ "@libreapps/auth": "3.0.1",
43
+ "@libreapps/commerce": "7.5.1",
44
+ "@libreapps/ui": "5.4.1"
45
+ },
46
+ "devDependencies": {
47
+ "@types/react": "^19.0.0",
48
+ "@types/react-dom": "^19.0.0",
49
+ "typescript": "5.6.3"
50
+ },
51
+ "scripts": {
52
+ "lat": "npm show @libreapps/checkout version",
53
+ "pub": "npm publish",
54
+ "tc": "tsc",
55
+ "build": "tsc --outDir dist --declaration --emitDeclarationOnly false --module ESNext --esModuleInterop true --skipLibCheck true --sourceMap true"
56
+ }
57
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../tsconfig.libreapps.base.json",
3
+ "include": [
4
+ "**/*.ts",
5
+ "**/*.tsx"
6
+ ],
7
+ "exclude": [
8
+ "node_modules",
9
+ "dist"
10
+ ],
11
+ "compilerOptions": {
12
+ "noEmit": false,
13
+ "declaration": true
14
+ }
15
+ }