recur-tw 0.20.0 → 0.21.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/checkout.d.ts +12 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/recur.umd.js +1 -1
- package/dist/server.cjs +197 -1
- package/dist/server.d.cts +134 -1
- package/dist/server.d.ts +134 -1
- package/dist/server.js +197 -2
- package/dist/vanilla.d.ts +1379 -0
- package/dist/vanilla.js +2388 -0
- package/dist/widget.d.ts +11 -0
- package/package.json +23 -7
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type companion for "recur-tw/checkout" (dist/checkout.js).
|
|
3
|
+
*
|
|
4
|
+
* checkout.js is a side-effect auto-init script (Gumroad-style): it scans the
|
|
5
|
+
* page for `.recur-button` / data attributes and wires up the checkout dialog.
|
|
6
|
+
* It exports nothing — import it for its side effects only:
|
|
7
|
+
*
|
|
8
|
+
* import 'recur-tw/checkout'
|
|
9
|
+
*
|
|
10
|
+
* (Copied into dist/ by the build script; see package.json "build".)
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/dist/recur.umd.js
CHANGED
|
@@ -2245,7 +2245,7 @@ function ee(e,t){if(!w(e)||!e.hasOwnProperty(`raw`))throw Error(`invalid templat
|
|
|
2245
2245
|
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
|
|
2246
2246
|
<circle cx="12" cy="7" r="4"/>
|
|
2247
2247
|
</svg>
|
|
2248
|
-
`}setupEventListeners(){let e=this.shadowRoot?.querySelector(`button`);e&&e.addEventListener(`click`,this.handleClick)}redirectToPortal(e){let t=this.getAttribute(`target`);this.dispatchEvent(new CustomEvent(`portal-redirect`,{detail:{url:e},bubbles:!0,composed:!0})),t===`_blank`?window.open(e,`_blank`,`noopener,noreferrer`):window.location.href=e}async fetchAndRedirect(e){let t=this.getAttribute(`customer-id`),n=this.getAttribute(`return-url`);this._isLoading=!0,this.render(),this.setupEventListeners();try{let r={};t&&(r.customerId=t),n&&(r.returnUrl=n);let i=await fetch(e,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(r)});if(!i.ok){let e=await i.json().catch(()=>({}));throw Error(e.error?.message||e.message||`HTTP ${i.status}: Failed to create portal session`)}let a=await i.json(),o=a.url||a.portalUrl;if(!o)throw Error(`Portal URL not found in response`);this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(o)}catch(e){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(e instanceof Error?e.message:`Failed to create portal session`)}}dispatchError(e){console.error(`[recur-portal]`,e),this.dispatchEvent(new CustomEvent(`portal-error`,{detail:{message:e},bubbles:!0,composed:!0}))}},typeof window<`u`&&!customElements.get(`recur-portal`)&&customElements.define(`recur-portal`,nt)}));async function it(){if(typeof window>`u`){console.warn(`[Recur SDK] Cannot register Web Components: window is not defined`);return}await Promise.all([Promise.resolve().then(()=>(o(),i)),Promise.resolve().then(()=>(l(),s)),Promise.resolve().then(()=>(f(),u)),Promise.resolve().then(()=>(h(),p)),Promise.resolve().then(()=>(Se(),ye)),Promise.resolve().then(()=>(Ae(),Oe)),Promise.resolve().then(()=>(Le(),Pe)),Promise.resolve().then(()=>(Ze(),Ye)),Promise.resolve().then(()=>(et(),Qe)),Promise.resolve().then(()=>(rt(),tt))]);let e=[`recur-loading-spinner`,`recur-success-message`,`recur-error-display`,`recur-skeleton-loader`,`recur-payment-form-skeleton`,`recur-toast`,`recur-toast-container`,`recur-einvoice-section`,`recur-payment-form`,`recur-checkout`,`recur-portal`].filter(e=>!customElements.get(e));e.length>0&&console.warn(`[Recur SDK] The following components are not registered:`,e)}typeof window<`u`&&it();function at(e){return e.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function X(e){if(e==null)return e;if(Array.isArray(e))return e.map(e=>X(e));if(e instanceof Date)return e;if(typeof e==`object`){let t={};for(let[n,r]of Object.entries(e)){let e=at(n);t[e]=X(r)}return t}return e}var ot=`0.
|
|
2248
|
+
`}setupEventListeners(){let e=this.shadowRoot?.querySelector(`button`);e&&e.addEventListener(`click`,this.handleClick)}redirectToPortal(e){let t=this.getAttribute(`target`);this.dispatchEvent(new CustomEvent(`portal-redirect`,{detail:{url:e},bubbles:!0,composed:!0})),t===`_blank`?window.open(e,`_blank`,`noopener,noreferrer`):window.location.href=e}async fetchAndRedirect(e){let t=this.getAttribute(`customer-id`),n=this.getAttribute(`return-url`);this._isLoading=!0,this.render(),this.setupEventListeners();try{let r={};t&&(r.customerId=t),n&&(r.returnUrl=n);let i=await fetch(e,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(r)});if(!i.ok){let e=await i.json().catch(()=>({}));throw Error(e.error?.message||e.message||`HTTP ${i.status}: Failed to create portal session`)}let a=await i.json(),o=a.url||a.portalUrl;if(!o)throw Error(`Portal URL not found in response`);this._isLoading=!1,this.render(),this.setupEventListeners(),this.redirectToPortal(o)}catch(e){this._isLoading=!1,this.render(),this.setupEventListeners(),this.dispatchError(e instanceof Error?e.message:`Failed to create portal session`)}}dispatchError(e){console.error(`[recur-portal]`,e),this.dispatchEvent(new CustomEvent(`portal-error`,{detail:{message:e},bubbles:!0,composed:!0}))}},typeof window<`u`&&!customElements.get(`recur-portal`)&&customElements.define(`recur-portal`,nt)}));async function it(){if(typeof window>`u`){console.warn(`[Recur SDK] Cannot register Web Components: window is not defined`);return}await Promise.all([Promise.resolve().then(()=>(o(),i)),Promise.resolve().then(()=>(l(),s)),Promise.resolve().then(()=>(f(),u)),Promise.resolve().then(()=>(h(),p)),Promise.resolve().then(()=>(Se(),ye)),Promise.resolve().then(()=>(Ae(),Oe)),Promise.resolve().then(()=>(Le(),Pe)),Promise.resolve().then(()=>(Ze(),Ye)),Promise.resolve().then(()=>(et(),Qe)),Promise.resolve().then(()=>(rt(),tt))]);let e=[`recur-loading-spinner`,`recur-success-message`,`recur-error-display`,`recur-skeleton-loader`,`recur-payment-form-skeleton`,`recur-toast`,`recur-toast-container`,`recur-einvoice-section`,`recur-payment-form`,`recur-checkout`,`recur-portal`].filter(e=>!customElements.get(e));e.length>0&&console.warn(`[Recur SDK] The following components are not registered:`,e)}typeof window<`u`&&it();function at(e){return e.replace(/_([a-z])/g,(e,t)=>t.toUpperCase())}function X(e){if(e==null)return e;if(Array.isArray(e))return e.map(e=>X(e));if(e instanceof Date)return e;if(typeof e==`object`){let t={};for(let[n,r]of Object.entries(e)){let e=at(n);t[e]=X(r)}return t}return e}var ot=`0.21.0`;V();let st=ot;var ct=class{constructor(e){if(B(this,`config`,void 0),!e.publishableKey)throw Error(`publishableKey is required`);this.config={baseUrl:`https://api.recur.tw`,...e}}getHeaders(){return{"Content-Type":`application/json`,"X-Recur-Publishable-Key":this.config.publishableKey,"X-Recur-SDK-Type":`vanilla`,"X-Recur-SDK-Version":st,"X-Recur-Source":typeof window<`u`?window.location.origin:`server`}}async createSubscription(e){let{customerName:t,customerEmail:n}=e,r=e.productId||e.planId,i=e.productSlug;if(!r&&!i)throw Error(`Either productId or productSlug is required`);let a={customerName:t,customerEmail:n};r&&(a.productId=r),i&&(a.productSlug=i);let o=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:`POST`,headers:this.getHeaders(),body:JSON.stringify(a)});if(!o.ok){let e=await o.json().catch(()=>({}));throw{code:e.error||`CHECKOUT_FAILED`,message:e.message||`Failed to initiate checkout`,details:e}}return X(await o.json())}async fetchProducts(e){let t=new URL(`${this.config.baseUrl}/v1/products`);e?.type&&t.searchParams.set(`type`,e.type);let n=await fetch(t.toString(),{method:`GET`,headers:this.getHeaders()});if(!n.ok){let e=await n.json().catch(()=>({}));throw{code:e.error||`FETCH_PRODUCTS_FAILED`,message:e.message||`Failed to fetch products`,details:e}}return X(await n.json())}async fetchPlans(){return{plans:(await this.fetchProducts({type:`SUBSCRIPTION`})).data}}getConfig(){return{...this.config}}};V();var lt=class{constructor(e,t){if(B(this,`config`,void 0),B(this,`options`,void 0),B(this,`container`,void 0),B(this,`checkoutId`,null),B(this,`sdkToken`,null),B(this,`sdkTimestamp`,null),B(this,`creditToken`,null),B(this,`sdkEnv`,`S`),B(this,`payuniSDK`,null),B(this,`isFormValid`,!1),this.config=e,this.options=t,typeof t.container==`string`){let e=document.querySelector(t.container);if(!e)throw Error(`Container not found: ${t.container}`);this.container=e}else this.container=t.container}async render(){try{await this.initCheckout(),this.renderHTML(),await this.loadPayUniSDK(),await this.initPayUniSDK(),this.setupFormSubmit()}catch(e){this.handleError(e)}}getBaseUrl(){if(this.config.baseUrl)return this.config.baseUrl;if(typeof window<`u`){let e=window.location.hostname;if(e===`localhost`||e.includes(`.test`)||e.includes(`.local`)||e===`127.0.0.1`)return`${window.location.protocol}//${window.location.host}/api`}return`https://api.recur.tw`}async initCheckout(){let e=this.getBaseUrl(),t=await fetch(`${e}/v1/checkouts`,{method:`POST`,headers:{"Content-Type":`application/json`,"X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({productId:this.options.productId||this.options.planId,customerEmail:this.options.customerEmail,customerName:this.options.customerName})});if(!t.ok){let e=await t.json().catch(()=>({}));throw Error(e.error||`Failed to initialize checkout`)}let n=X(await t.json());this.checkoutId=n.checkout.id,this.sdkToken=n.sdkToken,this.sdkTimestamp=n.sdkTimestamp||null,this.creditToken=n.creditToken||null,this.sdkEnv=n.livemode?`P`:`S`}renderHTML(){this.container.innerHTML=`
|
|
2249
2249
|
<div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
|
|
2250
2250
|
<div class="recur-checkout-header" style="margin-bottom: 24px;">
|
|
2251
2251
|
<h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>
|
package/dist/server.cjs
CHANGED
|
@@ -29,6 +29,94 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
let node_crypto = require("node:crypto");
|
|
30
30
|
node_crypto = __toESM(node_crypto, 1);
|
|
31
31
|
|
|
32
|
+
//#region src/case-utils.ts
|
|
33
|
+
/**
|
|
34
|
+
* Case Conversion Utilities for SDK
|
|
35
|
+
*
|
|
36
|
+
* Converts API responses from snake_case to camelCase.
|
|
37
|
+
* Our API uses snake_case for all external responses (REST convention),
|
|
38
|
+
* but the SDK uses camelCase internally (TypeScript convention).
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Convert a string from snake_case to camelCase
|
|
42
|
+
* @example snakeToCamel('sdk_token') => 'sdkToken'
|
|
43
|
+
* @example snakeToCamel('sdk_token_expires_at') => 'sdkTokenExpiresAt'
|
|
44
|
+
*/
|
|
45
|
+
function snakeToCamel(str) {
|
|
46
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Recursively convert all keys in an object from snake_case to camelCase
|
|
50
|
+
* Used for parsing API responses
|
|
51
|
+
*/
|
|
52
|
+
function toCamelCase(obj) {
|
|
53
|
+
if (obj === null || obj === void 0) return obj;
|
|
54
|
+
if (Array.isArray(obj)) return obj.map((item) => toCamelCase(item));
|
|
55
|
+
if (obj instanceof Date) return obj;
|
|
56
|
+
if (typeof obj === "object") {
|
|
57
|
+
const result = {};
|
|
58
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
59
|
+
const camelKey = snakeToCamel(key);
|
|
60
|
+
result[camelKey] = toCamelCase(value);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
return obj;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/einvoice.ts
|
|
69
|
+
/** 手機條碼: `/` + 7 chars from the MOF alphabet. */
|
|
70
|
+
const MOBILE_BARCODE_RE = /^\/[0-9A-Z.+-]{7}$/;
|
|
71
|
+
/** 統一編號: 8 digits. */
|
|
72
|
+
const UBN_RE = /^\d{8}$/;
|
|
73
|
+
/** 愛心碼: 3–7 digits. */
|
|
74
|
+
const DONATION_CODE_RE = /^\d{3,7}$/;
|
|
75
|
+
/**
|
|
76
|
+
* 統一編號 checksum (post-2023 rule: weighted digit-sum divisible by 5, with
|
|
77
|
+
* the 7-in-7th-digit alternate). Providers reject checksum-invalid UBNs at
|
|
78
|
+
* issue time as a non-retryable failure, so the SDK catches this at entry.
|
|
79
|
+
*/
|
|
80
|
+
function isValidTaiwanUbn(ubn) {
|
|
81
|
+
if (!UBN_RE.test(ubn)) return false;
|
|
82
|
+
const weights = [
|
|
83
|
+
1,
|
|
84
|
+
2,
|
|
85
|
+
1,
|
|
86
|
+
2,
|
|
87
|
+
1,
|
|
88
|
+
2,
|
|
89
|
+
4,
|
|
90
|
+
1
|
|
91
|
+
];
|
|
92
|
+
let sum = 0;
|
|
93
|
+
for (let i = 0; i < 8; i++) {
|
|
94
|
+
const product = Number(ubn.charAt(i)) * (weights[i] ?? 0);
|
|
95
|
+
sum += Math.floor(product / 10) + product % 10;
|
|
96
|
+
}
|
|
97
|
+
if (sum % 5 === 0) return true;
|
|
98
|
+
return ubn.charAt(6) === "7" && (sum + 1) % 5 === 0;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Validate buyer invoice preferences.
|
|
102
|
+
*
|
|
103
|
+
* @returns an error message, or `null` when the prefs are valid
|
|
104
|
+
*/
|
|
105
|
+
function validateEinvoicePrefs(prefs) {
|
|
106
|
+
if (typeof prefs !== "object" || prefs === null) return "Invalid einvoice prefs: expected an object like { type: \"personal\" }";
|
|
107
|
+
switch (prefs.type) {
|
|
108
|
+
case "personal": return null;
|
|
109
|
+
case "mobile_barcode": return MOBILE_BARCODE_RE.test(prefs.carrierCode) ? null : "Invalid einvoice mobile barcode: must be \"/\" followed by 7 characters (0-9, A-Z, ., +, -)";
|
|
110
|
+
case "ubn":
|
|
111
|
+
if (!isValidTaiwanUbn(prefs.ubn)) return "Invalid einvoice UBN (統一編號): must be 8 digits with a valid checksum";
|
|
112
|
+
if (!prefs.buyerName || !prefs.buyerName.trim() || prefs.buyerName.trim().length > 60) return "Invalid einvoice buyerName (公司抬頭): required, at most 60 characters";
|
|
113
|
+
return null;
|
|
114
|
+
case "donation": return DONATION_CODE_RE.test(prefs.donationCode) ? null : "Invalid einvoice donation code (愛心碼): must be 3-7 digits";
|
|
115
|
+
default: return `Invalid einvoice type: ${String(prefs.type)}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
32
120
|
//#region \0@oxc-project+runtime@0.140.0/helpers/esm/typeof.js
|
|
33
121
|
function _typeof(o) {
|
|
34
122
|
"@babel/helpers - typeof";
|
|
@@ -140,7 +228,112 @@ var WebhookSignatureVerificationError = class extends Error {
|
|
|
140
228
|
|
|
141
229
|
//#endregion
|
|
142
230
|
//#region package.json
|
|
143
|
-
var version = "0.
|
|
231
|
+
var version = "0.21.0";
|
|
232
|
+
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/server/resources/checkout-sessions.ts
|
|
235
|
+
/**
|
|
236
|
+
* Checkout Sessions Resource
|
|
237
|
+
*
|
|
238
|
+
* Create hosted checkout sessions server-side (Secret Key) and inspect their
|
|
239
|
+
* status — the server-side counterpart of the client SDK's
|
|
240
|
+
* createCheckoutSession/redirectToCheckout.
|
|
241
|
+
*/
|
|
242
|
+
const SDK_VERSION$3 = version;
|
|
243
|
+
const SDK_TYPE$3 = "server";
|
|
244
|
+
/** v1 error payloads carry { code, message } but no `type` — normalize so
|
|
245
|
+
* RecurAPIError.type is always a string as its type claims. */
|
|
246
|
+
function toRecurError(error, fallbackMessage) {
|
|
247
|
+
const e = error ?? {};
|
|
248
|
+
return {
|
|
249
|
+
type: e.type === "authentication_error" || e.type === "invalid_request_error" ? e.type : "api_error",
|
|
250
|
+
code: e.code ?? "unknown",
|
|
251
|
+
message: e.message ?? fallbackMessage,
|
|
252
|
+
doc_url: e.doc_url,
|
|
253
|
+
details: e.details
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
var CheckoutSessions = class {
|
|
257
|
+
constructor(config) {
|
|
258
|
+
_defineProperty(this, "config", void 0);
|
|
259
|
+
this.config = config;
|
|
260
|
+
}
|
|
261
|
+
headers() {
|
|
262
|
+
return {
|
|
263
|
+
"Content-Type": "application/json",
|
|
264
|
+
Authorization: `Bearer ${this.config.secretKey}`,
|
|
265
|
+
"X-Recur-SDK-Version": SDK_VERSION$3,
|
|
266
|
+
"X-Recur-SDK-Type": SDK_TYPE$3
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Create a hosted checkout session and get the URL to redirect the buyer to.
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* const session = await recur.checkoutSessions.create({
|
|
275
|
+
* productId: 'prod_xxx',
|
|
276
|
+
* successUrl: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
|
|
277
|
+
* cancelUrl: 'https://example.com/pricing',
|
|
278
|
+
* externalCustomerId: 'user_123',
|
|
279
|
+
* einvoice: { type: 'ubn', ubn: '04595252', buyerName: '恆吉股份有限公司' },
|
|
280
|
+
* });
|
|
281
|
+
* redirect(session.url);
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
async create(params) {
|
|
285
|
+
if (!params.productId && !params.productSlug) throw new RecurAPIError({
|
|
286
|
+
type: "invalid_request_error",
|
|
287
|
+
code: "missing_product",
|
|
288
|
+
message: "Either productId or productSlug is required"
|
|
289
|
+
}, 400);
|
|
290
|
+
if (params.einvoice) {
|
|
291
|
+
const einvoiceError = validateEinvoicePrefs(params.einvoice);
|
|
292
|
+
if (einvoiceError) throw new RecurAPIError({
|
|
293
|
+
type: "invalid_request_error",
|
|
294
|
+
code: "invalid_einvoice",
|
|
295
|
+
message: einvoiceError
|
|
296
|
+
}, 400);
|
|
297
|
+
}
|
|
298
|
+
const body = {
|
|
299
|
+
successUrl: params.successUrl,
|
|
300
|
+
cancelUrl: params.cancelUrl
|
|
301
|
+
};
|
|
302
|
+
if (params.productId) body.productId = params.productId;
|
|
303
|
+
if (params.productSlug) body.productSlug = params.productSlug;
|
|
304
|
+
if (params.mode) body.mode = params.mode;
|
|
305
|
+
if (params.customerEmail) body.customerEmail = params.customerEmail;
|
|
306
|
+
if (params.customerName) body.customerName = params.customerName;
|
|
307
|
+
if (params.externalCustomerId) body.externalId = params.externalCustomerId;
|
|
308
|
+
if (params.metadata) body.metadata = params.metadata;
|
|
309
|
+
if (params.promotionCode) body.promotionCode = params.promotionCode;
|
|
310
|
+
if (params.collectPaymentMethod) body.collectPaymentMethod = params.collectPaymentMethod;
|
|
311
|
+
if (params.einvoice) body.einvoice = params.einvoice;
|
|
312
|
+
const baseUrl = this.config.baseUrl || "https://api.recur.tw";
|
|
313
|
+
const response = await fetch(`${baseUrl}/v1/checkout/sessions`, {
|
|
314
|
+
method: "POST",
|
|
315
|
+
headers: this.headers(),
|
|
316
|
+
body: JSON.stringify(body)
|
|
317
|
+
});
|
|
318
|
+
const data = await response.json();
|
|
319
|
+
if (!response.ok) throw new RecurAPIError(toRecurError(data.error, "Failed to create checkout session"), response.status);
|
|
320
|
+
return toCamelCase(data);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Retrieve a checkout session — e.g. to confirm status after the
|
|
324
|
+
* success-URL redirect.
|
|
325
|
+
*/
|
|
326
|
+
async retrieve(sessionId) {
|
|
327
|
+
const baseUrl = this.config.baseUrl || "https://api.recur.tw";
|
|
328
|
+
const response = await fetch(`${baseUrl}/v1/checkout/sessions/${encodeURIComponent(sessionId)}`, {
|
|
329
|
+
method: "GET",
|
|
330
|
+
headers: this.headers()
|
|
331
|
+
});
|
|
332
|
+
const data = await response.json();
|
|
333
|
+
if (!response.ok) throw new RecurAPIError(toRecurError(data.error, "Failed to retrieve checkout session"), response.status);
|
|
334
|
+
return toCamelCase(data);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
144
337
|
|
|
145
338
|
//#endregion
|
|
146
339
|
//#region src/server/resources/entitlements.ts
|
|
@@ -599,6 +792,7 @@ var Recur = class {
|
|
|
599
792
|
*/
|
|
600
793
|
constructor(secretKey, options) {
|
|
601
794
|
_defineProperty(this, "config", void 0);
|
|
795
|
+
_defineProperty(this, "checkoutSessions", void 0);
|
|
602
796
|
_defineProperty(this, "portal", void 0);
|
|
603
797
|
_defineProperty(this, "webhooks", void 0);
|
|
604
798
|
_defineProperty(this, "entitlements", void 0);
|
|
@@ -609,6 +803,7 @@ var Recur = class {
|
|
|
609
803
|
secretKey,
|
|
610
804
|
baseUrl: options?.baseUrl || "https://api.recur.tw"
|
|
611
805
|
};
|
|
806
|
+
this.checkoutSessions = new CheckoutSessions(this.config);
|
|
612
807
|
this.portal = new Portal(this.config);
|
|
613
808
|
this.webhooks = new Webhooks(this.config);
|
|
614
809
|
this.entitlements = new Entitlements(this.config);
|
|
@@ -617,6 +812,7 @@ var Recur = class {
|
|
|
617
812
|
};
|
|
618
813
|
|
|
619
814
|
//#endregion
|
|
815
|
+
exports.CheckoutSessions = CheckoutSessions;
|
|
620
816
|
exports.Recur = Recur;
|
|
621
817
|
exports.RecurAPIError = RecurAPIError;
|
|
622
818
|
exports.WEBHOOK_EVENT_TYPES = WEBHOOK_EVENT_TYPES;
|
package/dist/server.d.cts
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
//#region src/einvoice.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Taiwan e-invoice (電子發票) buyer preferences
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the server's einvoicePrefsSchema (@workspace/einvoice). Validation is
|
|
6
|
+
* kept inline — no zod, no dependency on the einvoice packages — so the browser
|
|
7
|
+
* bundles stay lean. A contract test (__tests__/einvoice.test.ts) pins this
|
|
8
|
+
* implementation against the server schema and @paid-tw/einvoice's validators.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Buyer invoice choice, collected at checkout when the merchant has an active
|
|
12
|
+
* e-invoice integration:
|
|
13
|
+
* - `personal`: 個人雲端發票, delivered via the buyer's email
|
|
14
|
+
* - `mobile_barcode`: 手機條碼載具, e.g. `/ABC1234`
|
|
15
|
+
* - `ubn`: 公司統編三聯式 (B2B), 8-digit 統一編號 + company title
|
|
16
|
+
* - `donation`: 捐贈發票 via 愛心碼
|
|
17
|
+
*/
|
|
18
|
+
type EinvoicePrefs = {
|
|
19
|
+
type: 'personal';
|
|
20
|
+
} | {
|
|
21
|
+
type: 'mobile_barcode';
|
|
22
|
+
carrierCode: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'ubn';
|
|
25
|
+
ubn: string;
|
|
26
|
+
buyerName: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: 'donation';
|
|
29
|
+
donationCode: string;
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
1
32
|
//#region src/server/types.d.ts
|
|
2
33
|
/**
|
|
3
34
|
* Recur Server SDK Types
|
|
@@ -251,6 +282,66 @@ interface EntitlementListResult {
|
|
|
251
282
|
*/
|
|
252
283
|
entitlements: Entitlement[];
|
|
253
284
|
}
|
|
285
|
+
interface CheckoutSessionCreateParams {
|
|
286
|
+
/** Product ID — either productId or productSlug is required */
|
|
287
|
+
productId?: string;
|
|
288
|
+
/** Product slug — alternative to productId */
|
|
289
|
+
productSlug?: string;
|
|
290
|
+
/** PAYMENT (one-time), SUBSCRIPTION (recurring), or SETUP (save card); inferred from the product when omitted */
|
|
291
|
+
mode?: 'PAYMENT' | 'SUBSCRIPTION' | 'SETUP';
|
|
292
|
+
/** Redirect URL after successful payment (supports {CHECKOUT_SESSION_ID}) */
|
|
293
|
+
successUrl: string;
|
|
294
|
+
/** Redirect URL when the buyer cancels */
|
|
295
|
+
cancelUrl: string;
|
|
296
|
+
customerEmail?: string;
|
|
297
|
+
customerName?: string;
|
|
298
|
+
/** Your system's user ID — binds the resulting customer */
|
|
299
|
+
externalCustomerId?: string;
|
|
300
|
+
/** Arbitrary key-value data (Secret Key only) */
|
|
301
|
+
metadata?: Record<string, unknown>;
|
|
302
|
+
/** Pre-applied promotion code (validated at creation) */
|
|
303
|
+
promotionCode?: string;
|
|
304
|
+
/** 'always' (default) or 'if_required' (skip card collection at $0) */
|
|
305
|
+
collectPaymentMethod?: 'always' | 'if_required';
|
|
306
|
+
/** Taiwan e-invoice buyer preferences — prefills 發票資訊 on the hosted page */
|
|
307
|
+
einvoice?: EinvoicePrefs;
|
|
308
|
+
}
|
|
309
|
+
interface CheckoutSession {
|
|
310
|
+
id: string;
|
|
311
|
+
/** Hosted checkout URL to redirect the buyer to */
|
|
312
|
+
url: string;
|
|
313
|
+
expiresAt: string;
|
|
314
|
+
/** Effective amount after discount */
|
|
315
|
+
amount: number;
|
|
316
|
+
requiresPaymentMethod: boolean;
|
|
317
|
+
/** Client secret for status polling (pass to the client SDK if needed) */
|
|
318
|
+
clientSecret?: string;
|
|
319
|
+
/** Whether the organization issues e-invoices for paid checkouts */
|
|
320
|
+
einvoiceEnabled?: boolean;
|
|
321
|
+
livemode: boolean;
|
|
322
|
+
}
|
|
323
|
+
interface CheckoutSessionDetails {
|
|
324
|
+
id: string;
|
|
325
|
+
status: 'OPEN' | 'COMPLETE' | 'EXPIRED';
|
|
326
|
+
mode: 'PAYMENT' | 'SUBSCRIPTION' | 'SETUP';
|
|
327
|
+
customerEmail: string | null;
|
|
328
|
+
externalCustomerId: string | null;
|
|
329
|
+
orderId: string | null;
|
|
330
|
+
subscriptionId: string | null;
|
|
331
|
+
paymentIntentId: string | null;
|
|
332
|
+
setupIntentId: string | null;
|
|
333
|
+
amountTotal: number;
|
|
334
|
+
currency: string;
|
|
335
|
+
product: {
|
|
336
|
+
id: string;
|
|
337
|
+
name: string;
|
|
338
|
+
description?: string | null;
|
|
339
|
+
};
|
|
340
|
+
expiresAt: string;
|
|
341
|
+
createdAt: string;
|
|
342
|
+
completedAt: string | null;
|
|
343
|
+
livemode: boolean;
|
|
344
|
+
}
|
|
254
345
|
/**
|
|
255
346
|
* All webhook event types Recur emits, as a runtime constant.
|
|
256
347
|
*
|
|
@@ -339,6 +430,34 @@ declare class WebhookSignatureVerificationError extends Error {
|
|
|
339
430
|
constructor(message: string);
|
|
340
431
|
}
|
|
341
432
|
//#endregion
|
|
433
|
+
//#region src/server/resources/checkout-sessions.d.ts
|
|
434
|
+
declare class CheckoutSessions {
|
|
435
|
+
private config;
|
|
436
|
+
constructor(config: RecurConfig);
|
|
437
|
+
private headers;
|
|
438
|
+
/**
|
|
439
|
+
* Create a hosted checkout session and get the URL to redirect the buyer to.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```typescript
|
|
443
|
+
* const session = await recur.checkoutSessions.create({
|
|
444
|
+
* productId: 'prod_xxx',
|
|
445
|
+
* successUrl: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
|
|
446
|
+
* cancelUrl: 'https://example.com/pricing',
|
|
447
|
+
* externalCustomerId: 'user_123',
|
|
448
|
+
* einvoice: { type: 'ubn', ubn: '04595252', buyerName: '恆吉股份有限公司' },
|
|
449
|
+
* });
|
|
450
|
+
* redirect(session.url);
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
create(params: CheckoutSessionCreateParams): Promise<CheckoutSession>;
|
|
454
|
+
/**
|
|
455
|
+
* Retrieve a checkout session — e.g. to confirm status after the
|
|
456
|
+
* success-URL redirect.
|
|
457
|
+
*/
|
|
458
|
+
retrieve(sessionId: string): Promise<CheckoutSessionDetails>;
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
342
461
|
//#region src/server/resources/entitlements.d.ts
|
|
343
462
|
/**
|
|
344
463
|
* Entitlements resource for checking and listing customer entitlements
|
|
@@ -549,6 +668,20 @@ declare class Webhooks {
|
|
|
549
668
|
//#region src/server/recur.d.ts
|
|
550
669
|
declare class Recur {
|
|
551
670
|
private config;
|
|
671
|
+
/**
|
|
672
|
+
* Checkout Sessions resource for hosted checkout (create + retrieve)
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```typescript
|
|
676
|
+
* const session = await recur.checkoutSessions.create({
|
|
677
|
+
* productId: 'prod_xxx',
|
|
678
|
+
* successUrl: 'https://example.com/success',
|
|
679
|
+
* cancelUrl: 'https://example.com/pricing',
|
|
680
|
+
* });
|
|
681
|
+
* redirect(session.url);
|
|
682
|
+
* ```
|
|
683
|
+
*/
|
|
684
|
+
readonly checkoutSessions: CheckoutSessions;
|
|
552
685
|
/**
|
|
553
686
|
* Portal resource for managing customer portal sessions
|
|
554
687
|
*/
|
|
@@ -606,4 +739,4 @@ declare class Recur {
|
|
|
606
739
|
constructor(secretKey: string, options?: Omit<RecurConfig, 'secretKey'>);
|
|
607
740
|
}
|
|
608
741
|
//#endregion
|
|
609
|
-
export { type ApiErrorCode, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCheckOptions, type EntitlementCheckResult, type EntitlementListOptions, type EntitlementListResult, type EntitlementStatus, type EntitlementSubscription, type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError, WEBHOOK_EVENT_TYPES, type WebhookEvent, type WebhookEventType, WebhookSignatureVerificationError };
|
|
742
|
+
export { type ApiErrorCode, type CheckoutSession, type CheckoutSessionCreateParams, type CheckoutSessionDetails, CheckoutSessions, type CustomerIdentifier, type DuplicateSubscriptionDetails, type EinvoicePrefs, type Entitlement, type EntitlementCheckOptions, type EntitlementCheckResult, type EntitlementListOptions, type EntitlementListResult, type EntitlementStatus, type EntitlementSubscription, type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError, WEBHOOK_EVENT_TYPES, type WebhookEvent, type WebhookEventType, WebhookSignatureVerificationError };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
//#region src/einvoice.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Taiwan e-invoice (電子發票) buyer preferences
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the server's einvoicePrefsSchema (@workspace/einvoice). Validation is
|
|
6
|
+
* kept inline — no zod, no dependency on the einvoice packages — so the browser
|
|
7
|
+
* bundles stay lean. A contract test (__tests__/einvoice.test.ts) pins this
|
|
8
|
+
* implementation against the server schema and @paid-tw/einvoice's validators.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Buyer invoice choice, collected at checkout when the merchant has an active
|
|
12
|
+
* e-invoice integration:
|
|
13
|
+
* - `personal`: 個人雲端發票, delivered via the buyer's email
|
|
14
|
+
* - `mobile_barcode`: 手機條碼載具, e.g. `/ABC1234`
|
|
15
|
+
* - `ubn`: 公司統編三聯式 (B2B), 8-digit 統一編號 + company title
|
|
16
|
+
* - `donation`: 捐贈發票 via 愛心碼
|
|
17
|
+
*/
|
|
18
|
+
type EinvoicePrefs = {
|
|
19
|
+
type: 'personal';
|
|
20
|
+
} | {
|
|
21
|
+
type: 'mobile_barcode';
|
|
22
|
+
carrierCode: string;
|
|
23
|
+
} | {
|
|
24
|
+
type: 'ubn';
|
|
25
|
+
ubn: string;
|
|
26
|
+
buyerName: string;
|
|
27
|
+
} | {
|
|
28
|
+
type: 'donation';
|
|
29
|
+
donationCode: string;
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
1
32
|
//#region src/server/types.d.ts
|
|
2
33
|
/**
|
|
3
34
|
* Recur Server SDK Types
|
|
@@ -251,6 +282,66 @@ interface EntitlementListResult {
|
|
|
251
282
|
*/
|
|
252
283
|
entitlements: Entitlement[];
|
|
253
284
|
}
|
|
285
|
+
interface CheckoutSessionCreateParams {
|
|
286
|
+
/** Product ID — either productId or productSlug is required */
|
|
287
|
+
productId?: string;
|
|
288
|
+
/** Product slug — alternative to productId */
|
|
289
|
+
productSlug?: string;
|
|
290
|
+
/** PAYMENT (one-time), SUBSCRIPTION (recurring), or SETUP (save card); inferred from the product when omitted */
|
|
291
|
+
mode?: 'PAYMENT' | 'SUBSCRIPTION' | 'SETUP';
|
|
292
|
+
/** Redirect URL after successful payment (supports {CHECKOUT_SESSION_ID}) */
|
|
293
|
+
successUrl: string;
|
|
294
|
+
/** Redirect URL when the buyer cancels */
|
|
295
|
+
cancelUrl: string;
|
|
296
|
+
customerEmail?: string;
|
|
297
|
+
customerName?: string;
|
|
298
|
+
/** Your system's user ID — binds the resulting customer */
|
|
299
|
+
externalCustomerId?: string;
|
|
300
|
+
/** Arbitrary key-value data (Secret Key only) */
|
|
301
|
+
metadata?: Record<string, unknown>;
|
|
302
|
+
/** Pre-applied promotion code (validated at creation) */
|
|
303
|
+
promotionCode?: string;
|
|
304
|
+
/** 'always' (default) or 'if_required' (skip card collection at $0) */
|
|
305
|
+
collectPaymentMethod?: 'always' | 'if_required';
|
|
306
|
+
/** Taiwan e-invoice buyer preferences — prefills 發票資訊 on the hosted page */
|
|
307
|
+
einvoice?: EinvoicePrefs;
|
|
308
|
+
}
|
|
309
|
+
interface CheckoutSession {
|
|
310
|
+
id: string;
|
|
311
|
+
/** Hosted checkout URL to redirect the buyer to */
|
|
312
|
+
url: string;
|
|
313
|
+
expiresAt: string;
|
|
314
|
+
/** Effective amount after discount */
|
|
315
|
+
amount: number;
|
|
316
|
+
requiresPaymentMethod: boolean;
|
|
317
|
+
/** Client secret for status polling (pass to the client SDK if needed) */
|
|
318
|
+
clientSecret?: string;
|
|
319
|
+
/** Whether the organization issues e-invoices for paid checkouts */
|
|
320
|
+
einvoiceEnabled?: boolean;
|
|
321
|
+
livemode: boolean;
|
|
322
|
+
}
|
|
323
|
+
interface CheckoutSessionDetails {
|
|
324
|
+
id: string;
|
|
325
|
+
status: 'OPEN' | 'COMPLETE' | 'EXPIRED';
|
|
326
|
+
mode: 'PAYMENT' | 'SUBSCRIPTION' | 'SETUP';
|
|
327
|
+
customerEmail: string | null;
|
|
328
|
+
externalCustomerId: string | null;
|
|
329
|
+
orderId: string | null;
|
|
330
|
+
subscriptionId: string | null;
|
|
331
|
+
paymentIntentId: string | null;
|
|
332
|
+
setupIntentId: string | null;
|
|
333
|
+
amountTotal: number;
|
|
334
|
+
currency: string;
|
|
335
|
+
product: {
|
|
336
|
+
id: string;
|
|
337
|
+
name: string;
|
|
338
|
+
description?: string | null;
|
|
339
|
+
};
|
|
340
|
+
expiresAt: string;
|
|
341
|
+
createdAt: string;
|
|
342
|
+
completedAt: string | null;
|
|
343
|
+
livemode: boolean;
|
|
344
|
+
}
|
|
254
345
|
/**
|
|
255
346
|
* All webhook event types Recur emits, as a runtime constant.
|
|
256
347
|
*
|
|
@@ -339,6 +430,34 @@ declare class WebhookSignatureVerificationError extends Error {
|
|
|
339
430
|
constructor(message: string);
|
|
340
431
|
}
|
|
341
432
|
//#endregion
|
|
433
|
+
//#region src/server/resources/checkout-sessions.d.ts
|
|
434
|
+
declare class CheckoutSessions {
|
|
435
|
+
private config;
|
|
436
|
+
constructor(config: RecurConfig);
|
|
437
|
+
private headers;
|
|
438
|
+
/**
|
|
439
|
+
* Create a hosted checkout session and get the URL to redirect the buyer to.
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```typescript
|
|
443
|
+
* const session = await recur.checkoutSessions.create({
|
|
444
|
+
* productId: 'prod_xxx',
|
|
445
|
+
* successUrl: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
|
|
446
|
+
* cancelUrl: 'https://example.com/pricing',
|
|
447
|
+
* externalCustomerId: 'user_123',
|
|
448
|
+
* einvoice: { type: 'ubn', ubn: '04595252', buyerName: '恆吉股份有限公司' },
|
|
449
|
+
* });
|
|
450
|
+
* redirect(session.url);
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
create(params: CheckoutSessionCreateParams): Promise<CheckoutSession>;
|
|
454
|
+
/**
|
|
455
|
+
* Retrieve a checkout session — e.g. to confirm status after the
|
|
456
|
+
* success-URL redirect.
|
|
457
|
+
*/
|
|
458
|
+
retrieve(sessionId: string): Promise<CheckoutSessionDetails>;
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
342
461
|
//#region src/server/resources/entitlements.d.ts
|
|
343
462
|
/**
|
|
344
463
|
* Entitlements resource for checking and listing customer entitlements
|
|
@@ -549,6 +668,20 @@ declare class Webhooks {
|
|
|
549
668
|
//#region src/server/recur.d.ts
|
|
550
669
|
declare class Recur {
|
|
551
670
|
private config;
|
|
671
|
+
/**
|
|
672
|
+
* Checkout Sessions resource for hosted checkout (create + retrieve)
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```typescript
|
|
676
|
+
* const session = await recur.checkoutSessions.create({
|
|
677
|
+
* productId: 'prod_xxx',
|
|
678
|
+
* successUrl: 'https://example.com/success',
|
|
679
|
+
* cancelUrl: 'https://example.com/pricing',
|
|
680
|
+
* });
|
|
681
|
+
* redirect(session.url);
|
|
682
|
+
* ```
|
|
683
|
+
*/
|
|
684
|
+
readonly checkoutSessions: CheckoutSessions;
|
|
552
685
|
/**
|
|
553
686
|
* Portal resource for managing customer portal sessions
|
|
554
687
|
*/
|
|
@@ -606,4 +739,4 @@ declare class Recur {
|
|
|
606
739
|
constructor(secretKey: string, options?: Omit<RecurConfig, 'secretKey'>);
|
|
607
740
|
}
|
|
608
741
|
//#endregion
|
|
609
|
-
export { type ApiErrorCode, type CustomerIdentifier, type DuplicateSubscriptionDetails, type Entitlement, type EntitlementCheckOptions, type EntitlementCheckResult, type EntitlementListOptions, type EntitlementListResult, type EntitlementStatus, type EntitlementSubscription, type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError, WEBHOOK_EVENT_TYPES, type WebhookEvent, type WebhookEventType, WebhookSignatureVerificationError };
|
|
742
|
+
export { type ApiErrorCode, type CheckoutSession, type CheckoutSessionCreateParams, type CheckoutSessionDetails, CheckoutSessions, type CustomerIdentifier, type DuplicateSubscriptionDetails, type EinvoicePrefs, type Entitlement, type EntitlementCheckOptions, type EntitlementCheckResult, type EntitlementListOptions, type EntitlementListResult, type EntitlementStatus, type EntitlementSubscription, type PortalSession, type PortalSessionCreateParams, Recur, RecurAPIError, type RecurConfig, type RecurError, WEBHOOK_EVENT_TYPES, type WebhookEvent, type WebhookEventType, WebhookSignatureVerificationError };
|