@snap-pay/js 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 +12 -0
- package/README.md +57 -0
- package/dist/index.cjs +319 -0
- package/dist/index.d.cts +74 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +309 -0
- package/package.json +46 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
Initial public release.
|
|
6
|
+
|
|
7
|
+
- `Snap` class — constructs from a publishable key (`pk_test_` or `pk_live_`). Rejects secret keys.
|
|
8
|
+
- `snap.elements({ clientSecret, appearance })` — creates an Elements container bound to a checkout session.
|
|
9
|
+
- `snap.confirmPayment({ elements, clientSecret })` — reads the customer's selection from the mounted Elements and dispatches the payment. Attaches billing/customer/otp form data automatically when those elements are mounted.
|
|
10
|
+
- Provider environment derived from the key prefix (`pk_live_*` → live).
|
|
11
|
+
- Publishable-key HTTP client with a 30s AbortController-backed timeout.
|
|
12
|
+
- Element registry — `@snap-pay/elements` populates it on import; `snap.elements(...).create(type)` looks up the factory.
|
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @snap-pay/js
|
|
2
|
+
|
|
3
|
+
SNAP.js — client-side SDK for creating and confirming SNAP payments in the browser.
|
|
4
|
+
|
|
5
|
+
Pairs with `@snap-pay/elements` (UI components) and `@snap-pay/react` (React bindings).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @snap-pay/js @snap-pay/elements
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { Snap } from "@snap-pay/js";
|
|
15
|
+
import "@snap-pay/elements";
|
|
16
|
+
|
|
17
|
+
const snap = new Snap("pk_test_…");
|
|
18
|
+
const elements = snap.elements({ clientSecret: sessionId });
|
|
19
|
+
const payment = elements.create("payment");
|
|
20
|
+
payment.mount("#pay");
|
|
21
|
+
|
|
22
|
+
const result = await snap.confirmPayment({ elements, clientSecret: sessionId });
|
|
23
|
+
if (result.status === "succeeded") window.location.href = "/thanks";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Options
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
new Snap(publishableKey, {
|
|
30
|
+
apiBase: "http://localhost:3210", // for local dev / self-hosted
|
|
31
|
+
cardFrameUrl: "http://localhost:3000/elements/card-frame",
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `apiBase` — defaults to the SNAP production API. Point at a local Convex deployment during development.
|
|
36
|
+
- `cardFrameUrl` — the origin hosting the Card iframe. Defaults to SNAP's hosted page; override for local dev or self-hosted deployments.
|
|
37
|
+
|
|
38
|
+
## `confirmPayment`
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const result = await snap.confirmPayment({ elements, clientSecret });
|
|
42
|
+
// result.status: "succeeded" | "processing" | "pending" | "failed" | ...
|
|
43
|
+
// result.transactionId: the SNAP transaction id
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The SDK reads the customer's selection from the mounted Elements. Wallet selection sends `provider` directly; Card selection sends `provider: "mock"` alongside `card_token` / `card_brand` / `card_last4` metadata in sandbox. Live-mode Card tokenization throws explicitly — it's on the roadmap for a subsequent release.
|
|
47
|
+
|
|
48
|
+
Any form-collecting elements (BillingAddressElement, CustomerElement, OtpElement) that are mounted inside the same Elements set contribute their values automatically — no extra plumbing.
|
|
49
|
+
|
|
50
|
+
## Security
|
|
51
|
+
|
|
52
|
+
- Always pass a **publishable** key (`pk_test_` or `pk_live_`). Secret keys (`sk_…`) belong on the server and must never ship to the browser — the constructor refuses them.
|
|
53
|
+
- The `clientSecret` is a checkout session id. It grants payment rights only for that session and its owning merchant.
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var SnapError = class extends Error {
|
|
5
|
+
constructor(message, code, type, requestId) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "SnapError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.type = type;
|
|
10
|
+
this.requestId = requestId;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var SnapAuthenticationError = class extends SnapError {
|
|
14
|
+
constructor(message, requestId) {
|
|
15
|
+
super(message, "authentication_failed", "authentication_error", requestId);
|
|
16
|
+
this.name = "SnapAuthenticationError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var SnapValidationError = class extends SnapError {
|
|
20
|
+
constructor(message, requestId) {
|
|
21
|
+
super(message, "validation_error", "invalid_request_error", requestId);
|
|
22
|
+
this.name = "SnapValidationError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// src/session-client.ts
|
|
27
|
+
var DEFAULT_API_BASE = "https://api.snap.so";
|
|
28
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
29
|
+
async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
32
|
+
try {
|
|
33
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
36
|
+
throw new SnapError(
|
|
37
|
+
`Request timed out after ${timeoutMs} ms.`,
|
|
38
|
+
"timeout",
|
|
39
|
+
"api_connection_error"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
throw err;
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function throwFromResponse(response) {
|
|
48
|
+
let envelope;
|
|
49
|
+
try {
|
|
50
|
+
envelope = await response.json();
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
const err = envelope?.error;
|
|
54
|
+
const message = err?.message ?? `Request failed with status ${response.status}`;
|
|
55
|
+
const code = err?.code ?? `http_${response.status}`;
|
|
56
|
+
const type = err?.type ?? "api_error";
|
|
57
|
+
const requestId = err?.request_id;
|
|
58
|
+
if (response.status === 401 || code === "authentication_failed") {
|
|
59
|
+
throw new SnapAuthenticationError(message, requestId);
|
|
60
|
+
}
|
|
61
|
+
if (response.status === 400 || code === "validation_error") {
|
|
62
|
+
throw new SnapValidationError(message, requestId);
|
|
63
|
+
}
|
|
64
|
+
throw new SnapError(message, code, type, requestId);
|
|
65
|
+
}
|
|
66
|
+
async function fetchSession(apiBase, publishableKey, clientSecret) {
|
|
67
|
+
const url = `${apiBase.replace(/\/+$/, "")}/v1/checkout/sessions/${encodeURIComponent(clientSecret)}`;
|
|
68
|
+
const response = await fetchWithTimeout(url, {
|
|
69
|
+
method: "GET",
|
|
70
|
+
headers: {
|
|
71
|
+
Authorization: `Bearer ${publishableKey}`,
|
|
72
|
+
Accept: "application/json"
|
|
73
|
+
},
|
|
74
|
+
// Elements are cross-origin by construction — the merchant's
|
|
75
|
+
// page is not on snap.so. `cors` is the fetch default but
|
|
76
|
+
// stating it makes the intent readable.
|
|
77
|
+
mode: "cors",
|
|
78
|
+
credentials: "omit"
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) await throwFromResponse(response);
|
|
81
|
+
const body = await response.json();
|
|
82
|
+
return toPublicSession(body);
|
|
83
|
+
}
|
|
84
|
+
async function startPayment(apiBase, publishableKey, clientSecret, provider, customer, metadata) {
|
|
85
|
+
const url = `${apiBase.replace(/\/+$/, "")}/v1/checkout/sessions/${encodeURIComponent(clientSecret)}/pay`;
|
|
86
|
+
const body = { provider };
|
|
87
|
+
if (customer) body.customer = customer;
|
|
88
|
+
if (metadata) Object.assign(body, metadata);
|
|
89
|
+
const response = await fetchWithTimeout(url, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${publishableKey}`,
|
|
93
|
+
"Content-Type": "application/json",
|
|
94
|
+
Accept: "application/json"
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify(body),
|
|
97
|
+
mode: "cors",
|
|
98
|
+
credentials: "omit"
|
|
99
|
+
});
|
|
100
|
+
if (!response.ok) await throwFromResponse(response);
|
|
101
|
+
const wire = await response.json();
|
|
102
|
+
return { transactionId: wire.transaction_id, status: wire.status };
|
|
103
|
+
}
|
|
104
|
+
function toPublicSession(w) {
|
|
105
|
+
return {
|
|
106
|
+
id: w.id,
|
|
107
|
+
amount: w.amount,
|
|
108
|
+
currency: w.currency,
|
|
109
|
+
allowedProviders: w.allowed_providers,
|
|
110
|
+
status: w.status,
|
|
111
|
+
expiresAt: w.expires_at,
|
|
112
|
+
transactionId: w.transaction_id,
|
|
113
|
+
merchantName: w.merchant_name,
|
|
114
|
+
customer: w.customer,
|
|
115
|
+
successUrl: w.success_url,
|
|
116
|
+
cancelUrl: w.cancel_url
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/elements.ts
|
|
121
|
+
var REGISTRY = /* @__PURE__ */ new Map();
|
|
122
|
+
function registerElement(type, factory) {
|
|
123
|
+
REGISTRY.set(type, factory);
|
|
124
|
+
}
|
|
125
|
+
function registeredTypes() {
|
|
126
|
+
return Array.from(REGISTRY.keys());
|
|
127
|
+
}
|
|
128
|
+
var SnapElements = class {
|
|
129
|
+
constructor(args) {
|
|
130
|
+
// Registration-order children — each carries a getter for its
|
|
131
|
+
// current selection. Insertion-order iteration on JavaScript
|
|
132
|
+
// Sets is stable; new elements append to the tail. Deregistration
|
|
133
|
+
// (via the returned cleanup fn) drops the child entirely so a
|
|
134
|
+
// destroyed element can't accidentally answer a later
|
|
135
|
+
// `_getSelection` call.
|
|
136
|
+
this.children = /* @__PURE__ */ new Set();
|
|
137
|
+
// Form-data getters, keyed by (type, opaque token). One element
|
|
138
|
+
// may register a getter and another element of the same type may
|
|
139
|
+
// register another — first-registered wins on `_getValue`, which
|
|
140
|
+
// matches the visual convention that the earliest-mounted
|
|
141
|
+
// element on the page is the primary one.
|
|
142
|
+
this.values = /* @__PURE__ */ new Map();
|
|
143
|
+
if (!args.options.clientSecret) {
|
|
144
|
+
throw new Error("`clientSecret` is required when creating Elements.");
|
|
145
|
+
}
|
|
146
|
+
this.clientSecret = args.options.clientSecret;
|
|
147
|
+
this.appearance = args.options.appearance ?? {};
|
|
148
|
+
this.publishableKey = args.publishableKey;
|
|
149
|
+
this.apiBase = args.apiBase;
|
|
150
|
+
this.environment = args.environment;
|
|
151
|
+
this.cardFrameUrl = args.cardFrameUrl;
|
|
152
|
+
}
|
|
153
|
+
create(type, options = {}) {
|
|
154
|
+
const factory = REGISTRY.get(type);
|
|
155
|
+
if (!factory) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Element type "${type}" is not implemented in this SDK version. Supported: ${registeredTypes().map((t) => `'${t}'`).join(", ") || "(none \u2014 did you import @snap-pay/elements?)"}.`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return factory({
|
|
161
|
+
clientSecret: this.clientSecret,
|
|
162
|
+
appearance: this.appearance,
|
|
163
|
+
publishableKey: this.publishableKey,
|
|
164
|
+
apiBase: this.apiBase,
|
|
165
|
+
environment: this.environment,
|
|
166
|
+
cardFrameUrl: this.cardFrameUrl,
|
|
167
|
+
registerChild: (getSelection) => {
|
|
168
|
+
this.children.add(getSelection);
|
|
169
|
+
return () => this.children.delete(getSelection);
|
|
170
|
+
},
|
|
171
|
+
registerValue: (childType, getValue) => {
|
|
172
|
+
let bucket = this.values.get(childType);
|
|
173
|
+
if (!bucket) {
|
|
174
|
+
bucket = /* @__PURE__ */ new Set();
|
|
175
|
+
this.values.set(childType, bucket);
|
|
176
|
+
}
|
|
177
|
+
bucket.add(getValue);
|
|
178
|
+
return () => bucket.delete(getValue);
|
|
179
|
+
},
|
|
180
|
+
options
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
_getSelection() {
|
|
184
|
+
for (const getSelection of this.children) {
|
|
185
|
+
const s = getSelection();
|
|
186
|
+
if (s !== null) return s;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
_getValue(type) {
|
|
191
|
+
const bucket = this.values.get(type);
|
|
192
|
+
if (!bucket) return null;
|
|
193
|
+
for (const getValue of bucket) {
|
|
194
|
+
const v = getValue();
|
|
195
|
+
if (v !== null && v !== void 0) return v;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/snap.ts
|
|
202
|
+
var DEFAULT_CARD_FRAME_URL = "https://checkout.snap.so/elements/card-frame";
|
|
203
|
+
var Snap = class {
|
|
204
|
+
constructor(publishableKey, options = {}) {
|
|
205
|
+
if (typeof publishableKey !== "string" || publishableKey.length === 0) {
|
|
206
|
+
throw new Error("Snap requires a publishable key (pk_test_\u2026 or pk_live_\u2026).");
|
|
207
|
+
}
|
|
208
|
+
if (!publishableKey.startsWith("pk_")) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
"Publishable key must start with 'pk_test_' or 'pk_live_'. Secret keys (sk_\u2026) are for server code only and must NEVER be shipped to the browser."
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
this.publishableKey = publishableKey;
|
|
214
|
+
this.apiBase = options.apiBase ?? DEFAULT_API_BASE;
|
|
215
|
+
this.environment = publishableKey.startsWith("pk_live_") ? "live" : "sandbox";
|
|
216
|
+
this.cardFrameUrl = options.cardFrameUrl ?? DEFAULT_CARD_FRAME_URL;
|
|
217
|
+
}
|
|
218
|
+
// Creates an Elements set bound to a specific client secret
|
|
219
|
+
// (checkout session id). Returned handle stays local — the SDK
|
|
220
|
+
// deliberately does NOT stash it in module scope so multiple
|
|
221
|
+
// sessions can coexist on the same page (useful for merchants
|
|
222
|
+
// rendering upsell + main-cart Elements simultaneously).
|
|
223
|
+
elements(options) {
|
|
224
|
+
return new SnapElements({
|
|
225
|
+
publishableKey: this.publishableKey,
|
|
226
|
+
apiBase: this.apiBase,
|
|
227
|
+
environment: this.environment,
|
|
228
|
+
cardFrameUrl: this.cardFrameUrl,
|
|
229
|
+
options
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
// Confirms the payment by asking the server to dispatch it to
|
|
233
|
+
// the payment method the customer chose inside the mounted
|
|
234
|
+
// Elements. Two selection kinds:
|
|
235
|
+
// - "provider": Wallet flow (evcPlus / zaad / …). Sends the
|
|
236
|
+
// provider straight to the /pay endpoint.
|
|
237
|
+
// - "card": Card Element sandbox flow. Sends provider="mock"
|
|
238
|
+
// alongside `card_token` metadata so the backend can drive
|
|
239
|
+
// the transaction through the mock adapter (real Mastercard
|
|
240
|
+
// tokenization is deferred to a future PR). Live-mode card
|
|
241
|
+
// tokens throw explicitly.
|
|
242
|
+
//
|
|
243
|
+
// Throws if no selection is available — the UI should keep the
|
|
244
|
+
// confirm button disabled until an Element reports a `change`
|
|
245
|
+
// with `complete: true`.
|
|
246
|
+
async confirmPayment(options) {
|
|
247
|
+
const selection = options.elements._getSelection();
|
|
248
|
+
if (selection === null) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
"No payment method selected. Ask the customer to pick a provider before confirming."
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const getVal = options.elements._getValue?.bind(options.elements);
|
|
254
|
+
const customer = getVal?.("customer") ?? void 0;
|
|
255
|
+
const billing = getVal?.("billingAddress") ?? void 0;
|
|
256
|
+
const otp = getVal?.("otp") ?? null;
|
|
257
|
+
const extraMetadata = {};
|
|
258
|
+
if (billing) {
|
|
259
|
+
const billingKeyMap = {
|
|
260
|
+
name: "billing_name",
|
|
261
|
+
line1: "billing_line1",
|
|
262
|
+
line2: "billing_line2",
|
|
263
|
+
city: "billing_city",
|
|
264
|
+
region: "billing_region",
|
|
265
|
+
postalCode: "billing_postal_code",
|
|
266
|
+
country: "billing_country"
|
|
267
|
+
};
|
|
268
|
+
for (const [k, v] of Object.entries(billing)) {
|
|
269
|
+
if (typeof v === "string" && v.length > 0) {
|
|
270
|
+
const wireKey = billingKeyMap[k];
|
|
271
|
+
if (wireKey) extraMetadata[wireKey] = v;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (otp && typeof otp.code === "string" && otp.code.length > 0) {
|
|
276
|
+
extraMetadata.otp_code = otp.code;
|
|
277
|
+
}
|
|
278
|
+
if (selection.kind === "card") {
|
|
279
|
+
if (this.environment === "live") {
|
|
280
|
+
throw new Error(
|
|
281
|
+
"Card Element live-mode tokenization is not yet supported. Use a `pk_test_` key or wire a wallet Element for live payments."
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
const result2 = await startPayment(
|
|
285
|
+
this.apiBase,
|
|
286
|
+
this.publishableKey,
|
|
287
|
+
options.clientSecret,
|
|
288
|
+
"mock",
|
|
289
|
+
customer,
|
|
290
|
+
{
|
|
291
|
+
card_token: selection.token,
|
|
292
|
+
card_brand: selection.brand,
|
|
293
|
+
card_last4: selection.last4,
|
|
294
|
+
...extraMetadata
|
|
295
|
+
}
|
|
296
|
+
);
|
|
297
|
+
return { transactionId: result2.transactionId, status: result2.status };
|
|
298
|
+
}
|
|
299
|
+
const result = await startPayment(
|
|
300
|
+
this.apiBase,
|
|
301
|
+
this.publishableKey,
|
|
302
|
+
options.clientSecret,
|
|
303
|
+
selection.provider,
|
|
304
|
+
customer,
|
|
305
|
+
Object.keys(extraMetadata).length > 0 ? extraMetadata : void 0
|
|
306
|
+
);
|
|
307
|
+
return { transactionId: result.transactionId, status: result.status };
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
exports.DEFAULT_API_BASE = DEFAULT_API_BASE;
|
|
312
|
+
exports.Snap = Snap;
|
|
313
|
+
exports.SnapAuthenticationError = SnapAuthenticationError;
|
|
314
|
+
exports.SnapElements = SnapElements;
|
|
315
|
+
exports.SnapError = SnapError;
|
|
316
|
+
exports.SnapValidationError = SnapValidationError;
|
|
317
|
+
exports.fetchSession = fetchSession;
|
|
318
|
+
exports.registerElement = registerElement;
|
|
319
|
+
exports.startPayment = startPayment;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as _snap_pay_types from '@snap-pay/types';
|
|
2
|
+
import { Appearance, Environment, SnapSelection, ElementType, CreateElementOptions, SnapElementHandle, SnapElementsHandle, ElementsOptions, SnapOptions, ConfirmPaymentOptions, PaymentResult, PublicSession, CheckoutProvider } from '@snap-pay/types';
|
|
3
|
+
export { Appearance, CheckoutProvider, ConfirmPaymentOptions, CreateElementOptions, ElementEventHandler, ElementEventMap, ElementEventName, ElementType, ElementsOptions, Environment, PaymentResult, PaymentStatus, PublicSession, PublicSessionResponse, SessionStatus, SnapElementHandle, SnapElementsHandle, SnapOptions, WalletProvider } from '@snap-pay/types';
|
|
4
|
+
|
|
5
|
+
type ElementFactory = (args: {
|
|
6
|
+
clientSecret: string;
|
|
7
|
+
appearance: Appearance;
|
|
8
|
+
publishableKey: string;
|
|
9
|
+
apiBase: string;
|
|
10
|
+
environment: Environment;
|
|
11
|
+
cardFrameUrl: string | undefined;
|
|
12
|
+
registerChild: (getSelection: () => SnapSelection | null) => () => void;
|
|
13
|
+
registerValue: (type: ElementType, getValue: () => unknown | null) => () => void;
|
|
14
|
+
options: CreateElementOptions;
|
|
15
|
+
}) => SnapElementHandle;
|
|
16
|
+
declare function registerElement(type: ElementType, factory: ElementFactory): void;
|
|
17
|
+
interface SnapElementsConstructorArgs {
|
|
18
|
+
publishableKey: string;
|
|
19
|
+
apiBase: string;
|
|
20
|
+
environment: Environment;
|
|
21
|
+
cardFrameUrl: string | undefined;
|
|
22
|
+
options: ElementsOptions;
|
|
23
|
+
}
|
|
24
|
+
declare class SnapElements implements SnapElementsHandle {
|
|
25
|
+
readonly clientSecret: string;
|
|
26
|
+
readonly appearance: Appearance;
|
|
27
|
+
private children;
|
|
28
|
+
private values;
|
|
29
|
+
private readonly publishableKey;
|
|
30
|
+
private readonly apiBase;
|
|
31
|
+
private readonly environment;
|
|
32
|
+
private readonly cardFrameUrl;
|
|
33
|
+
constructor(args: SnapElementsConstructorArgs);
|
|
34
|
+
create(type: ElementType, options?: CreateElementOptions): SnapElementHandle;
|
|
35
|
+
_getSelection(): SnapSelection | null;
|
|
36
|
+
_getValue(type: ElementType): unknown | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare class Snap {
|
|
40
|
+
private readonly publishableKey;
|
|
41
|
+
private readonly apiBase;
|
|
42
|
+
private readonly environment;
|
|
43
|
+
private readonly cardFrameUrl;
|
|
44
|
+
constructor(publishableKey: string, options?: SnapOptions);
|
|
45
|
+
elements(options: ElementsOptions): SnapElements;
|
|
46
|
+
confirmPayment(options: ConfirmPaymentOptions): Promise<PaymentResult>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare class SnapError extends Error {
|
|
50
|
+
readonly code: string;
|
|
51
|
+
readonly type: string;
|
|
52
|
+
readonly requestId?: string;
|
|
53
|
+
constructor(message: string, code: string, type: string, requestId?: string);
|
|
54
|
+
}
|
|
55
|
+
declare class SnapAuthenticationError extends SnapError {
|
|
56
|
+
constructor(message: string, requestId?: string);
|
|
57
|
+
}
|
|
58
|
+
declare class SnapValidationError extends SnapError {
|
|
59
|
+
constructor(message: string, requestId?: string);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
declare const DEFAULT_API_BASE = "https://api.snap.so";
|
|
63
|
+
declare function fetchSession(apiBase: string, publishableKey: string, clientSecret: string): Promise<PublicSession>;
|
|
64
|
+
declare function startPayment(apiBase: string, publishableKey: string, clientSecret: string, provider: CheckoutProvider, customer?: {
|
|
65
|
+
name?: string;
|
|
66
|
+
email?: string;
|
|
67
|
+
phone?: string;
|
|
68
|
+
country?: string;
|
|
69
|
+
}, metadata?: Record<string, string>): Promise<{
|
|
70
|
+
transactionId: string;
|
|
71
|
+
status: _snap_pay_types.PaymentStatus;
|
|
72
|
+
}>;
|
|
73
|
+
|
|
74
|
+
export { DEFAULT_API_BASE, type ElementFactory, Snap, SnapAuthenticationError, SnapElements, SnapError, SnapValidationError, fetchSession, registerElement, startPayment };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import * as _snap_pay_types from '@snap-pay/types';
|
|
2
|
+
import { Appearance, Environment, SnapSelection, ElementType, CreateElementOptions, SnapElementHandle, SnapElementsHandle, ElementsOptions, SnapOptions, ConfirmPaymentOptions, PaymentResult, PublicSession, CheckoutProvider } from '@snap-pay/types';
|
|
3
|
+
export { Appearance, CheckoutProvider, ConfirmPaymentOptions, CreateElementOptions, ElementEventHandler, ElementEventMap, ElementEventName, ElementType, ElementsOptions, Environment, PaymentResult, PaymentStatus, PublicSession, PublicSessionResponse, SessionStatus, SnapElementHandle, SnapElementsHandle, SnapOptions, WalletProvider } from '@snap-pay/types';
|
|
4
|
+
|
|
5
|
+
type ElementFactory = (args: {
|
|
6
|
+
clientSecret: string;
|
|
7
|
+
appearance: Appearance;
|
|
8
|
+
publishableKey: string;
|
|
9
|
+
apiBase: string;
|
|
10
|
+
environment: Environment;
|
|
11
|
+
cardFrameUrl: string | undefined;
|
|
12
|
+
registerChild: (getSelection: () => SnapSelection | null) => () => void;
|
|
13
|
+
registerValue: (type: ElementType, getValue: () => unknown | null) => () => void;
|
|
14
|
+
options: CreateElementOptions;
|
|
15
|
+
}) => SnapElementHandle;
|
|
16
|
+
declare function registerElement(type: ElementType, factory: ElementFactory): void;
|
|
17
|
+
interface SnapElementsConstructorArgs {
|
|
18
|
+
publishableKey: string;
|
|
19
|
+
apiBase: string;
|
|
20
|
+
environment: Environment;
|
|
21
|
+
cardFrameUrl: string | undefined;
|
|
22
|
+
options: ElementsOptions;
|
|
23
|
+
}
|
|
24
|
+
declare class SnapElements implements SnapElementsHandle {
|
|
25
|
+
readonly clientSecret: string;
|
|
26
|
+
readonly appearance: Appearance;
|
|
27
|
+
private children;
|
|
28
|
+
private values;
|
|
29
|
+
private readonly publishableKey;
|
|
30
|
+
private readonly apiBase;
|
|
31
|
+
private readonly environment;
|
|
32
|
+
private readonly cardFrameUrl;
|
|
33
|
+
constructor(args: SnapElementsConstructorArgs);
|
|
34
|
+
create(type: ElementType, options?: CreateElementOptions): SnapElementHandle;
|
|
35
|
+
_getSelection(): SnapSelection | null;
|
|
36
|
+
_getValue(type: ElementType): unknown | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare class Snap {
|
|
40
|
+
private readonly publishableKey;
|
|
41
|
+
private readonly apiBase;
|
|
42
|
+
private readonly environment;
|
|
43
|
+
private readonly cardFrameUrl;
|
|
44
|
+
constructor(publishableKey: string, options?: SnapOptions);
|
|
45
|
+
elements(options: ElementsOptions): SnapElements;
|
|
46
|
+
confirmPayment(options: ConfirmPaymentOptions): Promise<PaymentResult>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
declare class SnapError extends Error {
|
|
50
|
+
readonly code: string;
|
|
51
|
+
readonly type: string;
|
|
52
|
+
readonly requestId?: string;
|
|
53
|
+
constructor(message: string, code: string, type: string, requestId?: string);
|
|
54
|
+
}
|
|
55
|
+
declare class SnapAuthenticationError extends SnapError {
|
|
56
|
+
constructor(message: string, requestId?: string);
|
|
57
|
+
}
|
|
58
|
+
declare class SnapValidationError extends SnapError {
|
|
59
|
+
constructor(message: string, requestId?: string);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
declare const DEFAULT_API_BASE = "https://api.snap.so";
|
|
63
|
+
declare function fetchSession(apiBase: string, publishableKey: string, clientSecret: string): Promise<PublicSession>;
|
|
64
|
+
declare function startPayment(apiBase: string, publishableKey: string, clientSecret: string, provider: CheckoutProvider, customer?: {
|
|
65
|
+
name?: string;
|
|
66
|
+
email?: string;
|
|
67
|
+
phone?: string;
|
|
68
|
+
country?: string;
|
|
69
|
+
}, metadata?: Record<string, string>): Promise<{
|
|
70
|
+
transactionId: string;
|
|
71
|
+
status: _snap_pay_types.PaymentStatus;
|
|
72
|
+
}>;
|
|
73
|
+
|
|
74
|
+
export { DEFAULT_API_BASE, type ElementFactory, Snap, SnapAuthenticationError, SnapElements, SnapError, SnapValidationError, fetchSession, registerElement, startPayment };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var SnapError = class extends Error {
|
|
3
|
+
constructor(message, code, type, requestId) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "SnapError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.type = type;
|
|
8
|
+
this.requestId = requestId;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var SnapAuthenticationError = class extends SnapError {
|
|
12
|
+
constructor(message, requestId) {
|
|
13
|
+
super(message, "authentication_failed", "authentication_error", requestId);
|
|
14
|
+
this.name = "SnapAuthenticationError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var SnapValidationError = class extends SnapError {
|
|
18
|
+
constructor(message, requestId) {
|
|
19
|
+
super(message, "validation_error", "invalid_request_error", requestId);
|
|
20
|
+
this.name = "SnapValidationError";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/session-client.ts
|
|
25
|
+
var DEFAULT_API_BASE = "https://api.snap.so";
|
|
26
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
27
|
+
async function fetchWithTimeout(url, init, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
30
|
+
try {
|
|
31
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
32
|
+
} catch (err) {
|
|
33
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
34
|
+
throw new SnapError(
|
|
35
|
+
`Request timed out after ${timeoutMs} ms.`,
|
|
36
|
+
"timeout",
|
|
37
|
+
"api_connection_error"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
throw err;
|
|
41
|
+
} finally {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function throwFromResponse(response) {
|
|
46
|
+
let envelope;
|
|
47
|
+
try {
|
|
48
|
+
envelope = await response.json();
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
const err = envelope?.error;
|
|
52
|
+
const message = err?.message ?? `Request failed with status ${response.status}`;
|
|
53
|
+
const code = err?.code ?? `http_${response.status}`;
|
|
54
|
+
const type = err?.type ?? "api_error";
|
|
55
|
+
const requestId = err?.request_id;
|
|
56
|
+
if (response.status === 401 || code === "authentication_failed") {
|
|
57
|
+
throw new SnapAuthenticationError(message, requestId);
|
|
58
|
+
}
|
|
59
|
+
if (response.status === 400 || code === "validation_error") {
|
|
60
|
+
throw new SnapValidationError(message, requestId);
|
|
61
|
+
}
|
|
62
|
+
throw new SnapError(message, code, type, requestId);
|
|
63
|
+
}
|
|
64
|
+
async function fetchSession(apiBase, publishableKey, clientSecret) {
|
|
65
|
+
const url = `${apiBase.replace(/\/+$/, "")}/v1/checkout/sessions/${encodeURIComponent(clientSecret)}`;
|
|
66
|
+
const response = await fetchWithTimeout(url, {
|
|
67
|
+
method: "GET",
|
|
68
|
+
headers: {
|
|
69
|
+
Authorization: `Bearer ${publishableKey}`,
|
|
70
|
+
Accept: "application/json"
|
|
71
|
+
},
|
|
72
|
+
// Elements are cross-origin by construction — the merchant's
|
|
73
|
+
// page is not on snap.so. `cors` is the fetch default but
|
|
74
|
+
// stating it makes the intent readable.
|
|
75
|
+
mode: "cors",
|
|
76
|
+
credentials: "omit"
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) await throwFromResponse(response);
|
|
79
|
+
const body = await response.json();
|
|
80
|
+
return toPublicSession(body);
|
|
81
|
+
}
|
|
82
|
+
async function startPayment(apiBase, publishableKey, clientSecret, provider, customer, metadata) {
|
|
83
|
+
const url = `${apiBase.replace(/\/+$/, "")}/v1/checkout/sessions/${encodeURIComponent(clientSecret)}/pay`;
|
|
84
|
+
const body = { provider };
|
|
85
|
+
if (customer) body.customer = customer;
|
|
86
|
+
if (metadata) Object.assign(body, metadata);
|
|
87
|
+
const response = await fetchWithTimeout(url, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: {
|
|
90
|
+
Authorization: `Bearer ${publishableKey}`,
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
Accept: "application/json"
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(body),
|
|
95
|
+
mode: "cors",
|
|
96
|
+
credentials: "omit"
|
|
97
|
+
});
|
|
98
|
+
if (!response.ok) await throwFromResponse(response);
|
|
99
|
+
const wire = await response.json();
|
|
100
|
+
return { transactionId: wire.transaction_id, status: wire.status };
|
|
101
|
+
}
|
|
102
|
+
function toPublicSession(w) {
|
|
103
|
+
return {
|
|
104
|
+
id: w.id,
|
|
105
|
+
amount: w.amount,
|
|
106
|
+
currency: w.currency,
|
|
107
|
+
allowedProviders: w.allowed_providers,
|
|
108
|
+
status: w.status,
|
|
109
|
+
expiresAt: w.expires_at,
|
|
110
|
+
transactionId: w.transaction_id,
|
|
111
|
+
merchantName: w.merchant_name,
|
|
112
|
+
customer: w.customer,
|
|
113
|
+
successUrl: w.success_url,
|
|
114
|
+
cancelUrl: w.cancel_url
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/elements.ts
|
|
119
|
+
var REGISTRY = /* @__PURE__ */ new Map();
|
|
120
|
+
function registerElement(type, factory) {
|
|
121
|
+
REGISTRY.set(type, factory);
|
|
122
|
+
}
|
|
123
|
+
function registeredTypes() {
|
|
124
|
+
return Array.from(REGISTRY.keys());
|
|
125
|
+
}
|
|
126
|
+
var SnapElements = class {
|
|
127
|
+
constructor(args) {
|
|
128
|
+
// Registration-order children — each carries a getter for its
|
|
129
|
+
// current selection. Insertion-order iteration on JavaScript
|
|
130
|
+
// Sets is stable; new elements append to the tail. Deregistration
|
|
131
|
+
// (via the returned cleanup fn) drops the child entirely so a
|
|
132
|
+
// destroyed element can't accidentally answer a later
|
|
133
|
+
// `_getSelection` call.
|
|
134
|
+
this.children = /* @__PURE__ */ new Set();
|
|
135
|
+
// Form-data getters, keyed by (type, opaque token). One element
|
|
136
|
+
// may register a getter and another element of the same type may
|
|
137
|
+
// register another — first-registered wins on `_getValue`, which
|
|
138
|
+
// matches the visual convention that the earliest-mounted
|
|
139
|
+
// element on the page is the primary one.
|
|
140
|
+
this.values = /* @__PURE__ */ new Map();
|
|
141
|
+
if (!args.options.clientSecret) {
|
|
142
|
+
throw new Error("`clientSecret` is required when creating Elements.");
|
|
143
|
+
}
|
|
144
|
+
this.clientSecret = args.options.clientSecret;
|
|
145
|
+
this.appearance = args.options.appearance ?? {};
|
|
146
|
+
this.publishableKey = args.publishableKey;
|
|
147
|
+
this.apiBase = args.apiBase;
|
|
148
|
+
this.environment = args.environment;
|
|
149
|
+
this.cardFrameUrl = args.cardFrameUrl;
|
|
150
|
+
}
|
|
151
|
+
create(type, options = {}) {
|
|
152
|
+
const factory = REGISTRY.get(type);
|
|
153
|
+
if (!factory) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Element type "${type}" is not implemented in this SDK version. Supported: ${registeredTypes().map((t) => `'${t}'`).join(", ") || "(none \u2014 did you import @snap-pay/elements?)"}.`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
return factory({
|
|
159
|
+
clientSecret: this.clientSecret,
|
|
160
|
+
appearance: this.appearance,
|
|
161
|
+
publishableKey: this.publishableKey,
|
|
162
|
+
apiBase: this.apiBase,
|
|
163
|
+
environment: this.environment,
|
|
164
|
+
cardFrameUrl: this.cardFrameUrl,
|
|
165
|
+
registerChild: (getSelection) => {
|
|
166
|
+
this.children.add(getSelection);
|
|
167
|
+
return () => this.children.delete(getSelection);
|
|
168
|
+
},
|
|
169
|
+
registerValue: (childType, getValue) => {
|
|
170
|
+
let bucket = this.values.get(childType);
|
|
171
|
+
if (!bucket) {
|
|
172
|
+
bucket = /* @__PURE__ */ new Set();
|
|
173
|
+
this.values.set(childType, bucket);
|
|
174
|
+
}
|
|
175
|
+
bucket.add(getValue);
|
|
176
|
+
return () => bucket.delete(getValue);
|
|
177
|
+
},
|
|
178
|
+
options
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
_getSelection() {
|
|
182
|
+
for (const getSelection of this.children) {
|
|
183
|
+
const s = getSelection();
|
|
184
|
+
if (s !== null) return s;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
_getValue(type) {
|
|
189
|
+
const bucket = this.values.get(type);
|
|
190
|
+
if (!bucket) return null;
|
|
191
|
+
for (const getValue of bucket) {
|
|
192
|
+
const v = getValue();
|
|
193
|
+
if (v !== null && v !== void 0) return v;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// src/snap.ts
|
|
200
|
+
var DEFAULT_CARD_FRAME_URL = "https://checkout.snap.so/elements/card-frame";
|
|
201
|
+
var Snap = class {
|
|
202
|
+
constructor(publishableKey, options = {}) {
|
|
203
|
+
if (typeof publishableKey !== "string" || publishableKey.length === 0) {
|
|
204
|
+
throw new Error("Snap requires a publishable key (pk_test_\u2026 or pk_live_\u2026).");
|
|
205
|
+
}
|
|
206
|
+
if (!publishableKey.startsWith("pk_")) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
"Publishable key must start with 'pk_test_' or 'pk_live_'. Secret keys (sk_\u2026) are for server code only and must NEVER be shipped to the browser."
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
this.publishableKey = publishableKey;
|
|
212
|
+
this.apiBase = options.apiBase ?? DEFAULT_API_BASE;
|
|
213
|
+
this.environment = publishableKey.startsWith("pk_live_") ? "live" : "sandbox";
|
|
214
|
+
this.cardFrameUrl = options.cardFrameUrl ?? DEFAULT_CARD_FRAME_URL;
|
|
215
|
+
}
|
|
216
|
+
// Creates an Elements set bound to a specific client secret
|
|
217
|
+
// (checkout session id). Returned handle stays local — the SDK
|
|
218
|
+
// deliberately does NOT stash it in module scope so multiple
|
|
219
|
+
// sessions can coexist on the same page (useful for merchants
|
|
220
|
+
// rendering upsell + main-cart Elements simultaneously).
|
|
221
|
+
elements(options) {
|
|
222
|
+
return new SnapElements({
|
|
223
|
+
publishableKey: this.publishableKey,
|
|
224
|
+
apiBase: this.apiBase,
|
|
225
|
+
environment: this.environment,
|
|
226
|
+
cardFrameUrl: this.cardFrameUrl,
|
|
227
|
+
options
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Confirms the payment by asking the server to dispatch it to
|
|
231
|
+
// the payment method the customer chose inside the mounted
|
|
232
|
+
// Elements. Two selection kinds:
|
|
233
|
+
// - "provider": Wallet flow (evcPlus / zaad / …). Sends the
|
|
234
|
+
// provider straight to the /pay endpoint.
|
|
235
|
+
// - "card": Card Element sandbox flow. Sends provider="mock"
|
|
236
|
+
// alongside `card_token` metadata so the backend can drive
|
|
237
|
+
// the transaction through the mock adapter (real Mastercard
|
|
238
|
+
// tokenization is deferred to a future PR). Live-mode card
|
|
239
|
+
// tokens throw explicitly.
|
|
240
|
+
//
|
|
241
|
+
// Throws if no selection is available — the UI should keep the
|
|
242
|
+
// confirm button disabled until an Element reports a `change`
|
|
243
|
+
// with `complete: true`.
|
|
244
|
+
async confirmPayment(options) {
|
|
245
|
+
const selection = options.elements._getSelection();
|
|
246
|
+
if (selection === null) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
"No payment method selected. Ask the customer to pick a provider before confirming."
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const getVal = options.elements._getValue?.bind(options.elements);
|
|
252
|
+
const customer = getVal?.("customer") ?? void 0;
|
|
253
|
+
const billing = getVal?.("billingAddress") ?? void 0;
|
|
254
|
+
const otp = getVal?.("otp") ?? null;
|
|
255
|
+
const extraMetadata = {};
|
|
256
|
+
if (billing) {
|
|
257
|
+
const billingKeyMap = {
|
|
258
|
+
name: "billing_name",
|
|
259
|
+
line1: "billing_line1",
|
|
260
|
+
line2: "billing_line2",
|
|
261
|
+
city: "billing_city",
|
|
262
|
+
region: "billing_region",
|
|
263
|
+
postalCode: "billing_postal_code",
|
|
264
|
+
country: "billing_country"
|
|
265
|
+
};
|
|
266
|
+
for (const [k, v] of Object.entries(billing)) {
|
|
267
|
+
if (typeof v === "string" && v.length > 0) {
|
|
268
|
+
const wireKey = billingKeyMap[k];
|
|
269
|
+
if (wireKey) extraMetadata[wireKey] = v;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (otp && typeof otp.code === "string" && otp.code.length > 0) {
|
|
274
|
+
extraMetadata.otp_code = otp.code;
|
|
275
|
+
}
|
|
276
|
+
if (selection.kind === "card") {
|
|
277
|
+
if (this.environment === "live") {
|
|
278
|
+
throw new Error(
|
|
279
|
+
"Card Element live-mode tokenization is not yet supported. Use a `pk_test_` key or wire a wallet Element for live payments."
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const result2 = await startPayment(
|
|
283
|
+
this.apiBase,
|
|
284
|
+
this.publishableKey,
|
|
285
|
+
options.clientSecret,
|
|
286
|
+
"mock",
|
|
287
|
+
customer,
|
|
288
|
+
{
|
|
289
|
+
card_token: selection.token,
|
|
290
|
+
card_brand: selection.brand,
|
|
291
|
+
card_last4: selection.last4,
|
|
292
|
+
...extraMetadata
|
|
293
|
+
}
|
|
294
|
+
);
|
|
295
|
+
return { transactionId: result2.transactionId, status: result2.status };
|
|
296
|
+
}
|
|
297
|
+
const result = await startPayment(
|
|
298
|
+
this.apiBase,
|
|
299
|
+
this.publishableKey,
|
|
300
|
+
options.clientSecret,
|
|
301
|
+
selection.provider,
|
|
302
|
+
customer,
|
|
303
|
+
Object.keys(extraMetadata).length > 0 ? extraMetadata : void 0
|
|
304
|
+
);
|
|
305
|
+
return { transactionId: result.transactionId, status: result.status };
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export { DEFAULT_API_BASE, Snap, SnapAuthenticationError, SnapElements, SnapError, SnapValidationError, fetchSession, registerElement, startPayment };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@snap-pay/js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SNAP.js — client-side SDK for creating and confirming SNAP payments in the browser.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SNAP",
|
|
7
|
+
"homepage": "https://github.com/khaledhussein957/SNAP/tree/main/packages/js",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/khaledhussein957/SNAP.git",
|
|
11
|
+
"directory": "packages/js"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/khaledhussein957/SNAP/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["snap", "payments", "checkout", "sdk", "browser", "somalia", "mobile-money"],
|
|
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/types": "1.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"tsup": "^8.3.5",
|
|
43
|
+
"typescript": "^5.7.2",
|
|
44
|
+
"vitest": "^4.1.9"
|
|
45
|
+
}
|
|
46
|
+
}
|