recur-tw 0.6.1 → 0.7.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/dist/index.cjs +269 -2
- package/dist/index.d.cts +71 -3
- package/dist/index.d.ts +71 -3
- package/dist/index.js +269 -2
- package/dist/recur.umd.js +146 -19
- package/dist/server.cjs +117 -0
- package/dist/server.d.cts +157 -0
- package/dist/server.d.ts +157 -0
- package/dist/server.js +114 -0
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -2226,6 +2226,271 @@ var init_checkout_button = __esm({
|
|
|
2226
2226
|
}
|
|
2227
2227
|
});
|
|
2228
2228
|
|
|
2229
|
+
// src/components/portal-button.ts
|
|
2230
|
+
var portal_button_exports = {};
|
|
2231
|
+
__export(portal_button_exports, {
|
|
2232
|
+
RecurPortalButton: () => RecurPortalButton
|
|
2233
|
+
});
|
|
2234
|
+
var RecurPortalButton;
|
|
2235
|
+
var init_portal_button = __esm({
|
|
2236
|
+
"src/components/portal-button.ts"() {
|
|
2237
|
+
RecurPortalButton = class extends HTMLElement {
|
|
2238
|
+
constructor() {
|
|
2239
|
+
super();
|
|
2240
|
+
__publicField(this, "_isLoading", false);
|
|
2241
|
+
__publicField(this, "handleClick", async (e) => {
|
|
2242
|
+
e.preventDefault();
|
|
2243
|
+
if (this._isLoading || this.hasAttribute("disabled")) {
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
const portalUrl = this.getAttribute("portal-url");
|
|
2247
|
+
const apiEndpoint = this.getAttribute("api-endpoint");
|
|
2248
|
+
if (portalUrl) {
|
|
2249
|
+
this.redirectToPortal(portalUrl);
|
|
2250
|
+
return;
|
|
2251
|
+
}
|
|
2252
|
+
if (apiEndpoint) {
|
|
2253
|
+
await this.fetchAndRedirect(apiEndpoint);
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
this.dispatchError("Missing required attribute: either portal-url or api-endpoint must be provided");
|
|
2257
|
+
});
|
|
2258
|
+
this.attachShadow({ mode: "open" });
|
|
2259
|
+
}
|
|
2260
|
+
static get observedAttributes() {
|
|
2261
|
+
return [
|
|
2262
|
+
"portal-url",
|
|
2263
|
+
"api-endpoint",
|
|
2264
|
+
"customer-id",
|
|
2265
|
+
"return-url",
|
|
2266
|
+
"button-text",
|
|
2267
|
+
"button-style",
|
|
2268
|
+
"disabled",
|
|
2269
|
+
"target"
|
|
2270
|
+
];
|
|
2271
|
+
}
|
|
2272
|
+
connectedCallback() {
|
|
2273
|
+
this.render();
|
|
2274
|
+
this.setupEventListeners();
|
|
2275
|
+
}
|
|
2276
|
+
disconnectedCallback() {
|
|
2277
|
+
const button = this.shadowRoot?.querySelector("button");
|
|
2278
|
+
if (button) {
|
|
2279
|
+
button.removeEventListener("click", this.handleClick);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
attributeChangedCallback(_name, oldValue, newValue) {
|
|
2283
|
+
if (oldValue !== newValue) {
|
|
2284
|
+
this.render();
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
render() {
|
|
2288
|
+
const buttonText = this.getAttribute("button-text") || this.textContent?.trim() || "\u7BA1\u7406\u8A02\u95B1";
|
|
2289
|
+
const buttonStyle = this.getAttribute("button-style") || "primary";
|
|
2290
|
+
const isDisabled = this.hasAttribute("disabled") || this._isLoading;
|
|
2291
|
+
this.shadowRoot.innerHTML = `
|
|
2292
|
+
<style>
|
|
2293
|
+
:host {
|
|
2294
|
+
display: inline-block;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
button {
|
|
2298
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2299
|
+
font-size: 16px;
|
|
2300
|
+
font-weight: 500;
|
|
2301
|
+
padding: 12px 24px;
|
|
2302
|
+
border-radius: 8px;
|
|
2303
|
+
cursor: pointer;
|
|
2304
|
+
transition: all 0.2s ease;
|
|
2305
|
+
display: inline-flex;
|
|
2306
|
+
align-items: center;
|
|
2307
|
+
justify-content: center;
|
|
2308
|
+
gap: 8px;
|
|
2309
|
+
min-width: 120px;
|
|
2310
|
+
border: none;
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
button:disabled {
|
|
2314
|
+
opacity: 0.6;
|
|
2315
|
+
cursor: not-allowed;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
/* Primary style (default) */
|
|
2319
|
+
button.primary {
|
|
2320
|
+
background: #18181b;
|
|
2321
|
+
color: #ffffff;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
button.primary:hover:not(:disabled) {
|
|
2325
|
+
background: #27272a;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
button.primary:active:not(:disabled) {
|
|
2329
|
+
background: #3f3f46;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
/* Outline style */
|
|
2333
|
+
button.outline {
|
|
2334
|
+
background: transparent;
|
|
2335
|
+
color: #18181b;
|
|
2336
|
+
border: 2px solid #18181b;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
button.outline:hover:not(:disabled) {
|
|
2340
|
+
background: #f4f4f5;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
button.outline:active:not(:disabled) {
|
|
2344
|
+
background: #e4e4e7;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
/* Gradient style */
|
|
2348
|
+
button.gradient {
|
|
2349
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2350
|
+
color: #ffffff;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
button.gradient:hover:not(:disabled) {
|
|
2354
|
+
opacity: 0.9;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
button.gradient:active:not(:disabled) {
|
|
2358
|
+
opacity: 0.8;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
/* Link style */
|
|
2362
|
+
button.link {
|
|
2363
|
+
background: transparent;
|
|
2364
|
+
color: #3b82f6;
|
|
2365
|
+
padding: 4px 8px;
|
|
2366
|
+
min-width: auto;
|
|
2367
|
+
text-decoration: underline;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
button.link:hover:not(:disabled) {
|
|
2371
|
+
color: #2563eb;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
button.link:active:not(:disabled) {
|
|
2375
|
+
color: #1d4ed8;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
/* Loading spinner */
|
|
2379
|
+
.spinner {
|
|
2380
|
+
width: 16px;
|
|
2381
|
+
height: 16px;
|
|
2382
|
+
border: 2px solid currentColor;
|
|
2383
|
+
border-top-color: transparent;
|
|
2384
|
+
border-radius: 50%;
|
|
2385
|
+
animation: spin 0.6s linear infinite;
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
@keyframes spin {
|
|
2389
|
+
to { transform: rotate(360deg); }
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
/* Focus state */
|
|
2393
|
+
button:focus-visible {
|
|
2394
|
+
outline: 2px solid #3b82f6;
|
|
2395
|
+
outline-offset: 2px;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
/* Portal icon */
|
|
2399
|
+
.portal-icon {
|
|
2400
|
+
width: 18px;
|
|
2401
|
+
height: 18px;
|
|
2402
|
+
}
|
|
2403
|
+
</style>
|
|
2404
|
+
|
|
2405
|
+
<button
|
|
2406
|
+
class="${buttonStyle}"
|
|
2407
|
+
${isDisabled ? "disabled" : ""}
|
|
2408
|
+
aria-busy="${this._isLoading}"
|
|
2409
|
+
>
|
|
2410
|
+
${this._isLoading ? '<span class="spinner"></span>' : this.getPortalIcon()}
|
|
2411
|
+
<span>${this._isLoading ? "\u8655\u7406\u4E2D..." : buttonText}</span>
|
|
2412
|
+
</button>
|
|
2413
|
+
`;
|
|
2414
|
+
}
|
|
2415
|
+
getPortalIcon() {
|
|
2416
|
+
return `
|
|
2417
|
+
<svg class="portal-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2418
|
+
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
|
|
2419
|
+
<circle cx="12" cy="7" r="4"/>
|
|
2420
|
+
</svg>
|
|
2421
|
+
`;
|
|
2422
|
+
}
|
|
2423
|
+
setupEventListeners() {
|
|
2424
|
+
const button = this.shadowRoot?.querySelector("button");
|
|
2425
|
+
if (button) {
|
|
2426
|
+
button.addEventListener("click", this.handleClick);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
redirectToPortal(url) {
|
|
2430
|
+
const target = this.getAttribute("target");
|
|
2431
|
+
this.dispatchEvent(new CustomEvent("portal-redirect", {
|
|
2432
|
+
detail: { url },
|
|
2433
|
+
bubbles: true,
|
|
2434
|
+
composed: true
|
|
2435
|
+
}));
|
|
2436
|
+
if (target === "_blank") {
|
|
2437
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
2438
|
+
} else {
|
|
2439
|
+
window.location.href = url;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
async fetchAndRedirect(apiEndpoint) {
|
|
2443
|
+
const customerId = this.getAttribute("customer-id");
|
|
2444
|
+
const returnUrl = this.getAttribute("return-url");
|
|
2445
|
+
this._isLoading = true;
|
|
2446
|
+
this.render();
|
|
2447
|
+
this.setupEventListeners();
|
|
2448
|
+
try {
|
|
2449
|
+
const requestBody = {};
|
|
2450
|
+
if (customerId) requestBody.customerId = customerId;
|
|
2451
|
+
if (returnUrl) requestBody.returnUrl = returnUrl;
|
|
2452
|
+
const response = await fetch(apiEndpoint, {
|
|
2453
|
+
method: "POST",
|
|
2454
|
+
headers: {
|
|
2455
|
+
"Content-Type": "application/json"
|
|
2456
|
+
},
|
|
2457
|
+
body: JSON.stringify(requestBody)
|
|
2458
|
+
});
|
|
2459
|
+
if (!response.ok) {
|
|
2460
|
+
const error = await response.json().catch(() => ({}));
|
|
2461
|
+
throw new Error(error.error?.message || error.message || `HTTP ${response.status}: Failed to create portal session`);
|
|
2462
|
+
}
|
|
2463
|
+
const data = await response.json();
|
|
2464
|
+
const portalUrl = data.url || data.portalUrl;
|
|
2465
|
+
if (!portalUrl) {
|
|
2466
|
+
throw new Error("Portal URL not found in response");
|
|
2467
|
+
}
|
|
2468
|
+
this._isLoading = false;
|
|
2469
|
+
this.render();
|
|
2470
|
+
this.setupEventListeners();
|
|
2471
|
+
this.redirectToPortal(portalUrl);
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
this._isLoading = false;
|
|
2474
|
+
this.render();
|
|
2475
|
+
this.setupEventListeners();
|
|
2476
|
+
this.dispatchError(error.message || "Failed to create portal session");
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
dispatchError(message) {
|
|
2480
|
+
console.error("[recur-portal]", message);
|
|
2481
|
+
this.dispatchEvent(new CustomEvent("portal-error", {
|
|
2482
|
+
detail: { message },
|
|
2483
|
+
bubbles: true,
|
|
2484
|
+
composed: true
|
|
2485
|
+
}));
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
if (typeof window !== "undefined" && !customElements.get("recur-portal")) {
|
|
2489
|
+
customElements.define("recur-portal", RecurPortalButton);
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
});
|
|
2493
|
+
|
|
2229
2494
|
// src/components/index.ts
|
|
2230
2495
|
async function registerComponents() {
|
|
2231
2496
|
if (typeof window === "undefined") {
|
|
@@ -2240,7 +2505,8 @@ async function registerComponents() {
|
|
|
2240
2505
|
Promise.resolve().then(() => (init_payment_form_skeleton(), payment_form_skeleton_exports)),
|
|
2241
2506
|
Promise.resolve().then(() => (init_toast(), toast_exports)),
|
|
2242
2507
|
Promise.resolve().then(() => (init_payment_form(), payment_form_exports)),
|
|
2243
|
-
Promise.resolve().then(() => (init_checkout_button(), checkout_button_exports))
|
|
2508
|
+
Promise.resolve().then(() => (init_checkout_button(), checkout_button_exports)),
|
|
2509
|
+
Promise.resolve().then(() => (init_portal_button(), portal_button_exports))
|
|
2244
2510
|
]);
|
|
2245
2511
|
const components = [
|
|
2246
2512
|
"recur-loading-spinner",
|
|
@@ -2251,7 +2517,8 @@ async function registerComponents() {
|
|
|
2251
2517
|
"recur-toast",
|
|
2252
2518
|
"recur-toast-container",
|
|
2253
2519
|
"recur-payment-form",
|
|
2254
|
-
"recur-checkout"
|
|
2520
|
+
"recur-checkout",
|
|
2521
|
+
"recur-portal"
|
|
2255
2522
|
];
|
|
2256
2523
|
const unregistered = components.filter((name) => !customElements.get(name));
|
|
2257
2524
|
if (unregistered.length > 0) {
|