@snap-pay/react 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/CHANGELOG.md +11 -0
- package/README.md +80 -0
- package/dist/index.cjs +214 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +200 -0
- package/package.json +57 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
Initial public release.
|
|
6
|
+
|
|
7
|
+
- `<SnapProvider>` — context provider carrying the `Snap` instance, Elements handle, and client secret.
|
|
8
|
+
- Hooks: `useSnap`, `useElements`, `useClientSecret`.
|
|
9
|
+
- React wrappers for every Element: `PaymentElement`, `WalletElement`, `CardElement`, `ExpressCheckoutElement`, `PaymentMethodSelector`, `BillingAddressElement`, `CustomerElement`, `OtpElement`, `QrElement`.
|
|
10
|
+
- Shared `useElementLifecycle` / `useElementEvent` hooks keep event wiring stable across re-renders.
|
|
11
|
+
- Peer dependency `react >= 18` — supports React 18 and 19.
|
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @snap-pay/react
|
|
2
|
+
|
|
3
|
+
React bindings for SNAP Elements — provider, hooks, and component wrappers.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun add @snap-pay/js @snap-pay/elements @snap-pay/react
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Snap } from "@snap-pay/js";
|
|
13
|
+
import "@snap-pay/elements"; // side effect: registers every element
|
|
14
|
+
import {
|
|
15
|
+
SnapProvider,
|
|
16
|
+
PaymentElement,
|
|
17
|
+
BillingAddressElement,
|
|
18
|
+
CustomerElement,
|
|
19
|
+
useSnap,
|
|
20
|
+
useElements,
|
|
21
|
+
useClientSecret,
|
|
22
|
+
} from "@snap-pay/react";
|
|
23
|
+
|
|
24
|
+
const snap = new Snap(process.env.NEXT_PUBLIC_SNAP_PK!);
|
|
25
|
+
|
|
26
|
+
export function CheckoutPage({ clientSecret }: { clientSecret: string }) {
|
|
27
|
+
return (
|
|
28
|
+
<SnapProvider snap={snap} clientSecret={clientSecret}>
|
|
29
|
+
<CustomerElement />
|
|
30
|
+
<BillingAddressElement defaultCountry="Somalia" />
|
|
31
|
+
<PaymentElement />
|
|
32
|
+
<ConfirmButton />
|
|
33
|
+
</SnapProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ConfirmButton() {
|
|
38
|
+
const snap = useSnap();
|
|
39
|
+
const elements = useElements();
|
|
40
|
+
const clientSecret = useClientSecret();
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
onClick={async () => {
|
|
44
|
+
const result = await snap.confirmPayment({ elements, clientSecret });
|
|
45
|
+
if (result.status === "succeeded") window.location.href = "/thanks";
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
Pay
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Components
|
|
55
|
+
|
|
56
|
+
Every wrapper accepts the standard event props (`onReady`, `onChange`, `onFocus`, `onBlur`, `onFailed`) plus `style` and `className` for the outer container.
|
|
57
|
+
|
|
58
|
+
| Component | Notes |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `<PaymentElement />` | Umbrella — auto-picks Wallet + Card |
|
|
61
|
+
| `<WalletElement />` | Mobile-wallet radio buttons |
|
|
62
|
+
| `<CardElement />` | Card fields inside a cross-origin iframe |
|
|
63
|
+
| `<ExpressCheckoutElement />` | Prominent one-tap buttons; adds `onSubmit` for the click event |
|
|
64
|
+
| `<PaymentMethodSelector />` | Tabbed Wallet/Card switcher |
|
|
65
|
+
| `<BillingAddressElement defaultCountry={...} />` | Address form |
|
|
66
|
+
| `<CustomerElement />` | Name / email / phone |
|
|
67
|
+
| `<OtpElement length={6} />` | Multi-cell OTP input |
|
|
68
|
+
| `<QrElement />` | Placeholder — emits `failed` on mount until the real QR handshake ships |
|
|
69
|
+
|
|
70
|
+
## Hooks
|
|
71
|
+
|
|
72
|
+
- `useSnap()` — the `Snap` instance from the provider
|
|
73
|
+
- `useElements()` — the shared Elements handle
|
|
74
|
+
- `useClientSecret()` — the client secret
|
|
75
|
+
|
|
76
|
+
All three throw if called outside a `<SnapProvider>`.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
require('@snap-pay/js');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/provider.tsx
|
|
8
|
+
var SnapContext = react.createContext(null);
|
|
9
|
+
function SnapProvider(props) {
|
|
10
|
+
const { snap, clientSecret, appearance, children } = props;
|
|
11
|
+
const appearanceKey = appearance ? JSON.stringify(appearance) : "";
|
|
12
|
+
const value = react.useMemo(
|
|
13
|
+
() => ({
|
|
14
|
+
snap,
|
|
15
|
+
elements: snap.elements({ clientSecret, appearance }),
|
|
16
|
+
clientSecret
|
|
17
|
+
}),
|
|
18
|
+
// Intentional: appearanceKey stands in for appearance identity.
|
|
19
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
20
|
+
[snap, clientSecret, appearanceKey]
|
|
21
|
+
);
|
|
22
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SnapContext.Provider, { value, children });
|
|
23
|
+
}
|
|
24
|
+
function useSnap() {
|
|
25
|
+
const ctx = react.useContext(SnapContext);
|
|
26
|
+
if (!ctx) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"useSnap must be called inside a <SnapProvider>."
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return ctx.snap;
|
|
32
|
+
}
|
|
33
|
+
function useElements() {
|
|
34
|
+
const ctx = react.useContext(SnapContext);
|
|
35
|
+
if (!ctx) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"useElements must be called inside a <SnapProvider>."
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return ctx.elements;
|
|
41
|
+
}
|
|
42
|
+
function useClientSecret() {
|
|
43
|
+
const ctx = react.useContext(SnapContext);
|
|
44
|
+
if (!ctx) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"useClientSecret must be called inside a <SnapProvider>."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return ctx.clientSecret;
|
|
50
|
+
}
|
|
51
|
+
function WalletElement(props) {
|
|
52
|
+
const elements = useElements();
|
|
53
|
+
const containerRef = react.useRef(null);
|
|
54
|
+
const elementRef = react.useRef(null);
|
|
55
|
+
react.useEffect(() => {
|
|
56
|
+
const container = containerRef.current;
|
|
57
|
+
if (!container) return;
|
|
58
|
+
const el = elements.create("wallet");
|
|
59
|
+
elementRef.current = el;
|
|
60
|
+
el.mount(container);
|
|
61
|
+
return () => {
|
|
62
|
+
el.destroy();
|
|
63
|
+
elementRef.current = null;
|
|
64
|
+
};
|
|
65
|
+
}, [elements]);
|
|
66
|
+
useElementEvent(elementRef, "ready", props.onReady);
|
|
67
|
+
useElementEvent(elementRef, "change", props.onChange);
|
|
68
|
+
useElementEvent(elementRef, "focus", props.onFocus);
|
|
69
|
+
useElementEvent(elementRef, "blur", props.onBlur);
|
|
70
|
+
useElementEvent(elementRef, "failed", props.onFailed);
|
|
71
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
72
|
+
}
|
|
73
|
+
function useElementEvent(elementRef, event, handler, _unused) {
|
|
74
|
+
react.useEffect(() => {
|
|
75
|
+
const el = elementRef.current;
|
|
76
|
+
if (!el) return;
|
|
77
|
+
if (!handler) return;
|
|
78
|
+
el.on(event, handler);
|
|
79
|
+
return () => el.off(event, handler);
|
|
80
|
+
}, [elementRef, event, handler]);
|
|
81
|
+
}
|
|
82
|
+
function CardElement(props) {
|
|
83
|
+
const elements = useElements();
|
|
84
|
+
const containerRef = react.useRef(null);
|
|
85
|
+
const elementRef = react.useRef(null);
|
|
86
|
+
react.useEffect(() => {
|
|
87
|
+
const container = containerRef.current;
|
|
88
|
+
if (!container) return;
|
|
89
|
+
const el = elements.create("card");
|
|
90
|
+
elementRef.current = el;
|
|
91
|
+
el.mount(container);
|
|
92
|
+
return () => {
|
|
93
|
+
el.destroy();
|
|
94
|
+
elementRef.current = null;
|
|
95
|
+
};
|
|
96
|
+
}, [elements]);
|
|
97
|
+
useElementEvent2(elementRef, "ready", props.onReady);
|
|
98
|
+
useElementEvent2(elementRef, "change", props.onChange);
|
|
99
|
+
useElementEvent2(elementRef, "focus", props.onFocus);
|
|
100
|
+
useElementEvent2(elementRef, "blur", props.onBlur);
|
|
101
|
+
useElementEvent2(elementRef, "failed", props.onFailed);
|
|
102
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
103
|
+
}
|
|
104
|
+
function useElementEvent2(elementRef, event, handler) {
|
|
105
|
+
react.useEffect(() => {
|
|
106
|
+
const el = elementRef.current;
|
|
107
|
+
if (!el) return;
|
|
108
|
+
if (!handler) return;
|
|
109
|
+
el.on(event, handler);
|
|
110
|
+
return () => el.off(event, handler);
|
|
111
|
+
}, [elementRef, event, handler]);
|
|
112
|
+
}
|
|
113
|
+
function useElementEvent3(elementRef, event, handler) {
|
|
114
|
+
react.useEffect(() => {
|
|
115
|
+
const el = elementRef.current;
|
|
116
|
+
if (!el) return;
|
|
117
|
+
if (!handler) return;
|
|
118
|
+
el.on(event, handler);
|
|
119
|
+
return () => el.off(event, handler);
|
|
120
|
+
}, [elementRef, event, handler]);
|
|
121
|
+
}
|
|
122
|
+
function useElementLifecycle(type, options) {
|
|
123
|
+
const elements = useElements();
|
|
124
|
+
const containerRef = react.useRef(null);
|
|
125
|
+
const elementRef = react.useRef(null);
|
|
126
|
+
const optionsJson = options ? JSON.stringify(options) : "";
|
|
127
|
+
react.useEffect(() => {
|
|
128
|
+
const container = containerRef.current;
|
|
129
|
+
if (!container) return;
|
|
130
|
+
const el = elements.create(type, options ?? {});
|
|
131
|
+
elementRef.current = el;
|
|
132
|
+
el.mount(container);
|
|
133
|
+
return () => {
|
|
134
|
+
el.destroy();
|
|
135
|
+
elementRef.current = null;
|
|
136
|
+
};
|
|
137
|
+
}, [elements, type, optionsJson]);
|
|
138
|
+
return { containerRef, elementRef };
|
|
139
|
+
}
|
|
140
|
+
function PaymentElement(props) {
|
|
141
|
+
const { containerRef, elementRef } = useElementLifecycle("payment");
|
|
142
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
143
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
144
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
145
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
146
|
+
useElementEvent3(elementRef, "failed", props.onFailed);
|
|
147
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
148
|
+
}
|
|
149
|
+
function ExpressCheckoutElement(props) {
|
|
150
|
+
const { containerRef, elementRef } = useElementLifecycle("express");
|
|
151
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
152
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
153
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
154
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
155
|
+
useElementEvent3(elementRef, "failed", props.onFailed);
|
|
156
|
+
useElementEvent3(elementRef, "submit", props.onSubmit);
|
|
157
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
158
|
+
}
|
|
159
|
+
function PaymentMethodSelector(props) {
|
|
160
|
+
const { containerRef, elementRef } = useElementLifecycle("paymentMethodSelector");
|
|
161
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
162
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
163
|
+
useElementEvent3(elementRef, "failed", props.onFailed);
|
|
164
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
165
|
+
}
|
|
166
|
+
function BillingAddressElement(props) {
|
|
167
|
+
const { containerRef, elementRef } = useElementLifecycle(
|
|
168
|
+
"billingAddress",
|
|
169
|
+
{ defaultCountry: props.defaultCountry }
|
|
170
|
+
);
|
|
171
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
172
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
173
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
174
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
175
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
176
|
+
}
|
|
177
|
+
function CustomerElement(props) {
|
|
178
|
+
const { containerRef, elementRef } = useElementLifecycle("customer");
|
|
179
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
180
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
181
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
182
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
183
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
184
|
+
}
|
|
185
|
+
function OtpElement(props) {
|
|
186
|
+
const { containerRef, elementRef } = useElementLifecycle("otp", {
|
|
187
|
+
otpLength: props.length
|
|
188
|
+
});
|
|
189
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
190
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
191
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
192
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
193
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
194
|
+
}
|
|
195
|
+
function QrElement(props) {
|
|
196
|
+
const { containerRef, elementRef } = useElementLifecycle("qr");
|
|
197
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
198
|
+
useElementEvent3(elementRef, "failed", props.onFailed);
|
|
199
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
exports.BillingAddressElement = BillingAddressElement;
|
|
203
|
+
exports.CardElement = CardElement;
|
|
204
|
+
exports.CustomerElement = CustomerElement;
|
|
205
|
+
exports.ExpressCheckoutElement = ExpressCheckoutElement;
|
|
206
|
+
exports.OtpElement = OtpElement;
|
|
207
|
+
exports.PaymentElement = PaymentElement;
|
|
208
|
+
exports.PaymentMethodSelector = PaymentMethodSelector;
|
|
209
|
+
exports.QrElement = QrElement;
|
|
210
|
+
exports.SnapProvider = SnapProvider;
|
|
211
|
+
exports.WalletElement = WalletElement;
|
|
212
|
+
exports.useClientSecret = useClientSecret;
|
|
213
|
+
exports.useElements = useElements;
|
|
214
|
+
exports.useSnap = useSnap;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
+
import { Snap, SnapElements } from '@snap-pay/js';
|
|
4
|
+
import { Appearance, ElementEventMap } from '@snap-pay/types';
|
|
5
|
+
|
|
6
|
+
interface SnapProviderProps {
|
|
7
|
+
snap: Snap;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
appearance?: Appearance;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
declare function SnapProvider(props: SnapProviderProps): react.JSX.Element;
|
|
13
|
+
declare function useSnap(): Snap;
|
|
14
|
+
declare function useElements(): SnapElements;
|
|
15
|
+
declare function useClientSecret(): string;
|
|
16
|
+
|
|
17
|
+
interface WalletElementProps {
|
|
18
|
+
onReady?: () => void;
|
|
19
|
+
onChange?: (payload: ElementEventMap["change"]) => void;
|
|
20
|
+
onFocus?: () => void;
|
|
21
|
+
onBlur?: () => void;
|
|
22
|
+
onFailed?: (payload: ElementEventMap["failed"]) => void;
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
declare function WalletElement(props: WalletElementProps): react.JSX.Element;
|
|
27
|
+
|
|
28
|
+
interface CardElementProps {
|
|
29
|
+
onReady?: () => void;
|
|
30
|
+
onChange?: (payload: ElementEventMap["change"]) => void;
|
|
31
|
+
onFocus?: () => void;
|
|
32
|
+
onBlur?: () => void;
|
|
33
|
+
onFailed?: (payload: ElementEventMap["failed"]) => void;
|
|
34
|
+
style?: CSSProperties;
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
declare function CardElement(props: CardElementProps): react.JSX.Element;
|
|
38
|
+
|
|
39
|
+
interface CommonElementProps {
|
|
40
|
+
onReady?: () => void;
|
|
41
|
+
onChange?: (payload: ElementEventMap["change"]) => void;
|
|
42
|
+
onFocus?: () => void;
|
|
43
|
+
onBlur?: () => void;
|
|
44
|
+
onFailed?: (payload: ElementEventMap["failed"]) => void;
|
|
45
|
+
onSubmit?: () => void;
|
|
46
|
+
style?: CSSProperties;
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type PaymentElementProps = CommonElementProps;
|
|
51
|
+
declare function PaymentElement(props: PaymentElementProps): react.JSX.Element;
|
|
52
|
+
|
|
53
|
+
type ExpressCheckoutElementProps = CommonElementProps;
|
|
54
|
+
declare function ExpressCheckoutElement(props: ExpressCheckoutElementProps): react.JSX.Element;
|
|
55
|
+
|
|
56
|
+
type PaymentMethodSelectorProps = CommonElementProps;
|
|
57
|
+
declare function PaymentMethodSelector(props: PaymentMethodSelectorProps): react.JSX.Element;
|
|
58
|
+
|
|
59
|
+
interface BillingAddressElementProps extends CommonElementProps {
|
|
60
|
+
defaultCountry?: string;
|
|
61
|
+
}
|
|
62
|
+
declare function BillingAddressElement(props: BillingAddressElementProps): react.JSX.Element;
|
|
63
|
+
|
|
64
|
+
type CustomerElementProps = CommonElementProps;
|
|
65
|
+
declare function CustomerElement(props: CustomerElementProps): react.JSX.Element;
|
|
66
|
+
|
|
67
|
+
interface OtpElementProps extends CommonElementProps {
|
|
68
|
+
length?: number;
|
|
69
|
+
}
|
|
70
|
+
declare function OtpElement(props: OtpElementProps): react.JSX.Element;
|
|
71
|
+
|
|
72
|
+
type QrElementProps = CommonElementProps;
|
|
73
|
+
declare function QrElement(props: QrElementProps): react.JSX.Element;
|
|
74
|
+
|
|
75
|
+
export { BillingAddressElement, type BillingAddressElementProps, CardElement, type CardElementProps, CustomerElement, type CustomerElementProps, ExpressCheckoutElement, type ExpressCheckoutElementProps, OtpElement, type OtpElementProps, PaymentElement, type PaymentElementProps, PaymentMethodSelector, type PaymentMethodSelectorProps, QrElement, type QrElementProps, SnapProvider, type SnapProviderProps, WalletElement, type WalletElementProps, useClientSecret, useElements, useSnap };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
+
import { Snap, SnapElements } from '@snap-pay/js';
|
|
4
|
+
import { Appearance, ElementEventMap } from '@snap-pay/types';
|
|
5
|
+
|
|
6
|
+
interface SnapProviderProps {
|
|
7
|
+
snap: Snap;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
appearance?: Appearance;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
declare function SnapProvider(props: SnapProviderProps): react.JSX.Element;
|
|
13
|
+
declare function useSnap(): Snap;
|
|
14
|
+
declare function useElements(): SnapElements;
|
|
15
|
+
declare function useClientSecret(): string;
|
|
16
|
+
|
|
17
|
+
interface WalletElementProps {
|
|
18
|
+
onReady?: () => void;
|
|
19
|
+
onChange?: (payload: ElementEventMap["change"]) => void;
|
|
20
|
+
onFocus?: () => void;
|
|
21
|
+
onBlur?: () => void;
|
|
22
|
+
onFailed?: (payload: ElementEventMap["failed"]) => void;
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
declare function WalletElement(props: WalletElementProps): react.JSX.Element;
|
|
27
|
+
|
|
28
|
+
interface CardElementProps {
|
|
29
|
+
onReady?: () => void;
|
|
30
|
+
onChange?: (payload: ElementEventMap["change"]) => void;
|
|
31
|
+
onFocus?: () => void;
|
|
32
|
+
onBlur?: () => void;
|
|
33
|
+
onFailed?: (payload: ElementEventMap["failed"]) => void;
|
|
34
|
+
style?: CSSProperties;
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
declare function CardElement(props: CardElementProps): react.JSX.Element;
|
|
38
|
+
|
|
39
|
+
interface CommonElementProps {
|
|
40
|
+
onReady?: () => void;
|
|
41
|
+
onChange?: (payload: ElementEventMap["change"]) => void;
|
|
42
|
+
onFocus?: () => void;
|
|
43
|
+
onBlur?: () => void;
|
|
44
|
+
onFailed?: (payload: ElementEventMap["failed"]) => void;
|
|
45
|
+
onSubmit?: () => void;
|
|
46
|
+
style?: CSSProperties;
|
|
47
|
+
className?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type PaymentElementProps = CommonElementProps;
|
|
51
|
+
declare function PaymentElement(props: PaymentElementProps): react.JSX.Element;
|
|
52
|
+
|
|
53
|
+
type ExpressCheckoutElementProps = CommonElementProps;
|
|
54
|
+
declare function ExpressCheckoutElement(props: ExpressCheckoutElementProps): react.JSX.Element;
|
|
55
|
+
|
|
56
|
+
type PaymentMethodSelectorProps = CommonElementProps;
|
|
57
|
+
declare function PaymentMethodSelector(props: PaymentMethodSelectorProps): react.JSX.Element;
|
|
58
|
+
|
|
59
|
+
interface BillingAddressElementProps extends CommonElementProps {
|
|
60
|
+
defaultCountry?: string;
|
|
61
|
+
}
|
|
62
|
+
declare function BillingAddressElement(props: BillingAddressElementProps): react.JSX.Element;
|
|
63
|
+
|
|
64
|
+
type CustomerElementProps = CommonElementProps;
|
|
65
|
+
declare function CustomerElement(props: CustomerElementProps): react.JSX.Element;
|
|
66
|
+
|
|
67
|
+
interface OtpElementProps extends CommonElementProps {
|
|
68
|
+
length?: number;
|
|
69
|
+
}
|
|
70
|
+
declare function OtpElement(props: OtpElementProps): react.JSX.Element;
|
|
71
|
+
|
|
72
|
+
type QrElementProps = CommonElementProps;
|
|
73
|
+
declare function QrElement(props: QrElementProps): react.JSX.Element;
|
|
74
|
+
|
|
75
|
+
export { BillingAddressElement, type BillingAddressElementProps, CardElement, type CardElementProps, CustomerElement, type CustomerElementProps, ExpressCheckoutElement, type ExpressCheckoutElementProps, OtpElement, type OtpElementProps, PaymentElement, type PaymentElementProps, PaymentMethodSelector, type PaymentMethodSelectorProps, QrElement, type QrElementProps, SnapProvider, type SnapProviderProps, WalletElement, type WalletElementProps, useClientSecret, useElements, useSnap };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createContext, useMemo, useContext, useRef, useEffect } from 'react';
|
|
2
|
+
import '@snap-pay/js';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/provider.tsx
|
|
6
|
+
var SnapContext = createContext(null);
|
|
7
|
+
function SnapProvider(props) {
|
|
8
|
+
const { snap, clientSecret, appearance, children } = props;
|
|
9
|
+
const appearanceKey = appearance ? JSON.stringify(appearance) : "";
|
|
10
|
+
const value = useMemo(
|
|
11
|
+
() => ({
|
|
12
|
+
snap,
|
|
13
|
+
elements: snap.elements({ clientSecret, appearance }),
|
|
14
|
+
clientSecret
|
|
15
|
+
}),
|
|
16
|
+
// Intentional: appearanceKey stands in for appearance identity.
|
|
17
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18
|
+
[snap, clientSecret, appearanceKey]
|
|
19
|
+
);
|
|
20
|
+
return /* @__PURE__ */ jsx(SnapContext.Provider, { value, children });
|
|
21
|
+
}
|
|
22
|
+
function useSnap() {
|
|
23
|
+
const ctx = useContext(SnapContext);
|
|
24
|
+
if (!ctx) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"useSnap must be called inside a <SnapProvider>."
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return ctx.snap;
|
|
30
|
+
}
|
|
31
|
+
function useElements() {
|
|
32
|
+
const ctx = useContext(SnapContext);
|
|
33
|
+
if (!ctx) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"useElements must be called inside a <SnapProvider>."
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return ctx.elements;
|
|
39
|
+
}
|
|
40
|
+
function useClientSecret() {
|
|
41
|
+
const ctx = useContext(SnapContext);
|
|
42
|
+
if (!ctx) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"useClientSecret must be called inside a <SnapProvider>."
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return ctx.clientSecret;
|
|
48
|
+
}
|
|
49
|
+
function WalletElement(props) {
|
|
50
|
+
const elements = useElements();
|
|
51
|
+
const containerRef = useRef(null);
|
|
52
|
+
const elementRef = useRef(null);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
const container = containerRef.current;
|
|
55
|
+
if (!container) return;
|
|
56
|
+
const el = elements.create("wallet");
|
|
57
|
+
elementRef.current = el;
|
|
58
|
+
el.mount(container);
|
|
59
|
+
return () => {
|
|
60
|
+
el.destroy();
|
|
61
|
+
elementRef.current = null;
|
|
62
|
+
};
|
|
63
|
+
}, [elements]);
|
|
64
|
+
useElementEvent(elementRef, "ready", props.onReady);
|
|
65
|
+
useElementEvent(elementRef, "change", props.onChange);
|
|
66
|
+
useElementEvent(elementRef, "focus", props.onFocus);
|
|
67
|
+
useElementEvent(elementRef, "blur", props.onBlur);
|
|
68
|
+
useElementEvent(elementRef, "failed", props.onFailed);
|
|
69
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
70
|
+
}
|
|
71
|
+
function useElementEvent(elementRef, event, handler, _unused) {
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const el = elementRef.current;
|
|
74
|
+
if (!el) return;
|
|
75
|
+
if (!handler) return;
|
|
76
|
+
el.on(event, handler);
|
|
77
|
+
return () => el.off(event, handler);
|
|
78
|
+
}, [elementRef, event, handler]);
|
|
79
|
+
}
|
|
80
|
+
function CardElement(props) {
|
|
81
|
+
const elements = useElements();
|
|
82
|
+
const containerRef = useRef(null);
|
|
83
|
+
const elementRef = useRef(null);
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
const container = containerRef.current;
|
|
86
|
+
if (!container) return;
|
|
87
|
+
const el = elements.create("card");
|
|
88
|
+
elementRef.current = el;
|
|
89
|
+
el.mount(container);
|
|
90
|
+
return () => {
|
|
91
|
+
el.destroy();
|
|
92
|
+
elementRef.current = null;
|
|
93
|
+
};
|
|
94
|
+
}, [elements]);
|
|
95
|
+
useElementEvent2(elementRef, "ready", props.onReady);
|
|
96
|
+
useElementEvent2(elementRef, "change", props.onChange);
|
|
97
|
+
useElementEvent2(elementRef, "focus", props.onFocus);
|
|
98
|
+
useElementEvent2(elementRef, "blur", props.onBlur);
|
|
99
|
+
useElementEvent2(elementRef, "failed", props.onFailed);
|
|
100
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
101
|
+
}
|
|
102
|
+
function useElementEvent2(elementRef, event, handler) {
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
const el = elementRef.current;
|
|
105
|
+
if (!el) return;
|
|
106
|
+
if (!handler) return;
|
|
107
|
+
el.on(event, handler);
|
|
108
|
+
return () => el.off(event, handler);
|
|
109
|
+
}, [elementRef, event, handler]);
|
|
110
|
+
}
|
|
111
|
+
function useElementEvent3(elementRef, event, handler) {
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
const el = elementRef.current;
|
|
114
|
+
if (!el) return;
|
|
115
|
+
if (!handler) return;
|
|
116
|
+
el.on(event, handler);
|
|
117
|
+
return () => el.off(event, handler);
|
|
118
|
+
}, [elementRef, event, handler]);
|
|
119
|
+
}
|
|
120
|
+
function useElementLifecycle(type, options) {
|
|
121
|
+
const elements = useElements();
|
|
122
|
+
const containerRef = useRef(null);
|
|
123
|
+
const elementRef = useRef(null);
|
|
124
|
+
const optionsJson = options ? JSON.stringify(options) : "";
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const container = containerRef.current;
|
|
127
|
+
if (!container) return;
|
|
128
|
+
const el = elements.create(type, options ?? {});
|
|
129
|
+
elementRef.current = el;
|
|
130
|
+
el.mount(container);
|
|
131
|
+
return () => {
|
|
132
|
+
el.destroy();
|
|
133
|
+
elementRef.current = null;
|
|
134
|
+
};
|
|
135
|
+
}, [elements, type, optionsJson]);
|
|
136
|
+
return { containerRef, elementRef };
|
|
137
|
+
}
|
|
138
|
+
function PaymentElement(props) {
|
|
139
|
+
const { containerRef, elementRef } = useElementLifecycle("payment");
|
|
140
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
141
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
142
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
143
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
144
|
+
useElementEvent3(elementRef, "failed", props.onFailed);
|
|
145
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
146
|
+
}
|
|
147
|
+
function ExpressCheckoutElement(props) {
|
|
148
|
+
const { containerRef, elementRef } = useElementLifecycle("express");
|
|
149
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
150
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
151
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
152
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
153
|
+
useElementEvent3(elementRef, "failed", props.onFailed);
|
|
154
|
+
useElementEvent3(elementRef, "submit", props.onSubmit);
|
|
155
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
156
|
+
}
|
|
157
|
+
function PaymentMethodSelector(props) {
|
|
158
|
+
const { containerRef, elementRef } = useElementLifecycle("paymentMethodSelector");
|
|
159
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
160
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
161
|
+
useElementEvent3(elementRef, "failed", props.onFailed);
|
|
162
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
163
|
+
}
|
|
164
|
+
function BillingAddressElement(props) {
|
|
165
|
+
const { containerRef, elementRef } = useElementLifecycle(
|
|
166
|
+
"billingAddress",
|
|
167
|
+
{ defaultCountry: props.defaultCountry }
|
|
168
|
+
);
|
|
169
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
170
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
171
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
172
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
173
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
174
|
+
}
|
|
175
|
+
function CustomerElement(props) {
|
|
176
|
+
const { containerRef, elementRef } = useElementLifecycle("customer");
|
|
177
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
178
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
179
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
180
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
181
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
182
|
+
}
|
|
183
|
+
function OtpElement(props) {
|
|
184
|
+
const { containerRef, elementRef } = useElementLifecycle("otp", {
|
|
185
|
+
otpLength: props.length
|
|
186
|
+
});
|
|
187
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
188
|
+
useElementEvent3(elementRef, "change", props.onChange);
|
|
189
|
+
useElementEvent3(elementRef, "focus", props.onFocus);
|
|
190
|
+
useElementEvent3(elementRef, "blur", props.onBlur);
|
|
191
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
192
|
+
}
|
|
193
|
+
function QrElement(props) {
|
|
194
|
+
const { containerRef, elementRef } = useElementLifecycle("qr");
|
|
195
|
+
useElementEvent3(elementRef, "ready", props.onReady);
|
|
196
|
+
useElementEvent3(elementRef, "failed", props.onFailed);
|
|
197
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: props.className, style: props.style });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export { BillingAddressElement, CardElement, CustomerElement, ExpressCheckoutElement, OtpElement, PaymentElement, PaymentMethodSelector, QrElement, SnapProvider, WalletElement, useClientSecret, useElements, useSnap };
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@snap-pay/react",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React bindings for SNAP Elements — provider, hooks, and component wrappers.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SNAP",
|
|
7
|
+
"homepage": "https://github.com/khaledhussein957/SNAP/tree/main/packages/react",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/khaledhussein957/SNAP.git",
|
|
11
|
+
"directory": "packages/react"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/khaledhussein957/SNAP/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["snap", "payments", "react", "elements", "checkout", "hooks"],
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "./dist/index.cjs",
|
|
23
|
+
"module": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": ["dist", "README.md", "CHANGELOG.md"],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup",
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@snap-pay/elements": "1.0.0",
|
|
40
|
+
"@snap-pay/js": "1.0.0",
|
|
41
|
+
"@snap-pay/types": "1.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@testing-library/react": "^16.3.0",
|
|
48
|
+
"@types/react": "^19",
|
|
49
|
+
"@vitejs/plugin-react": "^5.0.5",
|
|
50
|
+
"happy-dom": "^15.11.7",
|
|
51
|
+
"react": "19.2.4",
|
|
52
|
+
"react-dom": "19.2.4",
|
|
53
|
+
"tsup": "^8.3.5",
|
|
54
|
+
"typescript": "^5.7.2",
|
|
55
|
+
"vitest": "^4.1.9"
|
|
56
|
+
}
|
|
57
|
+
}
|