@loofta/pay-sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @loofta/pay-sdk
2
+
3
+ Embeddable **Loofta Pay Button** for React. The button opens **Loofta’s hosted checkout** — your customers pay there, and funds go to the destination wallet, network, and token configured in your organization.
4
+
5
+ ## Requirements
6
+
7
+ - **Organization ID** — You must have an organization set up with Loofta. Your **destination wallet address**, **network**, and **token** are configured in that organization.
8
+ - **Don’t have an organization ID?** [Contact Loofta](https://t.me/looftaxyz) to get one.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @loofta/pay-sdk
14
+ # or
15
+ yarn add @loofta/pay-sdk
16
+ pnpm add @loofta/pay-sdk
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### React
22
+
23
+ ```tsx
24
+ import { PayButton } from '@loofta/pay-sdk';
25
+
26
+ <PayButton
27
+ organizationId="your-org-id"
28
+ amount={100}
29
+ buttonText="Pay $100"
30
+ onSuccess={(paymentId) => {
31
+ console.log('Payment completed:', paymentId);
32
+ }}
33
+ />
34
+ ```
35
+
36
+ You usually **don’t** need `checkoutBaseUrl`. The button opens Loofta’s checkout; if your app is on a different domain, the default URL is used. Only set `checkoutBaseUrl` if Loofta has given you a specific checkout URL.
37
+
38
+ ### Props
39
+
40
+ | Prop | Type | Description |
41
+ |------|------|-------------|
42
+ | `organizationId` | `string` | **Required.** Your Loofta organization ID. Destination wallet, network, and token are set in your organization. Don’t have one? Contact Loofta. |
43
+ | `amount` | `number \| string` | Optional payment amount (USD). |
44
+ | `checkoutBaseUrl` | `string` | Optional. URL of the Loofta Pay checkout page. Only set if the button is on a different domain and you need to point to a specific Loofta checkout URL; otherwise omit. |
45
+ | `buttonBgColor` | `string` | Optional button background (CSS color). |
46
+ | `pageBgColor` | `string` | Optional checkout page background. |
47
+ | `callbackUrl` | `string` | Optional URL to redirect after payment. |
48
+ | `onSuccess` | `(paymentId: string) => void` | Called when payment completes (popup mode). |
49
+ | `buttonText` | `string` | Button label (default: "Pay with Loofta"). |
50
+ | `successText` | `string` | Text after success (default: "Paid Successfully"). |
51
+ | `openMode` | `'popup' \| 'redirect' \| 'tab'` | How to open checkout (default: `popup`). |
52
+ | `disabled` | `boolean` | Disable the button. |
53
+ | `className` | `string` | Optional CSS class. |
54
+
55
+ ### How it works
56
+
57
+ 1. User clicks the button on your site.
58
+ 2. Loofta’s hosted checkout opens (popup, new tab, or redirect).
59
+ 3. User pays with the token/network they choose; funds are sent to **your organization’s configured destination wallet, network, and token**.
60
+ 4. You get the outcome via `onSuccess` and/or `callbackUrl`.
61
+
62
+ ### Embed code helpers
63
+
64
+ ```tsx
65
+ import { generateEmbedCode, generateScriptEmbed } from '@loofta/pay-sdk';
66
+
67
+ const reactSnippet = generateEmbedCode({
68
+ organizationId: 'your-org-id',
69
+ amount: 100,
70
+ });
71
+
72
+ const scriptSnippet = generateScriptEmbed({
73
+ organizationId: 'your-org-id',
74
+ amount: 100,
75
+ });
76
+ ```
77
+
78
+ ## Build
79
+
80
+ ```bash
81
+ cd packages/loofta-pay-sdk
82
+ npm install
83
+ npm run build
84
+ ```
85
+
86
+ Output: `dist/` (ESM, CJS, and types).
87
+
88
+ ## Publish to npm
89
+
90
+ 1. **Build the package**
91
+ ```bash
92
+ cd packages/loofta-pay-sdk
93
+ npm install
94
+ npm run build
95
+ ```
96
+
97
+ 2. **Log in to npm** (create an account at [npmjs.com](https://www.npmjs.com) if needed)
98
+ ```bash
99
+ npm login
100
+ ```
101
+ Enter your npm username, password, and email when prompted.
102
+
103
+ 3. **Publish**
104
+ ```bash
105
+ npm publish
106
+ ```
107
+ Scoped packages like `@loofta/pay-sdk` use `publishConfig.access: "public"` in package.json, so you don’t need `--access public` each time.
108
+
109
+ 4. **Later releases**: bump `version` in `package.json`, run `npm run build`, then `npm publish` again.
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,43 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface PayButtonProps {
4
+ /**
5
+ * Organization ID (required). Must be set up with Loofta — your destination wallet, network, and token are configured in your organization.
6
+ * If you don't have an organization ID, contact Loofta to get one.
7
+ */
8
+ organizationId: string;
9
+ /** Payment amount in USD (optional) */
10
+ amount?: number | string;
11
+ /** Button background color (optional) */
12
+ buttonBgColor?: string;
13
+ /** Checkout page background color (optional - falls back to org settings) */
14
+ pageBgColor?: string;
15
+ /** @deprecated Use buttonBgColor instead */
16
+ bgColor?: string;
17
+ /** Callback URL after payment (optional) */
18
+ callbackUrl?: string;
19
+ /** Callback function after payment (optional) */
20
+ onSuccess?: (paymentId: string) => void;
21
+ /** Button text (optional) */
22
+ buttonText?: string;
23
+ /** Success text shown after payment (optional) */
24
+ successText?: string;
25
+ /** Custom className for button styling */
26
+ className?: string;
27
+ /** Open checkout in new tab vs popup (default: popup) */
28
+ openMode?: 'popup' | 'redirect' | 'tab';
29
+ /** Disable button */
30
+ disabled?: boolean;
31
+ /**
32
+ * Base URL of the Loofta Pay hosted checkout page. Checkout is always Loofta's — this is only needed if the button is embedded on a different domain than the default Loofta Pay app. Usually omit; default is used.
33
+ */
34
+ checkoutBaseUrl?: string;
35
+ }
36
+ /**
37
+ * Loofta Pay Button – opens your Loofta Pay checkout (same API as your app).
38
+ */
39
+ declare function PayButton({ organizationId, amount, buttonBgColor, pageBgColor, bgColor, callbackUrl, onSuccess, buttonText, successText, className, openMode, disabled, checkoutBaseUrl, }: PayButtonProps): react_jsx_runtime.JSX.Element;
40
+ declare function generateEmbedCode(props: Partial<PayButtonProps>): string;
41
+ declare function generateScriptEmbed(props: Partial<PayButtonProps>): string;
42
+
43
+ export { PayButton, type PayButtonProps, generateEmbedCode, generateScriptEmbed };
@@ -0,0 +1,43 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface PayButtonProps {
4
+ /**
5
+ * Organization ID (required). Must be set up with Loofta — your destination wallet, network, and token are configured in your organization.
6
+ * If you don't have an organization ID, contact Loofta to get one.
7
+ */
8
+ organizationId: string;
9
+ /** Payment amount in USD (optional) */
10
+ amount?: number | string;
11
+ /** Button background color (optional) */
12
+ buttonBgColor?: string;
13
+ /** Checkout page background color (optional - falls back to org settings) */
14
+ pageBgColor?: string;
15
+ /** @deprecated Use buttonBgColor instead */
16
+ bgColor?: string;
17
+ /** Callback URL after payment (optional) */
18
+ callbackUrl?: string;
19
+ /** Callback function after payment (optional) */
20
+ onSuccess?: (paymentId: string) => void;
21
+ /** Button text (optional) */
22
+ buttonText?: string;
23
+ /** Success text shown after payment (optional) */
24
+ successText?: string;
25
+ /** Custom className for button styling */
26
+ className?: string;
27
+ /** Open checkout in new tab vs popup (default: popup) */
28
+ openMode?: 'popup' | 'redirect' | 'tab';
29
+ /** Disable button */
30
+ disabled?: boolean;
31
+ /**
32
+ * Base URL of the Loofta Pay hosted checkout page. Checkout is always Loofta's — this is only needed if the button is embedded on a different domain than the default Loofta Pay app. Usually omit; default is used.
33
+ */
34
+ checkoutBaseUrl?: string;
35
+ }
36
+ /**
37
+ * Loofta Pay Button – opens your Loofta Pay checkout (same API as your app).
38
+ */
39
+ declare function PayButton({ organizationId, amount, buttonBgColor, pageBgColor, bgColor, callbackUrl, onSuccess, buttonText, successText, className, openMode, disabled, checkoutBaseUrl, }: PayButtonProps): react_jsx_runtime.JSX.Element;
40
+ declare function generateEmbedCode(props: Partial<PayButtonProps>): string;
41
+ declare function generateScriptEmbed(props: Partial<PayButtonProps>): string;
42
+
43
+ export { PayButton, type PayButtonProps, generateEmbedCode, generateScriptEmbed };
package/dist/index.js ADDED
@@ -0,0 +1,206 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ // src/PayButton.tsx
7
+ var DEFAULT_STYLE = {
8
+ background: "linear-gradient(to right, #FF0F00, #EAB308)",
9
+ color: "#ffffff",
10
+ border: "none",
11
+ borderRadius: "12px",
12
+ padding: "12px 24px",
13
+ fontSize: "16px",
14
+ fontWeight: 600,
15
+ cursor: "pointer",
16
+ display: "inline-flex",
17
+ alignItems: "center",
18
+ justifyContent: "center",
19
+ gap: "8px",
20
+ minWidth: "160px",
21
+ transition: "transform 0.2s, box-shadow 0.2s",
22
+ boxShadow: "0 4px 14px rgba(255, 15, 0, 0.25)"
23
+ };
24
+ var HOVER_STYLE = {
25
+ transform: "scale(1.02)",
26
+ boxShadow: "0 6px 20px rgba(255, 15, 0, 0.35)"
27
+ };
28
+ function LoaderIcon() {
29
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
30
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10", strokeOpacity: 0.25 }),
31
+ /* @__PURE__ */ jsxRuntime.jsx(
32
+ "path",
33
+ {
34
+ d: "M12 2a10 10 0 0 1 10 10",
35
+ style: { transformOrigin: "12px 12px" },
36
+ children: /* @__PURE__ */ jsxRuntime.jsx(
37
+ "animateTransform",
38
+ {
39
+ attributeName: "transform",
40
+ type: "rotate",
41
+ from: "0 12 12",
42
+ to: "360 12 12",
43
+ dur: "0.8s",
44
+ repeatCount: "indefinite"
45
+ }
46
+ )
47
+ }
48
+ )
49
+ ] });
50
+ }
51
+ function CheckIcon() {
52
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) });
53
+ }
54
+ function PayIcon() {
55
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) });
56
+ }
57
+ function PayButton({
58
+ organizationId,
59
+ amount,
60
+ buttonBgColor,
61
+ pageBgColor,
62
+ bgColor,
63
+ callbackUrl,
64
+ onSuccess,
65
+ buttonText = "Pay with Loofta",
66
+ successText = "Paid Successfully",
67
+ className,
68
+ openMode = "popup",
69
+ disabled = false,
70
+ checkoutBaseUrl
71
+ }) {
72
+ const [loading, setLoading] = react.useState(false);
73
+ const [paid, setPaid] = react.useState(false);
74
+ const [isHovered, setIsHovered] = react.useState(false);
75
+ const effectiveButtonBgColor = buttonBgColor || bgColor;
76
+ const getCheckoutUrl = react.useCallback(() => {
77
+ const defaultBase = "https://pay.loofta.com";
78
+ const base = checkoutBaseUrl != null && checkoutBaseUrl !== "" ? checkoutBaseUrl.replace(/\/$/, "") : defaultBase;
79
+ const path = base.endsWith("/checkout") ? base : `${base}/checkout`;
80
+ const params = new URLSearchParams();
81
+ params.set("organizationId", organizationId);
82
+ if (amount) params.set("amount", String(amount));
83
+ if (pageBgColor) params.set("bgColor", encodeURIComponent(pageBgColor));
84
+ if (callbackUrl) params.set("callback", encodeURIComponent(callbackUrl));
85
+ return `${path}?${params.toString()}`;
86
+ }, [checkoutBaseUrl, organizationId, amount, pageBgColor, callbackUrl]);
87
+ const handleClick = react.useCallback(() => {
88
+ if (disabled || loading || paid) return;
89
+ setLoading(true);
90
+ const url = getCheckoutUrl();
91
+ if (openMode === "redirect") {
92
+ window.location.href = url;
93
+ return;
94
+ }
95
+ if (openMode === "tab") {
96
+ window.open(url, "_blank", "noopener,noreferrer");
97
+ setLoading(false);
98
+ return;
99
+ }
100
+ const width = 500;
101
+ const height = 700;
102
+ const left = window.screenX + (window.outerWidth - width) / 2;
103
+ const top = window.screenY + (window.outerHeight - height) / 2;
104
+ const popup = window.open(
105
+ url,
106
+ "loofta-pay-checkout",
107
+ `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`
108
+ );
109
+ const handleMessage = (event) => {
110
+ if (event.data?.type === "loofta-payment-success") {
111
+ setLoading(false);
112
+ setPaid(true);
113
+ onSuccess?.(event.data.paymentId);
114
+ popup?.close();
115
+ window.removeEventListener("message", handleMessage);
116
+ }
117
+ };
118
+ window.addEventListener("message", handleMessage);
119
+ const checkClosed = setInterval(() => {
120
+ if (popup?.closed) {
121
+ clearInterval(checkClosed);
122
+ setLoading(false);
123
+ window.removeEventListener("message", handleMessage);
124
+ }
125
+ }, 500);
126
+ }, [disabled, loading, paid, getCheckoutUrl, openMode, onSuccess]);
127
+ const successStyle = {
128
+ ...DEFAULT_STYLE,
129
+ background: "linear-gradient(to right, #10B981, #059669)",
130
+ boxShadow: "0 4px 14px rgba(16, 185, 129, 0.25)",
131
+ cursor: "default"
132
+ };
133
+ const buttonStyle = {
134
+ ...DEFAULT_STYLE,
135
+ ...effectiveButtonBgColor ? { background: effectiveButtonBgColor } : {},
136
+ ...isHovered && !disabled && !paid ? HOVER_STYLE : {},
137
+ ...disabled ? { opacity: 0.6, cursor: "not-allowed" } : {}
138
+ };
139
+ return /* @__PURE__ */ jsxRuntime.jsx(
140
+ "button",
141
+ {
142
+ type: "button",
143
+ onClick: handleClick,
144
+ disabled: disabled || loading || paid,
145
+ className,
146
+ style: className ? void 0 : paid ? successStyle : buttonStyle,
147
+ onMouseEnter: () => setIsHovered(true),
148
+ onMouseLeave: () => setIsHovered(false),
149
+ children: paid ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
150
+ /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}),
151
+ successText
152
+ ] }) : loading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
153
+ /* @__PURE__ */ jsxRuntime.jsx(LoaderIcon, {}),
154
+ "Processing..."
155
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
156
+ /* @__PURE__ */ jsxRuntime.jsx(PayIcon, {}),
157
+ buttonText
158
+ ] })
159
+ }
160
+ );
161
+ }
162
+ function generateEmbedCode(props) {
163
+ const { organizationId, amount, buttonBgColor, pageBgColor, callbackUrl, buttonText, checkoutBaseUrl } = props;
164
+ const propsStr = [
165
+ `organizationId="${organizationId || "your-org-id"}"`,
166
+ amount ? `amount={${amount}}` : null,
167
+ buttonBgColor ? `buttonBgColor="${buttonBgColor}"` : null,
168
+ pageBgColor ? `pageBgColor="${pageBgColor}"` : null,
169
+ callbackUrl ? `callbackUrl="${callbackUrl}"` : null,
170
+ buttonText ? `buttonText="${buttonText}"` : null,
171
+ checkoutBaseUrl ? `checkoutBaseUrl="${checkoutBaseUrl}"` : null
172
+ ].filter(Boolean).join("\n ");
173
+ return `import { PayButton } from '@loofta/pay-sdk';
174
+
175
+ <PayButton
176
+ ${propsStr}
177
+ onSuccess={(paymentId) => {
178
+ console.log('Payment completed:', paymentId);
179
+ }}
180
+ />`;
181
+ }
182
+ function generateScriptEmbed(props) {
183
+ const { organizationId, amount, buttonBgColor, pageBgColor, callbackUrl, buttonText, checkoutBaseUrl } = props;
184
+ const base = checkoutBaseUrl || "https://pay.loofta.com";
185
+ return `<!-- Loofta Pay Button -->
186
+ <script src="${base}/sdk/loofta-pay.js"></script>
187
+ <div
188
+ id="loofta-pay-button"
189
+ data-organization-id="${organizationId || "your-org-id"}"
190
+ data-checkout-base-url="${base}"
191
+ ${amount ? `data-amount="${amount}"` : ""}
192
+ ${buttonBgColor ? `data-button-bg-color="${buttonBgColor}"` : ""}
193
+ ${pageBgColor ? `data-page-bg-color="${pageBgColor}"` : ""}
194
+ ${buttonText ? `data-button-text="${buttonText}"` : ""}
195
+ ${callbackUrl ? `data-callback="${callbackUrl}"` : ""}
196
+ ></div>
197
+ <script>
198
+ LooftaPay.mount('#loofta-pay-button');
199
+ </script>`;
200
+ }
201
+
202
+ exports.PayButton = PayButton;
203
+ exports.generateEmbedCode = generateEmbedCode;
204
+ exports.generateScriptEmbed = generateScriptEmbed;
205
+ //# sourceMappingURL=index.js.map
206
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/PayButton.tsx"],"names":["jsxs","jsx","useState","useCallback","Fragment"],"mappings":"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","file":"index.js","sourcesContent":["import { useState, useCallback } from 'react';\n\nexport interface PayButtonProps {\n /**\n * Organization ID (required). Must be set up with Loofta — your destination wallet, network, and token are configured in your organization.\n * If you don't have an organization ID, contact Loofta to get one.\n */\n organizationId: string;\n /** Payment amount in USD (optional) */\n amount?: number | string;\n /** Button background color (optional) */\n buttonBgColor?: string;\n /** Checkout page background color (optional - falls back to org settings) */\n pageBgColor?: string;\n /** @deprecated Use buttonBgColor instead */\n bgColor?: string;\n /** Callback URL after payment (optional) */\n callbackUrl?: string;\n /** Callback function after payment (optional) */\n onSuccess?: (paymentId: string) => void;\n /** Button text (optional) */\n buttonText?: string;\n /** Success text shown after payment (optional) */\n successText?: string;\n /** Custom className for button styling */\n className?: string;\n /** Open checkout in new tab vs popup (default: popup) */\n openMode?: 'popup' | 'redirect' | 'tab';\n /** Disable button */\n disabled?: boolean;\n /**\n * Base URL of the Loofta Pay hosted checkout page. Checkout is always Loofta's — this is only needed if the button is embedded on a different domain than the default Loofta Pay app. Usually omit; default is used.\n */\n checkoutBaseUrl?: string;\n}\n\nconst DEFAULT_STYLE: React.CSSProperties = {\n background: 'linear-gradient(to right, #FF0F00, #EAB308)',\n color: '#ffffff',\n border: 'none',\n borderRadius: '12px',\n padding: '12px 24px',\n fontSize: '16px',\n fontWeight: 600,\n cursor: 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '8px',\n minWidth: '160px',\n transition: 'transform 0.2s, box-shadow 0.2s',\n boxShadow: '0 4px 14px rgba(255, 15, 0, 0.25)',\n};\n\nconst HOVER_STYLE: React.CSSProperties = {\n transform: 'scale(1.02)',\n boxShadow: '0 6px 20px rgba(255, 15, 0, 0.35)',\n};\n\nfunction LoaderIcon() {\n return (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeOpacity={0.25} />\n <path\n d=\"M12 2a10 10 0 0 1 10 10\"\n style={{ transformOrigin: '12px 12px' }}\n >\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from=\"0 12 12\"\n to=\"360 12 12\"\n dur=\"0.8s\"\n repeatCount=\"indefinite\"\n />\n </path>\n </svg>\n );\n}\n\nfunction CheckIcon() {\n return (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n );\n}\n\nfunction PayIcon() {\n return (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\" />\n </svg>\n );\n}\n\n/**\n * Loofta Pay Button – opens your Loofta Pay checkout (same API as your app).\n */\nexport function PayButton({\n organizationId,\n amount,\n buttonBgColor,\n pageBgColor,\n bgColor,\n callbackUrl,\n onSuccess,\n buttonText = 'Pay with Loofta',\n successText = 'Paid Successfully',\n className,\n openMode = 'popup',\n disabled = false,\n checkoutBaseUrl,\n}: PayButtonProps) {\n const [loading, setLoading] = useState(false);\n const [paid, setPaid] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n\n const effectiveButtonBgColor = buttonBgColor || bgColor;\n\n const getCheckoutUrl = useCallback(() => {\n const defaultBase = 'https://pay.loofta.com';\n const base =\n checkoutBaseUrl != null && checkoutBaseUrl !== ''\n ? checkoutBaseUrl.replace(/\\/$/, '')\n : defaultBase;\n\n const path = base.endsWith('/checkout') ? base : `${base}/checkout`;\n const params = new URLSearchParams();\n params.set('organizationId', organizationId);\n if (amount) params.set('amount', String(amount));\n if (pageBgColor) params.set('bgColor', encodeURIComponent(pageBgColor));\n if (callbackUrl) params.set('callback', encodeURIComponent(callbackUrl));\n\n return `${path}?${params.toString()}`;\n }, [checkoutBaseUrl, organizationId, amount, pageBgColor, callbackUrl]);\n\n const handleClick = useCallback(() => {\n if (disabled || loading || paid) return;\n\n setLoading(true);\n const url = getCheckoutUrl();\n\n if (openMode === 'redirect') {\n window.location.href = url;\n return;\n }\n\n if (openMode === 'tab') {\n window.open(url, '_blank', 'noopener,noreferrer');\n setLoading(false);\n return;\n }\n\n const width = 500;\n const height = 700;\n const left = window.screenX + (window.outerWidth - width) / 2;\n const top = window.screenY + (window.outerHeight - height) / 2;\n\n const popup = window.open(\n url,\n 'loofta-pay-checkout',\n `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`\n );\n\n const handleMessage = (event: MessageEvent) => {\n if (event.data?.type === 'loofta-payment-success') {\n setLoading(false);\n setPaid(true);\n onSuccess?.(event.data.paymentId);\n popup?.close();\n window.removeEventListener('message', handleMessage);\n }\n };\n window.addEventListener('message', handleMessage);\n\n const checkClosed = setInterval(() => {\n if (popup?.closed) {\n clearInterval(checkClosed);\n setLoading(false);\n window.removeEventListener('message', handleMessage);\n }\n }, 500);\n }, [disabled, loading, paid, getCheckoutUrl, openMode, onSuccess]);\n\n const successStyle: React.CSSProperties = {\n ...DEFAULT_STYLE,\n background: 'linear-gradient(to right, #10B981, #059669)',\n boxShadow: '0 4px 14px rgba(16, 185, 129, 0.25)',\n cursor: 'default',\n };\n\n const buttonStyle: React.CSSProperties = {\n ...DEFAULT_STYLE,\n ...(effectiveButtonBgColor ? { background: effectiveButtonBgColor } : {}),\n ...(isHovered && !disabled && !paid ? HOVER_STYLE : {}),\n ...(disabled ? { opacity: 0.6, cursor: 'not-allowed' as const } : {}),\n };\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={disabled || loading || paid}\n className={className}\n style={className ? undefined : paid ? successStyle : buttonStyle}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {paid ? (\n <>\n <CheckIcon />\n {successText}\n </>\n ) : loading ? (\n <>\n <LoaderIcon />\n Processing...\n </>\n ) : (\n <>\n <PayIcon />\n {buttonText}\n </>\n )}\n </button>\n );\n}\n\nexport function generateEmbedCode(props: Partial<PayButtonProps>): string {\n const { organizationId, amount, buttonBgColor, pageBgColor, callbackUrl, buttonText, checkoutBaseUrl } = props;\n\n const propsStr = [\n `organizationId=\"${organizationId || 'your-org-id'}\"`,\n amount ? `amount={${amount}}` : null,\n buttonBgColor ? `buttonBgColor=\"${buttonBgColor}\"` : null,\n pageBgColor ? `pageBgColor=\"${pageBgColor}\"` : null,\n callbackUrl ? `callbackUrl=\"${callbackUrl}\"` : null,\n buttonText ? `buttonText=\"${buttonText}\"` : null,\n checkoutBaseUrl ? `checkoutBaseUrl=\"${checkoutBaseUrl}\"` : null,\n ]\n .filter(Boolean)\n .join('\\n ');\n\n return `import { PayButton } from '@loofta/pay-sdk';\n\n<PayButton\n ${propsStr}\n onSuccess={(paymentId) => {\n console.log('Payment completed:', paymentId);\n }}\n/>`;\n}\n\nexport function generateScriptEmbed(props: Partial<PayButtonProps>): string {\n const { organizationId, amount, buttonBgColor, pageBgColor, callbackUrl, buttonText, checkoutBaseUrl } = props;\n const base = checkoutBaseUrl || 'https://pay.loofta.com';\n\n return `<!-- Loofta Pay Button -->\n<script src=\"${base}/sdk/loofta-pay.js\"></script>\n<div \n id=\"loofta-pay-button\"\n data-organization-id=\"${organizationId || 'your-org-id'}\"\n data-checkout-base-url=\"${base}\"\n ${amount ? `data-amount=\"${amount}\"` : ''}\n ${buttonBgColor ? `data-button-bg-color=\"${buttonBgColor}\"` : ''}\n ${pageBgColor ? `data-page-bg-color=\"${pageBgColor}\"` : ''}\n ${buttonText ? `data-button-text=\"${buttonText}\"` : ''}\n ${callbackUrl ? `data-callback=\"${callbackUrl}\"` : ''}\n></div>\n<script>\n LooftaPay.mount('#loofta-pay-button');\n</script>`;\n}\n\nexport default PayButton;\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,202 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+
4
+ // src/PayButton.tsx
5
+ var DEFAULT_STYLE = {
6
+ background: "linear-gradient(to right, #FF0F00, #EAB308)",
7
+ color: "#ffffff",
8
+ border: "none",
9
+ borderRadius: "12px",
10
+ padding: "12px 24px",
11
+ fontSize: "16px",
12
+ fontWeight: 600,
13
+ cursor: "pointer",
14
+ display: "inline-flex",
15
+ alignItems: "center",
16
+ justifyContent: "center",
17
+ gap: "8px",
18
+ minWidth: "160px",
19
+ transition: "transform 0.2s, box-shadow 0.2s",
20
+ boxShadow: "0 4px 14px rgba(255, 15, 0, 0.25)"
21
+ };
22
+ var HOVER_STYLE = {
23
+ transform: "scale(1.02)",
24
+ boxShadow: "0 6px 20px rgba(255, 15, 0, 0.35)"
25
+ };
26
+ function LoaderIcon() {
27
+ return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
28
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", strokeOpacity: 0.25 }),
29
+ /* @__PURE__ */ jsx(
30
+ "path",
31
+ {
32
+ d: "M12 2a10 10 0 0 1 10 10",
33
+ style: { transformOrigin: "12px 12px" },
34
+ children: /* @__PURE__ */ jsx(
35
+ "animateTransform",
36
+ {
37
+ attributeName: "transform",
38
+ type: "rotate",
39
+ from: "0 12 12",
40
+ to: "360 12 12",
41
+ dur: "0.8s",
42
+ repeatCount: "indefinite"
43
+ }
44
+ )
45
+ }
46
+ )
47
+ ] });
48
+ }
49
+ function CheckIcon() {
50
+ return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
51
+ }
52
+ function PayIcon() {
53
+ return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) });
54
+ }
55
+ function PayButton({
56
+ organizationId,
57
+ amount,
58
+ buttonBgColor,
59
+ pageBgColor,
60
+ bgColor,
61
+ callbackUrl,
62
+ onSuccess,
63
+ buttonText = "Pay with Loofta",
64
+ successText = "Paid Successfully",
65
+ className,
66
+ openMode = "popup",
67
+ disabled = false,
68
+ checkoutBaseUrl
69
+ }) {
70
+ const [loading, setLoading] = useState(false);
71
+ const [paid, setPaid] = useState(false);
72
+ const [isHovered, setIsHovered] = useState(false);
73
+ const effectiveButtonBgColor = buttonBgColor || bgColor;
74
+ const getCheckoutUrl = useCallback(() => {
75
+ const defaultBase = "https://pay.loofta.com";
76
+ const base = checkoutBaseUrl != null && checkoutBaseUrl !== "" ? checkoutBaseUrl.replace(/\/$/, "") : defaultBase;
77
+ const path = base.endsWith("/checkout") ? base : `${base}/checkout`;
78
+ const params = new URLSearchParams();
79
+ params.set("organizationId", organizationId);
80
+ if (amount) params.set("amount", String(amount));
81
+ if (pageBgColor) params.set("bgColor", encodeURIComponent(pageBgColor));
82
+ if (callbackUrl) params.set("callback", encodeURIComponent(callbackUrl));
83
+ return `${path}?${params.toString()}`;
84
+ }, [checkoutBaseUrl, organizationId, amount, pageBgColor, callbackUrl]);
85
+ const handleClick = useCallback(() => {
86
+ if (disabled || loading || paid) return;
87
+ setLoading(true);
88
+ const url = getCheckoutUrl();
89
+ if (openMode === "redirect") {
90
+ window.location.href = url;
91
+ return;
92
+ }
93
+ if (openMode === "tab") {
94
+ window.open(url, "_blank", "noopener,noreferrer");
95
+ setLoading(false);
96
+ return;
97
+ }
98
+ const width = 500;
99
+ const height = 700;
100
+ const left = window.screenX + (window.outerWidth - width) / 2;
101
+ const top = window.screenY + (window.outerHeight - height) / 2;
102
+ const popup = window.open(
103
+ url,
104
+ "loofta-pay-checkout",
105
+ `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`
106
+ );
107
+ const handleMessage = (event) => {
108
+ if (event.data?.type === "loofta-payment-success") {
109
+ setLoading(false);
110
+ setPaid(true);
111
+ onSuccess?.(event.data.paymentId);
112
+ popup?.close();
113
+ window.removeEventListener("message", handleMessage);
114
+ }
115
+ };
116
+ window.addEventListener("message", handleMessage);
117
+ const checkClosed = setInterval(() => {
118
+ if (popup?.closed) {
119
+ clearInterval(checkClosed);
120
+ setLoading(false);
121
+ window.removeEventListener("message", handleMessage);
122
+ }
123
+ }, 500);
124
+ }, [disabled, loading, paid, getCheckoutUrl, openMode, onSuccess]);
125
+ const successStyle = {
126
+ ...DEFAULT_STYLE,
127
+ background: "linear-gradient(to right, #10B981, #059669)",
128
+ boxShadow: "0 4px 14px rgba(16, 185, 129, 0.25)",
129
+ cursor: "default"
130
+ };
131
+ const buttonStyle = {
132
+ ...DEFAULT_STYLE,
133
+ ...effectiveButtonBgColor ? { background: effectiveButtonBgColor } : {},
134
+ ...isHovered && !disabled && !paid ? HOVER_STYLE : {},
135
+ ...disabled ? { opacity: 0.6, cursor: "not-allowed" } : {}
136
+ };
137
+ return /* @__PURE__ */ jsx(
138
+ "button",
139
+ {
140
+ type: "button",
141
+ onClick: handleClick,
142
+ disabled: disabled || loading || paid,
143
+ className,
144
+ style: className ? void 0 : paid ? successStyle : buttonStyle,
145
+ onMouseEnter: () => setIsHovered(true),
146
+ onMouseLeave: () => setIsHovered(false),
147
+ children: paid ? /* @__PURE__ */ jsxs(Fragment, { children: [
148
+ /* @__PURE__ */ jsx(CheckIcon, {}),
149
+ successText
150
+ ] }) : loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
151
+ /* @__PURE__ */ jsx(LoaderIcon, {}),
152
+ "Processing..."
153
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
154
+ /* @__PURE__ */ jsx(PayIcon, {}),
155
+ buttonText
156
+ ] })
157
+ }
158
+ );
159
+ }
160
+ function generateEmbedCode(props) {
161
+ const { organizationId, amount, buttonBgColor, pageBgColor, callbackUrl, buttonText, checkoutBaseUrl } = props;
162
+ const propsStr = [
163
+ `organizationId="${organizationId || "your-org-id"}"`,
164
+ amount ? `amount={${amount}}` : null,
165
+ buttonBgColor ? `buttonBgColor="${buttonBgColor}"` : null,
166
+ pageBgColor ? `pageBgColor="${pageBgColor}"` : null,
167
+ callbackUrl ? `callbackUrl="${callbackUrl}"` : null,
168
+ buttonText ? `buttonText="${buttonText}"` : null,
169
+ checkoutBaseUrl ? `checkoutBaseUrl="${checkoutBaseUrl}"` : null
170
+ ].filter(Boolean).join("\n ");
171
+ return `import { PayButton } from '@loofta/pay-sdk';
172
+
173
+ <PayButton
174
+ ${propsStr}
175
+ onSuccess={(paymentId) => {
176
+ console.log('Payment completed:', paymentId);
177
+ }}
178
+ />`;
179
+ }
180
+ function generateScriptEmbed(props) {
181
+ const { organizationId, amount, buttonBgColor, pageBgColor, callbackUrl, buttonText, checkoutBaseUrl } = props;
182
+ const base = checkoutBaseUrl || "https://pay.loofta.com";
183
+ return `<!-- Loofta Pay Button -->
184
+ <script src="${base}/sdk/loofta-pay.js"></script>
185
+ <div
186
+ id="loofta-pay-button"
187
+ data-organization-id="${organizationId || "your-org-id"}"
188
+ data-checkout-base-url="${base}"
189
+ ${amount ? `data-amount="${amount}"` : ""}
190
+ ${buttonBgColor ? `data-button-bg-color="${buttonBgColor}"` : ""}
191
+ ${pageBgColor ? `data-page-bg-color="${pageBgColor}"` : ""}
192
+ ${buttonText ? `data-button-text="${buttonText}"` : ""}
193
+ ${callbackUrl ? `data-callback="${callbackUrl}"` : ""}
194
+ ></div>
195
+ <script>
196
+ LooftaPay.mount('#loofta-pay-button');
197
+ </script>`;
198
+ }
199
+
200
+ export { PayButton, generateEmbedCode, generateScriptEmbed };
201
+ //# sourceMappingURL=index.mjs.map
202
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/PayButton.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { useState, useCallback } from 'react';\n\nexport interface PayButtonProps {\n /**\n * Organization ID (required). Must be set up with Loofta — your destination wallet, network, and token are configured in your organization.\n * If you don't have an organization ID, contact Loofta to get one.\n */\n organizationId: string;\n /** Payment amount in USD (optional) */\n amount?: number | string;\n /** Button background color (optional) */\n buttonBgColor?: string;\n /** Checkout page background color (optional - falls back to org settings) */\n pageBgColor?: string;\n /** @deprecated Use buttonBgColor instead */\n bgColor?: string;\n /** Callback URL after payment (optional) */\n callbackUrl?: string;\n /** Callback function after payment (optional) */\n onSuccess?: (paymentId: string) => void;\n /** Button text (optional) */\n buttonText?: string;\n /** Success text shown after payment (optional) */\n successText?: string;\n /** Custom className for button styling */\n className?: string;\n /** Open checkout in new tab vs popup (default: popup) */\n openMode?: 'popup' | 'redirect' | 'tab';\n /** Disable button */\n disabled?: boolean;\n /**\n * Base URL of the Loofta Pay hosted checkout page. Checkout is always Loofta's — this is only needed if the button is embedded on a different domain than the default Loofta Pay app. Usually omit; default is used.\n */\n checkoutBaseUrl?: string;\n}\n\nconst DEFAULT_STYLE: React.CSSProperties = {\n background: 'linear-gradient(to right, #FF0F00, #EAB308)',\n color: '#ffffff',\n border: 'none',\n borderRadius: '12px',\n padding: '12px 24px',\n fontSize: '16px',\n fontWeight: 600,\n cursor: 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: '8px',\n minWidth: '160px',\n transition: 'transform 0.2s, box-shadow 0.2s',\n boxShadow: '0 4px 14px rgba(255, 15, 0, 0.25)',\n};\n\nconst HOVER_STYLE: React.CSSProperties = {\n transform: 'scale(1.02)',\n boxShadow: '0 6px 20px rgba(255, 15, 0, 0.35)',\n};\n\nfunction LoaderIcon() {\n return (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeOpacity={0.25} />\n <path\n d=\"M12 2a10 10 0 0 1 10 10\"\n style={{ transformOrigin: '12px 12px' }}\n >\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from=\"0 12 12\"\n to=\"360 12 12\"\n dur=\"0.8s\"\n repeatCount=\"indefinite\"\n />\n </path>\n </svg>\n );\n}\n\nfunction CheckIcon() {\n return (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n );\n}\n\nfunction PayIcon() {\n return (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\" />\n </svg>\n );\n}\n\n/**\n * Loofta Pay Button – opens your Loofta Pay checkout (same API as your app).\n */\nexport function PayButton({\n organizationId,\n amount,\n buttonBgColor,\n pageBgColor,\n bgColor,\n callbackUrl,\n onSuccess,\n buttonText = 'Pay with Loofta',\n successText = 'Paid Successfully',\n className,\n openMode = 'popup',\n disabled = false,\n checkoutBaseUrl,\n}: PayButtonProps) {\n const [loading, setLoading] = useState(false);\n const [paid, setPaid] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n\n const effectiveButtonBgColor = buttonBgColor || bgColor;\n\n const getCheckoutUrl = useCallback(() => {\n const defaultBase = 'https://pay.loofta.com';\n const base =\n checkoutBaseUrl != null && checkoutBaseUrl !== ''\n ? checkoutBaseUrl.replace(/\\/$/, '')\n : defaultBase;\n\n const path = base.endsWith('/checkout') ? base : `${base}/checkout`;\n const params = new URLSearchParams();\n params.set('organizationId', organizationId);\n if (amount) params.set('amount', String(amount));\n if (pageBgColor) params.set('bgColor', encodeURIComponent(pageBgColor));\n if (callbackUrl) params.set('callback', encodeURIComponent(callbackUrl));\n\n return `${path}?${params.toString()}`;\n }, [checkoutBaseUrl, organizationId, amount, pageBgColor, callbackUrl]);\n\n const handleClick = useCallback(() => {\n if (disabled || loading || paid) return;\n\n setLoading(true);\n const url = getCheckoutUrl();\n\n if (openMode === 'redirect') {\n window.location.href = url;\n return;\n }\n\n if (openMode === 'tab') {\n window.open(url, '_blank', 'noopener,noreferrer');\n setLoading(false);\n return;\n }\n\n const width = 500;\n const height = 700;\n const left = window.screenX + (window.outerWidth - width) / 2;\n const top = window.screenY + (window.outerHeight - height) / 2;\n\n const popup = window.open(\n url,\n 'loofta-pay-checkout',\n `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`\n );\n\n const handleMessage = (event: MessageEvent) => {\n if (event.data?.type === 'loofta-payment-success') {\n setLoading(false);\n setPaid(true);\n onSuccess?.(event.data.paymentId);\n popup?.close();\n window.removeEventListener('message', handleMessage);\n }\n };\n window.addEventListener('message', handleMessage);\n\n const checkClosed = setInterval(() => {\n if (popup?.closed) {\n clearInterval(checkClosed);\n setLoading(false);\n window.removeEventListener('message', handleMessage);\n }\n }, 500);\n }, [disabled, loading, paid, getCheckoutUrl, openMode, onSuccess]);\n\n const successStyle: React.CSSProperties = {\n ...DEFAULT_STYLE,\n background: 'linear-gradient(to right, #10B981, #059669)',\n boxShadow: '0 4px 14px rgba(16, 185, 129, 0.25)',\n cursor: 'default',\n };\n\n const buttonStyle: React.CSSProperties = {\n ...DEFAULT_STYLE,\n ...(effectiveButtonBgColor ? { background: effectiveButtonBgColor } : {}),\n ...(isHovered && !disabled && !paid ? HOVER_STYLE : {}),\n ...(disabled ? { opacity: 0.6, cursor: 'not-allowed' as const } : {}),\n };\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={disabled || loading || paid}\n className={className}\n style={className ? undefined : paid ? successStyle : buttonStyle}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {paid ? (\n <>\n <CheckIcon />\n {successText}\n </>\n ) : loading ? (\n <>\n <LoaderIcon />\n Processing...\n </>\n ) : (\n <>\n <PayIcon />\n {buttonText}\n </>\n )}\n </button>\n );\n}\n\nexport function generateEmbedCode(props: Partial<PayButtonProps>): string {\n const { organizationId, amount, buttonBgColor, pageBgColor, callbackUrl, buttonText, checkoutBaseUrl } = props;\n\n const propsStr = [\n `organizationId=\"${organizationId || 'your-org-id'}\"`,\n amount ? `amount={${amount}}` : null,\n buttonBgColor ? `buttonBgColor=\"${buttonBgColor}\"` : null,\n pageBgColor ? `pageBgColor=\"${pageBgColor}\"` : null,\n callbackUrl ? `callbackUrl=\"${callbackUrl}\"` : null,\n buttonText ? `buttonText=\"${buttonText}\"` : null,\n checkoutBaseUrl ? `checkoutBaseUrl=\"${checkoutBaseUrl}\"` : null,\n ]\n .filter(Boolean)\n .join('\\n ');\n\n return `import { PayButton } from '@loofta/pay-sdk';\n\n<PayButton\n ${propsStr}\n onSuccess={(paymentId) => {\n console.log('Payment completed:', paymentId);\n }}\n/>`;\n}\n\nexport function generateScriptEmbed(props: Partial<PayButtonProps>): string {\n const { organizationId, amount, buttonBgColor, pageBgColor, callbackUrl, buttonText, checkoutBaseUrl } = props;\n const base = checkoutBaseUrl || 'https://pay.loofta.com';\n\n return `<!-- Loofta Pay Button -->\n<script src=\"${base}/sdk/loofta-pay.js\"></script>\n<div \n id=\"loofta-pay-button\"\n data-organization-id=\"${organizationId || 'your-org-id'}\"\n data-checkout-base-url=\"${base}\"\n ${amount ? `data-amount=\"${amount}\"` : ''}\n ${buttonBgColor ? `data-button-bg-color=\"${buttonBgColor}\"` : ''}\n ${pageBgColor ? `data-page-bg-color=\"${pageBgColor}\"` : ''}\n ${buttonText ? `data-button-text=\"${buttonText}\"` : ''}\n ${callbackUrl ? `data-callback=\"${callbackUrl}\"` : ''}\n></div>\n<script>\n LooftaPay.mount('#loofta-pay-button');\n</script>`;\n}\n\nexport default PayButton;\n"]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@loofta/pay-sdk",
3
+ "version": "1.0.1",
4
+ "description": "Loofta Pay Button SDK - embeddable payment button that opens Loofta Pay checkout",
5
+ "author": "Loofta",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch"
24
+ },
25
+ "peerDependencies": {
26
+ "react": ">=18.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/react": "^18.0.0",
30
+ "react": "^18.0.0",
31
+ "tsup": "^8.0.0",
32
+ "typescript": "^5.0.0"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/loofta/loofta-swap.git"
37
+ },
38
+ "keywords": [
39
+ "loofta",
40
+ "pay",
41
+ "payment",
42
+ "checkout",
43
+ "crypto",
44
+ "multichain"
45
+ ],
46
+ "publishConfig": {
47
+ "access": "public"
48
+ }
49
+ }