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 +55 -0
- package/dist/checkout.js +2 -3
- package/dist/index.cjs +3048 -2807
- package/dist/index.d.cts +1131 -1028
- package/dist/index.d.ts +1131 -1028
- package/dist/index.js +3005 -2783
- package/dist/recur.umd.js +127 -136
- package/dist/server.cjs +558 -410
- package/dist/server.d.cts +487 -454
- package/dist/server.d.ts +487 -454
- package/dist/server.js +532 -407
- package/dist/widget.js +8 -9
- package/package.json +15 -15
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
|
-
|
|
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
|
|
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})();
|