@settlr/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,974 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BuyButton: () => BuyButton,
34
+ CheckoutWidget: () => CheckoutWidget,
35
+ SETTLR_CHECKOUT_URL: () => SETTLR_CHECKOUT_URL,
36
+ SUPPORTED_NETWORKS: () => SUPPORTED_NETWORKS,
37
+ Settlr: () => Settlr,
38
+ SettlrProvider: () => SettlrProvider,
39
+ USDC_MINT_DEVNET: () => USDC_MINT_DEVNET,
40
+ USDC_MINT_MAINNET: () => USDC_MINT_MAINNET,
41
+ createWebhookHandler: () => createWebhookHandler,
42
+ formatUSDC: () => formatUSDC,
43
+ parseUSDC: () => parseUSDC,
44
+ parseWebhookPayload: () => parseWebhookPayload,
45
+ shortenAddress: () => shortenAddress,
46
+ usePaymentLink: () => usePaymentLink,
47
+ useSettlr: () => useSettlr,
48
+ verifyWebhookSignature: () => verifyWebhookSignature
49
+ });
50
+ module.exports = __toCommonJS(index_exports);
51
+
52
+ // src/client.ts
53
+ var import_web32 = require("@solana/web3.js");
54
+ var import_spl_token = require("@solana/spl-token");
55
+
56
+ // src/constants.ts
57
+ var import_web3 = require("@solana/web3.js");
58
+ var USDC_MINT_DEVNET = new import_web3.PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU");
59
+ var USDC_MINT_MAINNET = new import_web3.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
60
+ var SETTLR_API_URL = {
61
+ production: "https://settlr.dev/api",
62
+ development: "http://localhost:3000/api"
63
+ };
64
+ var SETTLR_CHECKOUT_URL = {
65
+ production: "https://settlr.dev/pay",
66
+ development: "http://localhost:3000/pay"
67
+ };
68
+ var SUPPORTED_NETWORKS = ["devnet", "mainnet-beta"];
69
+ var USDC_DECIMALS = 6;
70
+ var DEFAULT_RPC_ENDPOINTS = {
71
+ devnet: "https://api.devnet.solana.com",
72
+ "mainnet-beta": "https://api.mainnet-beta.solana.com"
73
+ };
74
+
75
+ // src/utils.ts
76
+ function formatUSDC(lamports, decimals = 2) {
77
+ const amount = Number(lamports) / Math.pow(10, USDC_DECIMALS);
78
+ return amount.toFixed(decimals);
79
+ }
80
+ function parseUSDC(amount) {
81
+ const num = typeof amount === "string" ? parseFloat(amount) : amount;
82
+ return BigInt(Math.round(num * Math.pow(10, USDC_DECIMALS)));
83
+ }
84
+ function shortenAddress(address, chars = 4) {
85
+ if (address.length <= chars * 2 + 3) return address;
86
+ return `${address.slice(0, chars)}...${address.slice(-chars)}`;
87
+ }
88
+ function generatePaymentId() {
89
+ const timestamp = Date.now().toString(36);
90
+ const random = Math.random().toString(36).substring(2, 10);
91
+ return `pay_${timestamp}${random}`;
92
+ }
93
+ function isValidSolanaAddress(address) {
94
+ try {
95
+ const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
96
+ return base58Regex.test(address);
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ function sleep(ms) {
102
+ return new Promise((resolve) => setTimeout(resolve, ms));
103
+ }
104
+ async function retry(fn, maxRetries = 3, baseDelay = 1e3) {
105
+ let lastError;
106
+ for (let i = 0; i < maxRetries; i++) {
107
+ try {
108
+ return await fn();
109
+ } catch (error) {
110
+ lastError = error;
111
+ if (i < maxRetries - 1) {
112
+ await sleep(baseDelay * Math.pow(2, i));
113
+ }
114
+ }
115
+ }
116
+ throw lastError;
117
+ }
118
+
119
+ // src/client.ts
120
+ var Settlr = class {
121
+ constructor(config) {
122
+ this.validated = false;
123
+ if (!config.apiKey) {
124
+ throw new Error("API key is required. Get one at https://settlr.dev/dashboard");
125
+ }
126
+ const walletAddress = typeof config.merchant.walletAddress === "string" ? config.merchant.walletAddress : config.merchant.walletAddress.toBase58();
127
+ if (!isValidSolanaAddress(walletAddress)) {
128
+ throw new Error("Invalid merchant wallet address");
129
+ }
130
+ const network = config.network ?? "devnet";
131
+ const testMode = config.testMode ?? network === "devnet";
132
+ this.config = {
133
+ merchant: {
134
+ ...config.merchant,
135
+ walletAddress
136
+ },
137
+ apiKey: config.apiKey,
138
+ network,
139
+ rpcEndpoint: config.rpcEndpoint ?? DEFAULT_RPC_ENDPOINTS[network],
140
+ testMode
141
+ };
142
+ this.apiBaseUrl = testMode ? SETTLR_API_URL.development : SETTLR_API_URL.production;
143
+ this.connection = new import_web32.Connection(this.config.rpcEndpoint, "confirmed");
144
+ this.usdcMint = network === "devnet" ? USDC_MINT_DEVNET : USDC_MINT_MAINNET;
145
+ this.merchantWallet = new import_web32.PublicKey(walletAddress);
146
+ }
147
+ /**
148
+ * Validate API key with Settlr backend
149
+ */
150
+ async validateApiKey() {
151
+ if (this.validated) return;
152
+ try {
153
+ const response = await fetch(`${this.apiBaseUrl}/sdk/validate`, {
154
+ method: "POST",
155
+ headers: {
156
+ "Content-Type": "application/json",
157
+ "X-API-Key": this.config.apiKey
158
+ },
159
+ body: JSON.stringify({
160
+ walletAddress: this.config.merchant.walletAddress
161
+ })
162
+ });
163
+ if (!response.ok) {
164
+ const error = await response.json().catch(() => ({ error: "Invalid API key" }));
165
+ throw new Error(error.error || "API key validation failed");
166
+ }
167
+ const data = await response.json();
168
+ if (!data.valid) {
169
+ throw new Error(data.error || "Invalid API key");
170
+ }
171
+ this.validated = true;
172
+ this.merchantId = data.merchantId;
173
+ this.tier = data.tier;
174
+ } catch (error) {
175
+ if (error instanceof Error && error.message.includes("fetch")) {
176
+ if (this.config.apiKey.startsWith("sk_test_")) {
177
+ this.validated = true;
178
+ this.tier = "free";
179
+ return;
180
+ }
181
+ }
182
+ throw error;
183
+ }
184
+ }
185
+ /**
186
+ * Get the current tier
187
+ */
188
+ getTier() {
189
+ return this.tier;
190
+ }
191
+ /**
192
+ * Create a payment link
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * const payment = await settlr.createPayment({
197
+ * amount: 29.99,
198
+ * memo: 'Order #1234',
199
+ * successUrl: 'https://mystore.com/success',
200
+ * });
201
+ *
202
+ * console.log(payment.checkoutUrl);
203
+ * // https://settlr.dev/pay?amount=29.99&merchant=...
204
+ * ```
205
+ */
206
+ async createPayment(options) {
207
+ await this.validateApiKey();
208
+ const { amount, memo, orderId, metadata, successUrl, cancelUrl, expiresIn = 3600 } = options;
209
+ if (amount <= 0) {
210
+ throw new Error("Amount must be greater than 0");
211
+ }
212
+ const paymentId = generatePaymentId();
213
+ const amountLamports = parseUSDC(amount);
214
+ const createdAt = /* @__PURE__ */ new Date();
215
+ const expiresAt = new Date(createdAt.getTime() + expiresIn * 1e3);
216
+ const baseUrl = this.config.testMode ? SETTLR_CHECKOUT_URL.development : SETTLR_CHECKOUT_URL.production;
217
+ const params = new URLSearchParams({
218
+ amount: amount.toString(),
219
+ merchant: this.config.merchant.name,
220
+ to: this.config.merchant.walletAddress
221
+ });
222
+ if (memo) params.set("memo", memo);
223
+ if (orderId) params.set("orderId", orderId);
224
+ if (successUrl) params.set("successUrl", successUrl);
225
+ if (cancelUrl) params.set("cancelUrl", cancelUrl);
226
+ if (paymentId) params.set("paymentId", paymentId);
227
+ const checkoutUrl = `${baseUrl}?${params.toString()}`;
228
+ const qrCode = await this.generateQRCode(checkoutUrl);
229
+ const payment = {
230
+ id: paymentId,
231
+ amount,
232
+ amountLamports,
233
+ status: "pending",
234
+ merchantAddress: this.config.merchant.walletAddress,
235
+ checkoutUrl,
236
+ qrCode,
237
+ memo,
238
+ orderId,
239
+ metadata,
240
+ createdAt,
241
+ expiresAt
242
+ };
243
+ return payment;
244
+ }
245
+ /**
246
+ * Build a transaction for direct payment (for wallet integration)
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * const tx = await settlr.buildTransaction({
251
+ * payerPublicKey: wallet.publicKey,
252
+ * amount: 29.99,
253
+ * });
254
+ *
255
+ * const signature = await wallet.sendTransaction(tx, connection);
256
+ * ```
257
+ */
258
+ async buildTransaction(options) {
259
+ await this.validateApiKey();
260
+ const { payerPublicKey, amount, memo } = options;
261
+ const amountLamports = parseUSDC(amount);
262
+ const payerAta = await (0, import_spl_token.getAssociatedTokenAddress)(this.usdcMint, payerPublicKey);
263
+ const merchantAta = await (0, import_spl_token.getAssociatedTokenAddress)(this.usdcMint, this.merchantWallet);
264
+ const instructions = [];
265
+ try {
266
+ await (0, import_spl_token.getAccount)(this.connection, merchantAta);
267
+ } catch (error) {
268
+ if (error instanceof import_spl_token.TokenAccountNotFoundError) {
269
+ instructions.push(
270
+ (0, import_spl_token.createAssociatedTokenAccountInstruction)(
271
+ payerPublicKey,
272
+ merchantAta,
273
+ this.merchantWallet,
274
+ this.usdcMint
275
+ )
276
+ );
277
+ } else {
278
+ throw error;
279
+ }
280
+ }
281
+ instructions.push(
282
+ (0, import_spl_token.createTransferInstruction)(
283
+ payerAta,
284
+ merchantAta,
285
+ payerPublicKey,
286
+ BigInt(amountLamports)
287
+ )
288
+ );
289
+ if (memo) {
290
+ const MEMO_PROGRAM_ID = new import_web32.PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
291
+ instructions.push(
292
+ new import_web32.TransactionInstruction({
293
+ keys: [{ pubkey: payerPublicKey, isSigner: true, isWritable: false }],
294
+ programId: MEMO_PROGRAM_ID,
295
+ data: Buffer.from(memo, "utf-8")
296
+ })
297
+ );
298
+ }
299
+ const { blockhash } = await this.connection.getLatestBlockhash();
300
+ const transaction = new import_web32.Transaction();
301
+ transaction.recentBlockhash = blockhash;
302
+ transaction.feePayer = payerPublicKey;
303
+ transaction.add(...instructions);
304
+ return transaction;
305
+ }
306
+ /**
307
+ * Execute a direct payment (requires wallet adapter)
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * const result = await settlr.pay({
312
+ * wallet,
313
+ * amount: 29.99,
314
+ * memo: 'Order #1234',
315
+ * });
316
+ *
317
+ * if (result.success) {
318
+ * console.log('Paid!', result.signature);
319
+ * }
320
+ * ```
321
+ */
322
+ async pay(options) {
323
+ const { wallet, amount, memo, txOptions } = options;
324
+ try {
325
+ const transaction = await this.buildTransaction({
326
+ payerPublicKey: wallet.publicKey,
327
+ amount,
328
+ memo
329
+ });
330
+ const signedTx = await wallet.signTransaction(transaction);
331
+ const signature = await retry(
332
+ () => this.connection.sendRawTransaction(signedTx.serialize(), {
333
+ skipPreflight: txOptions?.skipPreflight ?? false,
334
+ preflightCommitment: txOptions?.commitment ?? "confirmed",
335
+ maxRetries: txOptions?.maxRetries ?? 3
336
+ }),
337
+ 3
338
+ );
339
+ const { blockhash, lastValidBlockHeight } = await this.connection.getLatestBlockhash();
340
+ await this.connection.confirmTransaction({
341
+ blockhash,
342
+ lastValidBlockHeight,
343
+ signature
344
+ });
345
+ return {
346
+ success: true,
347
+ signature,
348
+ amount,
349
+ merchantAddress: this.merchantWallet.toBase58()
350
+ };
351
+ } catch (error) {
352
+ return {
353
+ success: false,
354
+ signature: "",
355
+ amount,
356
+ merchantAddress: this.merchantWallet.toBase58(),
357
+ error: error instanceof Error ? error.message : "Payment failed"
358
+ };
359
+ }
360
+ }
361
+ /**
362
+ * Check payment status by transaction signature
363
+ */
364
+ async getPaymentStatus(signature) {
365
+ try {
366
+ const status = await this.connection.getSignatureStatus(signature);
367
+ if (!status.value) {
368
+ return "pending";
369
+ }
370
+ if (status.value.err) {
371
+ return "failed";
372
+ }
373
+ if (status.value.confirmationStatus === "confirmed" || status.value.confirmationStatus === "finalized") {
374
+ return "completed";
375
+ }
376
+ return "pending";
377
+ } catch {
378
+ return "failed";
379
+ }
380
+ }
381
+ /**
382
+ * Create a hosted checkout session (like Stripe Checkout)
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * const session = await settlr.createCheckoutSession({
387
+ * amount: 29.99,
388
+ * description: 'Premium Plan',
389
+ * successUrl: 'https://mystore.com/success',
390
+ * cancelUrl: 'https://mystore.com/cancel',
391
+ * webhookUrl: 'https://mystore.com/api/webhooks/settlr',
392
+ * });
393
+ *
394
+ * // Redirect customer to hosted checkout
395
+ * window.location.href = session.url;
396
+ * ```
397
+ */
398
+ async createCheckoutSession(options) {
399
+ const { amount, description, metadata, successUrl, cancelUrl, webhookUrl } = options;
400
+ if (amount <= 0) {
401
+ throw new Error("Amount must be greater than 0");
402
+ }
403
+ const baseUrl = this.config.testMode ? "http://localhost:3000" : "https://settlr.dev";
404
+ const response = await fetch(`${baseUrl}/api/checkout/sessions`, {
405
+ method: "POST",
406
+ headers: {
407
+ "Content-Type": "application/json",
408
+ ...this.config.apiKey && { "Authorization": `Bearer ${this.config.apiKey}` }
409
+ },
410
+ body: JSON.stringify({
411
+ merchantId: this.config.merchant.name.toLowerCase().replace(/\s+/g, "-"),
412
+ merchantName: this.config.merchant.name,
413
+ merchantWallet: this.config.merchant.walletAddress,
414
+ amount,
415
+ description,
416
+ metadata,
417
+ successUrl,
418
+ cancelUrl,
419
+ webhookUrl
420
+ })
421
+ });
422
+ if (!response.ok) {
423
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
424
+ throw new Error(error.error || "Failed to create checkout session");
425
+ }
426
+ return response.json();
427
+ }
428
+ /**
429
+ * Get merchant's USDC balance
430
+ */
431
+ async getMerchantBalance() {
432
+ try {
433
+ const ata = await (0, import_spl_token.getAssociatedTokenAddress)(this.usdcMint, this.merchantWallet);
434
+ const account = await (0, import_spl_token.getAccount)(this.connection, ata);
435
+ return Number(account.amount) / 1e6;
436
+ } catch {
437
+ return 0;
438
+ }
439
+ }
440
+ /**
441
+ * Generate QR code for payment URL
442
+ */
443
+ async generateQRCode(url) {
444
+ const encoded = encodeURIComponent(url);
445
+ return `data:image/svg+xml,${encoded}`;
446
+ }
447
+ /**
448
+ * Get the connection instance
449
+ */
450
+ getConnection() {
451
+ return this.connection;
452
+ }
453
+ /**
454
+ * Get merchant wallet address
455
+ */
456
+ getMerchantAddress() {
457
+ return this.merchantWallet;
458
+ }
459
+ /**
460
+ * Get USDC mint address
461
+ */
462
+ getUsdcMint() {
463
+ return this.usdcMint;
464
+ }
465
+ };
466
+
467
+ // src/react.tsx
468
+ var import_react = require("react");
469
+ var import_wallet_adapter_react = require("@solana/wallet-adapter-react");
470
+ var import_jsx_runtime = require("react/jsx-runtime");
471
+ var SettlrContext = (0, import_react.createContext)(null);
472
+ function SettlrProvider({ children, config }) {
473
+ const { connection } = (0, import_wallet_adapter_react.useConnection)();
474
+ const wallet = (0, import_wallet_adapter_react.useWallet)();
475
+ const settlr = (0, import_react.useMemo)(() => {
476
+ return new Settlr({
477
+ ...config,
478
+ rpcEndpoint: connection.rpcEndpoint
479
+ });
480
+ }, [config, connection.rpcEndpoint]);
481
+ const value = (0, import_react.useMemo)(
482
+ () => ({
483
+ settlr,
484
+ connected: wallet.connected,
485
+ createPayment: (options) => {
486
+ return settlr.createPayment(options);
487
+ },
488
+ pay: async (options) => {
489
+ if (!wallet.publicKey || !wallet.signTransaction) {
490
+ return {
491
+ success: false,
492
+ signature: "",
493
+ amount: options.amount,
494
+ merchantAddress: settlr.getMerchantAddress().toBase58(),
495
+ error: "Wallet not connected"
496
+ };
497
+ }
498
+ return settlr.pay({
499
+ wallet: {
500
+ publicKey: wallet.publicKey,
501
+ signTransaction: wallet.signTransaction
502
+ },
503
+ amount: options.amount,
504
+ memo: options.memo
505
+ });
506
+ },
507
+ getBalance: () => {
508
+ return settlr.getMerchantBalance();
509
+ }
510
+ }),
511
+ [settlr, wallet]
512
+ );
513
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SettlrContext.Provider, { value, children });
514
+ }
515
+ function useSettlr() {
516
+ const context = (0, import_react.useContext)(SettlrContext);
517
+ if (!context) {
518
+ throw new Error("useSettlr must be used within a SettlrProvider");
519
+ }
520
+ return context;
521
+ }
522
+
523
+ // src/components.tsx
524
+ var import_react2 = require("react");
525
+ var import_jsx_runtime2 = require("react/jsx-runtime");
526
+ var defaultStyles = {
527
+ base: {
528
+ display: "inline-flex",
529
+ alignItems: "center",
530
+ justifyContent: "center",
531
+ gap: "8px",
532
+ fontWeight: 600,
533
+ borderRadius: "12px",
534
+ cursor: "pointer",
535
+ transition: "all 0.2s ease",
536
+ border: "none",
537
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
538
+ },
539
+ primary: {
540
+ background: "linear-gradient(135deg, #f472b6 0%, #67e8f9 100%)",
541
+ color: "white"
542
+ },
543
+ secondary: {
544
+ background: "#12121a",
545
+ color: "white",
546
+ border: "1px solid rgba(255,255,255,0.1)"
547
+ },
548
+ outline: {
549
+ background: "transparent",
550
+ color: "#f472b6",
551
+ border: "2px solid #f472b6"
552
+ },
553
+ sm: {
554
+ padding: "8px 16px",
555
+ fontSize: "14px"
556
+ },
557
+ md: {
558
+ padding: "12px 24px",
559
+ fontSize: "16px"
560
+ },
561
+ lg: {
562
+ padding: "16px 32px",
563
+ fontSize: "18px"
564
+ },
565
+ disabled: {
566
+ opacity: 0.5,
567
+ cursor: "not-allowed"
568
+ },
569
+ loading: {
570
+ opacity: 0.8
571
+ }
572
+ };
573
+ function BuyButton({
574
+ amount,
575
+ memo,
576
+ orderId,
577
+ children,
578
+ onSuccess,
579
+ onError,
580
+ onProcessing,
581
+ useRedirect = false,
582
+ successUrl,
583
+ cancelUrl,
584
+ className,
585
+ style,
586
+ disabled = false,
587
+ variant = "primary",
588
+ size = "md"
589
+ }) {
590
+ const { pay, createPayment, connected } = useSettlr();
591
+ const [loading, setLoading] = (0, import_react2.useState)(false);
592
+ const [status, setStatus] = (0, import_react2.useState)("idle");
593
+ const handleClick = (0, import_react2.useCallback)(async () => {
594
+ if (disabled || loading) return;
595
+ setLoading(true);
596
+ setStatus("processing");
597
+ onProcessing?.();
598
+ try {
599
+ if (useRedirect) {
600
+ const payment = await createPayment({
601
+ amount,
602
+ memo,
603
+ orderId,
604
+ successUrl,
605
+ cancelUrl
606
+ });
607
+ window.location.href = payment.checkoutUrl;
608
+ } else {
609
+ const result = await pay({ amount, memo });
610
+ if (result.success) {
611
+ setStatus("success");
612
+ onSuccess?.({
613
+ signature: result.signature,
614
+ amount: result.amount,
615
+ merchantAddress: result.merchantAddress
616
+ });
617
+ } else {
618
+ throw new Error(result.error || "Payment failed");
619
+ }
620
+ }
621
+ } catch (error) {
622
+ setStatus("error");
623
+ onError?.(error instanceof Error ? error : new Error("Payment failed"));
624
+ } finally {
625
+ setLoading(false);
626
+ }
627
+ }, [
628
+ amount,
629
+ memo,
630
+ orderId,
631
+ disabled,
632
+ loading,
633
+ useRedirect,
634
+ successUrl,
635
+ cancelUrl,
636
+ pay,
637
+ createPayment,
638
+ onSuccess,
639
+ onError,
640
+ onProcessing
641
+ ]);
642
+ const buttonStyle = {
643
+ ...defaultStyles.base,
644
+ ...defaultStyles[variant],
645
+ ...defaultStyles[size],
646
+ ...disabled ? defaultStyles.disabled : {},
647
+ ...loading ? defaultStyles.loading : {},
648
+ ...style
649
+ };
650
+ const buttonContent = loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
651
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner, {}),
652
+ "Processing..."
653
+ ] }) : children || `Pay $${amount.toFixed(2)}`;
654
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
655
+ "button",
656
+ {
657
+ onClick: handleClick,
658
+ disabled: disabled || loading || !connected,
659
+ className,
660
+ style: buttonStyle,
661
+ type: "button",
662
+ children: !connected ? "Connect Wallet" : buttonContent
663
+ }
664
+ );
665
+ }
666
+ function Spinner() {
667
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
668
+ "svg",
669
+ {
670
+ width: "16",
671
+ height: "16",
672
+ viewBox: "0 0 16 16",
673
+ fill: "none",
674
+ style: { animation: "spin 1s linear infinite" },
675
+ children: [
676
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }` }),
677
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
678
+ "circle",
679
+ {
680
+ cx: "8",
681
+ cy: "8",
682
+ r: "6",
683
+ stroke: "currentColor",
684
+ strokeWidth: "2",
685
+ strokeLinecap: "round",
686
+ strokeDasharray: "32",
687
+ strokeDashoffset: "12"
688
+ }
689
+ )
690
+ ]
691
+ }
692
+ );
693
+ }
694
+ var widgetStyles = {
695
+ container: {
696
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
697
+ borderRadius: "16px",
698
+ overflow: "hidden",
699
+ maxWidth: "400px",
700
+ width: "100%"
701
+ },
702
+ containerDark: {
703
+ background: "#12121a",
704
+ border: "1px solid rgba(255,255,255,0.1)",
705
+ color: "white"
706
+ },
707
+ containerLight: {
708
+ background: "white",
709
+ border: "1px solid #e5e7eb",
710
+ color: "#111827"
711
+ },
712
+ header: {
713
+ padding: "24px",
714
+ borderBottom: "1px solid rgba(255,255,255,0.1)"
715
+ },
716
+ productImage: {
717
+ width: "64px",
718
+ height: "64px",
719
+ borderRadius: "12px",
720
+ objectFit: "cover",
721
+ marginBottom: "16px"
722
+ },
723
+ productName: {
724
+ fontSize: "20px",
725
+ fontWeight: 600,
726
+ margin: "0 0 4px 0"
727
+ },
728
+ productDescription: {
729
+ fontSize: "14px",
730
+ opacity: 0.7,
731
+ margin: 0
732
+ },
733
+ body: {
734
+ padding: "24px"
735
+ },
736
+ row: {
737
+ display: "flex",
738
+ justifyContent: "space-between",
739
+ alignItems: "center",
740
+ marginBottom: "12px"
741
+ },
742
+ label: {
743
+ fontSize: "14px",
744
+ opacity: 0.7
745
+ },
746
+ value: {
747
+ fontSize: "14px",
748
+ fontWeight: 500
749
+ },
750
+ total: {
751
+ fontSize: "24px",
752
+ fontWeight: 700
753
+ },
754
+ divider: {
755
+ height: "1px",
756
+ background: "rgba(255,255,255,0.1)",
757
+ margin: "16px 0"
758
+ },
759
+ footer: {
760
+ padding: "24px",
761
+ paddingTop: "0"
762
+ },
763
+ branding: {
764
+ textAlign: "center",
765
+ fontSize: "12px",
766
+ opacity: 0.5,
767
+ marginTop: "16px"
768
+ }
769
+ };
770
+ function CheckoutWidget({
771
+ amount,
772
+ productName,
773
+ productDescription,
774
+ productImage,
775
+ merchantName,
776
+ memo,
777
+ orderId,
778
+ onSuccess,
779
+ onError,
780
+ onCancel,
781
+ className,
782
+ style,
783
+ theme = "dark",
784
+ showBranding = true
785
+ }) {
786
+ const { connected } = useSettlr();
787
+ const [status, setStatus] = (0, import_react2.useState)("idle");
788
+ const containerStyle = {
789
+ ...widgetStyles.container,
790
+ ...theme === "dark" ? widgetStyles.containerDark : widgetStyles.containerLight,
791
+ ...style
792
+ };
793
+ const dividerStyle = {
794
+ ...widgetStyles.divider,
795
+ background: theme === "dark" ? "rgba(255,255,255,0.1)" : "#e5e7eb"
796
+ };
797
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: containerStyle, children: [
798
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: widgetStyles.header, children: [
799
+ productImage && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
800
+ "img",
801
+ {
802
+ src: productImage,
803
+ alt: productName,
804
+ style: widgetStyles.productImage
805
+ }
806
+ ),
807
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { style: widgetStyles.productName, children: productName }),
808
+ productDescription && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: widgetStyles.productDescription, children: productDescription })
809
+ ] }),
810
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: widgetStyles.body, children: [
811
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: widgetStyles.row, children: [
812
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: widgetStyles.label, children: "Subtotal" }),
813
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: widgetStyles.value, children: [
814
+ "$",
815
+ amount.toFixed(2)
816
+ ] })
817
+ ] }),
818
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: widgetStyles.row, children: [
819
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: widgetStyles.label, children: "Network Fee" }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: widgetStyles.value, children: "$0.01" })
821
+ ] }),
822
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: dividerStyle }),
823
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: widgetStyles.row, children: [
824
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: widgetStyles.label, children: "Total" }),
825
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: widgetStyles.total, children: [
826
+ "$",
827
+ (amount + 0.01).toFixed(2),
828
+ " USDC"
829
+ ] })
830
+ ] })
831
+ ] }),
832
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: widgetStyles.footer, children: [
833
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
834
+ BuyButton,
835
+ {
836
+ amount,
837
+ memo: memo || productName,
838
+ orderId,
839
+ onSuccess: (result) => {
840
+ setStatus("success");
841
+ onSuccess?.(result);
842
+ },
843
+ onError: (error) => {
844
+ setStatus("error");
845
+ onError?.(error);
846
+ },
847
+ onProcessing: () => setStatus("processing"),
848
+ size: "lg",
849
+ style: { width: "100%" },
850
+ children: status === "success" ? "\u2713 Payment Complete" : status === "error" ? "Payment Failed - Retry" : `Pay $${(amount + 0.01).toFixed(2)} USDC`
851
+ }
852
+ ),
853
+ showBranding && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: widgetStyles.branding, children: [
854
+ "Secured by ",
855
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: "Settlr" }),
856
+ " \u2022 Powered by Solana"
857
+ ] })
858
+ ] })
859
+ ] });
860
+ }
861
+ function usePaymentLink(config) {
862
+ const {
863
+ merchantWallet,
864
+ merchantName,
865
+ baseUrl = "https://settlr.dev/pay"
866
+ } = config;
867
+ const generateLink = (0, import_react2.useCallback)(
868
+ (options) => {
869
+ const params = new URLSearchParams({
870
+ amount: options.amount.toString(),
871
+ merchant: merchantName,
872
+ to: merchantWallet
873
+ });
874
+ if (options.memo) params.set("memo", options.memo);
875
+ if (options.orderId) params.set("orderId", options.orderId);
876
+ if (options.successUrl) params.set("successUrl", options.successUrl);
877
+ if (options.cancelUrl) params.set("cancelUrl", options.cancelUrl);
878
+ return `${baseUrl}?${params.toString()}`;
879
+ },
880
+ [merchantWallet, merchantName, baseUrl]
881
+ );
882
+ const generateQRCode = (0, import_react2.useCallback)(
883
+ async (options) => {
884
+ const link = generateLink(options);
885
+ const qrUrl = `https://chart.googleapis.com/chart?chs=300x300&cht=qr&chl=${encodeURIComponent(
886
+ link
887
+ )}&choe=UTF-8`;
888
+ return qrUrl;
889
+ },
890
+ [generateLink]
891
+ );
892
+ return {
893
+ generateLink,
894
+ generateQRCode
895
+ };
896
+ }
897
+
898
+ // src/webhooks.ts
899
+ var import_crypto = __toESM(require("crypto"));
900
+ function generateWebhookSignature(payload, secret) {
901
+ return import_crypto.default.createHmac("sha256", secret).update(payload).digest("hex");
902
+ }
903
+ function verifyWebhookSignature(payload, signature, secret) {
904
+ const expectedSignature = generateWebhookSignature(payload, secret);
905
+ try {
906
+ return import_crypto.default.timingSafeEqual(
907
+ Buffer.from(signature),
908
+ Buffer.from(expectedSignature)
909
+ );
910
+ } catch {
911
+ return false;
912
+ }
913
+ }
914
+ function parseWebhookPayload(rawBody, signature, secret) {
915
+ if (!verifyWebhookSignature(rawBody, signature, secret)) {
916
+ throw new Error("Invalid webhook signature");
917
+ }
918
+ const payload = JSON.parse(rawBody);
919
+ return payload;
920
+ }
921
+ function createWebhookHandler(options) {
922
+ const { secret, handlers, onError } = options;
923
+ return async (req, res) => {
924
+ try {
925
+ let rawBody;
926
+ if (typeof req.body === "string") {
927
+ rawBody = req.body;
928
+ } else if (Buffer.isBuffer(req.body)) {
929
+ rawBody = req.body.toString("utf8");
930
+ } else {
931
+ rawBody = JSON.stringify(req.body);
932
+ }
933
+ const signature = req.headers["x-settlr-signature"];
934
+ if (!signature) {
935
+ res.status(400).json({ error: "Missing signature header" });
936
+ return;
937
+ }
938
+ const event = parseWebhookPayload(rawBody, signature, secret);
939
+ const handler = handlers[event.type];
940
+ if (handler) {
941
+ await handler(event);
942
+ }
943
+ res.status(200).json({ received: true });
944
+ } catch (error) {
945
+ if (onError && error instanceof Error) {
946
+ onError(error);
947
+ }
948
+ if (error instanceof Error && error.message === "Invalid webhook signature") {
949
+ res.status(401).json({ error: "Invalid signature" });
950
+ } else {
951
+ res.status(500).json({ error: "Webhook processing failed" });
952
+ }
953
+ }
954
+ };
955
+ }
956
+ // Annotate the CommonJS export names for ESM import in node:
957
+ 0 && (module.exports = {
958
+ BuyButton,
959
+ CheckoutWidget,
960
+ SETTLR_CHECKOUT_URL,
961
+ SUPPORTED_NETWORKS,
962
+ Settlr,
963
+ SettlrProvider,
964
+ USDC_MINT_DEVNET,
965
+ USDC_MINT_MAINNET,
966
+ createWebhookHandler,
967
+ formatUSDC,
968
+ parseUSDC,
969
+ parseWebhookPayload,
970
+ shortenAddress,
971
+ usePaymentLink,
972
+ useSettlr,
973
+ verifyWebhookSignature
974
+ });