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