recur-tw 0.16.0 → 0.17.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/README.md CHANGED
@@ -212,6 +212,7 @@ function ProductsPage() {
212
212
  - `useProducts(options?)` - 取得商品列表
213
213
  - `useRecur()` - 取得 checkout 函式與狀態
214
214
  - `useCustomer()` - 檢查客戶權限與訂閱狀態
215
+ - `usePromoCode(options?)` - 驗證優惠碼並計算折扣
215
216
 
216
217
  ```tsx
217
218
  // 篩選商品類型
@@ -364,6 +365,60 @@ customer: { externalId: 'usr_12345' }
364
365
  customer: { id: 'cus_xxx' }
365
366
  ```
366
367
 
368
+ ### Webhook 驗證
369
+
370
+ 驗證 Webhook 簽名,確保事件來自 Recur:
371
+
372
+ ```typescript
373
+ import { Recur } from 'recur-tw/server';
374
+
375
+ const recur = new Recur(process.env.RECUR_SECRET_KEY!);
376
+
377
+ export async function POST(request: Request) {
378
+ const payload = await request.text();
379
+ const signature = request.headers.get('x-recur-signature');
380
+
381
+ try {
382
+ const event = recur.webhooks.verify(
383
+ payload,
384
+ signature,
385
+ process.env.RECUR_WEBHOOK_SECRET!,
386
+ );
387
+
388
+ switch (event.type) {
389
+ case 'checkout.completed':
390
+ // 處理付款完成
391
+ break;
392
+ case 'subscription.cancelled':
393
+ // 處理訂閱取消
394
+ break;
395
+ }
396
+ return new Response('OK', { status: 200 });
397
+ } catch (err) {
398
+ return new Response('Invalid signature', { status: 401 });
399
+ }
400
+ }
401
+ ```
402
+
403
+ > 簽名無效時會拋出 `WebhookSignatureVerificationError`。
404
+
405
+ ### Payment Links
406
+
407
+ 建立可分享的付款連結:
408
+
409
+ ```typescript
410
+ const link = await recur.paymentLinks.create({
411
+ productId: 'prod_xxx',
412
+ promotionCode: 'LAUNCH20', // 選填:預先套用優惠碼
413
+ maxCompletions: 100, // 選填:限制完成次數
414
+ });
415
+
416
+ console.log(link.url); // https://buy.recur.tw/7sY3cxcrX1
417
+
418
+ // 列出付款連結
419
+ const { data, hasMore } = await recur.paymentLinks.list({ active: true });
420
+ ```
421
+
367
422
  ---
368
423
 
369
424
  ## API Key
package/dist/checkout.js CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var RecurCheckoutScript=(()=>{var f=Object.defineProperty;var v=Object.getOwnPropertyDescriptor;var E=Object.getOwnPropertyNames;var C=Object.prototype.hasOwnProperty;var R=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},_=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of E(t))!C.call(e,o)&&o!==r&&f(e,o,{get:()=>t[o],enumerable:!(n=v(t,o))||n.enumerable});return e};var U=e=>_(f({},"__esModule",{value:!0}),e);var T={};R(T,{default:()=>I});var s={embedUrl:"https://embed.recur.tw",apiUrl:"https://api.recur.tw"},h=null,p=null;function m(){if(document.getElementById("recur-checkout-styles"))return;let e=document.createElement("style");e.id="recur-checkout-styles",e.textContent=`
1
+ var RecurCheckoutScript=(function(){let e={embedUrl:`https://embed.recur.tw`,apiUrl:`https://api.recur.tw`},t=null,n=null;function r(){if(document.getElementById(`recur-checkout-styles`))return;let e=document.createElement(`style`);e.id=`recur-checkout-styles`,e.textContent=`
2
2
  .recur-checkout-dialog {
3
3
  border: none;
4
4
  border-radius: 12px;
@@ -130,5 +130,4 @@
130
130
  opacity: 0.7;
131
131
  pointer-events: none;
132
132
  }
133
- `,document.head.appendChild(e)}function A(e){try{let r=new URL(e,window.location.origin).pathname.split("/"),n=r.indexOf("buy");if(n===-1||n>=r.length-1)return null;let o=r[n+1];return o?o.startsWith("prod_")?{productId:o}:{productSlug:o}:null}catch{return null}}function S(e){let t=e.getAttribute("data-key");if(t)return t;try{let o=e.getAttribute("href");if(o){let c=new URL(o,window.location.origin).searchParams.get("key");if(c)return c}}catch{}let r=document.querySelector("script[data-recur-key]");if(r)return r.getAttribute("data-recur-key");let n=window.RecurConfig;return n?.publishableKey?n.publishableKey:null}async function b(e,t){let r={};e.productId&&(r.productId=e.productId),e.email&&(r.customerEmail=e.email),e.name&&(r.customerName=e.name);let n=await fetch(`${s.apiUrl}/v1/checkouts`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":t,"X-Recur-Embed-Domain":s.embedUrl,"X-Recur-SDK-Type":"checkout.js","X-Recur-Source":window.location.origin},body:JSON.stringify(r)});if(!n.ok){let i=await n.json().catch(()=>({}));throw new Error(i.error?.message||i.error||"Failed to create checkout")}let o=await n.json();return{id:o.checkout.id,publishableKey:t,clientSecret:o.checkout.client_secret}}function y(e,t){u(),m();let r=document.createElement("dialog");r.className="recur-checkout-dialog";let n=document.createElement("div");n.className="recur-checkout-dialog__content",n.setAttribute("tabindex","-1"),n.setAttribute("autofocus","");let o=document.createElement("div");o.className="recur-checkout-dialog__header";let i=document.createElement("button");i.className="recur-checkout-dialog__close",i.innerHTML="×",i.type="button",i.setAttribute("aria-label","Close"),i.onclick=()=>{u(),t?.(),d("recur:close",{})},o.appendChild(i);let c=document.createElement("iframe");c.className="recur-checkout-dialog__iframe",c.src=e,c.setAttribute("allow","payment"),c.setAttribute("title","Recur Checkout"),n.appendChild(o),n.appendChild(c),r.appendChild(n),document.body.appendChild(r),h=r,p=c,r.showModal(),document.body.style.overflow="hidden",r.addEventListener("cancel",a=>{a.preventDefault(),u(),t?.(),d("recur:close",{})}),window.addEventListener("message",k)}function u(){h&&(h.close(),h.remove(),h=null),p=null,document.body.style.overflow="",window.removeEventListener("message",k)}function k(e){if(!e.origin.includes("recur.tw")&&!e.origin.includes("localhost"))return;let{type:t,data:r}=e.data||{};switch(t){case"RECUR_PAYMENT_SUCCESS":u(),d("recur:success",r);break;case"RECUR_PAYMENT_ERROR":d("recur:error",r);break;case"RECUR_PAYMENT_CANCEL":u(),d("recur:close",{});break;case"RECUR_RESIZE":p&&r?.height&&(p.style.height=`${Math.min(r.height+56,window.innerHeight*.9)}px`);break}}function d(e,t){window.dispatchEvent(new CustomEvent(e,{detail:t}))}async function P(e,t){e.preventDefault();let r=t.getAttribute("href");if(!r){console.error("[Recur] Missing href on recur-button");return}let n=A(r);if(!n){console.error("[Recur] Invalid product URL:",r);return}let o=S(t);if(!o){console.error("[Recur] Missing publishable key. Add data-key attribute or set RecurConfig.publishableKey");return}let i={productId:n.productId||"",email:t.getAttribute("data-email")||void 0,name:t.getAttribute("data-name")||void 0,successUrl:t.getAttribute("data-success-url")||void 0,theme:t.getAttribute("data-theme")||void 0};t.setAttribute("data-loading","true");let c=t.textContent;t.textContent="...";try{let a=await b(i,o),l=new URL(`${s.embedUrl}/checkout`);l.searchParams.set("id",a.id),l.searchParams.set("key",a.publishableKey),l.searchParams.set("client_secret",a.clientSecret),i.theme&&l.searchParams.set("theme",i.theme),y(l.toString(),()=>{let x=i.successUrl})}catch(a){console.error("[Recur] Checkout error:",a),d("recur:error",{message:a instanceof Error?a.message:"Unknown error"})}finally{t.setAttribute("data-loading","false"),t.textContent=c}}function g(){document.querySelectorAll(".recur-button").forEach(t=>{t.hasAttribute("data-recur-initialized")||(t.setAttribute("data-recur-initialized","true"),t.addEventListener("click",r=>P(r,t)))})}function L(){let e=window.location.hostname;(e==="localhost"||e==="127.0.0.1"||e.includes(".local"))&&(s.embedUrl=`${window.location.protocol}//${e}:3002`,s.apiUrl=`${window.location.protocol}//${e}:3000/api`)}var w={config(e){Object.assign(s,e)},async popup(e){let t=await b(e,e.key),r=new URL(`${s.embedUrl}/checkout`);r.searchParams.set("id",t.id),r.searchParams.set("key",t.publishableKey),r.searchParams.set("client_secret",t.clientSecret),e.theme&&r.searchParams.set("theme",e.theme),y(r.toString())},close(){u()},init(){g()}};function M(){L(),m(),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",g):g(),new MutationObserver(t=>{for(let r of t)r.addedNodes.length>0&&g()}).observe(document.body,{childList:!0,subtree:!0})}window.Recur=w;M();var I=w;return U(T);})();
134
- /* Recur Checkout - https://recur.tw */
133
+ `,document.head.appendChild(e)}function i(e){try{let t=new URL(e,window.location.origin).pathname.split(`/`),n=t.indexOf(`buy`);if(n===-1||n>=t.length-1)return null;let r=t[n+1];return r?r.startsWith(`prod_`)?{productId:r}:{productSlug:r}:null}catch{return null}}function a(e){let t=e.getAttribute(`data-key`);if(t)return t;try{let t=e.getAttribute(`href`);if(t){let e=new URL(t,window.location.origin).searchParams.get(`key`);if(e)return e}}catch{}let n=document.querySelector(`script[data-recur-key]`);if(n)return n.getAttribute(`data-recur-key`);let r=window.RecurConfig;return r?.publishableKey?r.publishableKey:null}async function o(t,n){let r={};t.productId&&(r.productId=t.productId),t.email&&(r.customerEmail=t.email),t.name&&(r.customerName=t.name);let i=await fetch(`${e.apiUrl}/v1/checkouts`,{method:`POST`,headers:{"Content-Type":`application/json`,"X-Recur-Publishable-Key":n,"X-Recur-Embed-Domain":e.embedUrl,"X-Recur-SDK-Type":`checkout.js`,"X-Recur-Source":window.location.origin},body:JSON.stringify(r)});if(!i.ok){let e=await i.json().catch(()=>({}));throw Error(e.error?.message||e.error||`Failed to create checkout`)}let a=await i.json();return{id:a.checkout.id,publishableKey:n,clientSecret:a.checkout.client_secret}}function s(e,i){c(),r();let a=document.createElement(`dialog`);a.className=`recur-checkout-dialog`;let o=document.createElement(`div`);o.className=`recur-checkout-dialog__content`,o.setAttribute(`tabindex`,`-1`),o.setAttribute(`autofocus`,``);let s=document.createElement(`div`);s.className=`recur-checkout-dialog__header`;let d=document.createElement(`button`);d.className=`recur-checkout-dialog__close`,d.innerHTML=`×`,d.type=`button`,d.setAttribute(`aria-label`,`Close`),d.onclick=()=>{c(),i?.(),u(`recur:close`,{})},s.appendChild(d);let f=document.createElement(`iframe`);f.className=`recur-checkout-dialog__iframe`,f.src=e,f.setAttribute(`allow`,`payment`),f.setAttribute(`title`,`Recur Checkout`),o.appendChild(s),o.appendChild(f),a.appendChild(o),document.body.appendChild(a),t=a,n=f,a.showModal(),document.body.style.overflow=`hidden`,a.addEventListener(`cancel`,e=>{e.preventDefault(),c(),i?.(),u(`recur:close`,{})}),window.addEventListener(`message`,l)}function c(){t&&(t.close(),t.remove(),t=null),n=null,document.body.style.overflow=``,window.removeEventListener(`message`,l)}function l(e){if(!e.origin.includes(`recur.tw`)&&!e.origin.includes(`localhost`))return;let{type:t,data:r}=e.data||{};switch(t){case`RECUR_PAYMENT_SUCCESS`:c(),u(`recur:success`,r);break;case`RECUR_PAYMENT_ERROR`:u(`recur:error`,r);break;case`RECUR_PAYMENT_CANCEL`:c(),u(`recur:close`,{});break;case`RECUR_RESIZE`:n&&r?.height&&(n.style.height=`${Math.min(r.height+56,window.innerHeight*.9)}px`);break}}function u(e,t){window.dispatchEvent(new CustomEvent(e,{detail:t}))}async function d(t,n){t.preventDefault();let r=n.getAttribute(`href`);if(!r){console.error(`[Recur] Missing href on recur-button`);return}let c=i(r);if(!c){console.error(`[Recur] Invalid product URL:`,r);return}let l=a(n);if(!l){console.error(`[Recur] Missing publishable key. Add data-key attribute or set RecurConfig.publishableKey`);return}let d={productId:c.productId||``,email:n.getAttribute(`data-email`)||void 0,name:n.getAttribute(`data-name`)||void 0,successUrl:n.getAttribute(`data-success-url`)||void 0,theme:n.getAttribute(`data-theme`)||void 0};n.setAttribute(`data-loading`,`true`);let f=n.textContent;n.textContent=`...`;try{let t=await o(d,l),n=new URL(`${e.embedUrl}/checkout`);n.searchParams.set(`id`,t.id),n.searchParams.set(`key`,t.publishableKey),n.searchParams.set(`client_secret`,t.clientSecret),d.theme&&n.searchParams.set(`theme`,d.theme),s(n.toString(),()=>{d.successUrl})}catch(e){console.error(`[Recur] Checkout error:`,e),u(`recur:error`,{message:e instanceof Error?e.message:`Unknown error`})}finally{n.setAttribute(`data-loading`,`false`),n.textContent=f}}function f(){document.querySelectorAll(`.recur-button`).forEach(e=>{e.hasAttribute(`data-recur-initialized`)||(e.setAttribute(`data-recur-initialized`,`true`),e.addEventListener(`click`,t=>d(t,e)))})}function p(){let t=window.location.hostname;(t===`localhost`||t===`127.0.0.1`||t.includes(`.local`))&&(e.embedUrl=`${window.location.protocol}//${t}:3002`,e.apiUrl=`${window.location.protocol}//${t}:3000/api`)}let m={config(t){Object.assign(e,t)},async popup(t){let n=await o(t,t.key),r=new URL(`${e.embedUrl}/checkout`);r.searchParams.set(`id`,n.id),r.searchParams.set(`key`,n.publishableKey),r.searchParams.set(`client_secret`,n.clientSecret),t.theme&&r.searchParams.set(`theme`,t.theme),s(r.toString())},close(){c()},init(){f()}};function h(){p(),r(),document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,f):f(),new MutationObserver(e=>{for(let t of e)t.addedNodes.length>0&&f()}).observe(document.body,{childList:!0,subtree:!0})}return window.Recur=m,h(),m})();