recur-tw 0.0.2 → 0.0.3-beta.3
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 +88 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +148 -18
- package/dist/index.d.ts +148 -18
- package/dist/index.js +88 -55
- package/dist/index.js.map +1 -1
- package/dist/recur.umd.js +209 -5
- package/dist/recur.umd.js.map +1 -1
- package/package.json +12 -11
package/dist/recur.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var RecurCheckout=(()=>{var
|
|
1
|
+
"use strict";var RecurCheckout=(()=>{var p=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var x=(s,e,t)=>e in s?p(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t;var k=(s,e)=>{for(var t in e)p(s,t,{get:e[t],enumerable:!0})},E=(s,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of v(e))!w.call(s,i)&&i!==t&&p(s,i,{get:()=>e[i],enumerable:!(o=y(e,i))||o.enumerable});return s};var C=s=>E(p({},"__esModule",{value:!0}),s);var r=(s,e,t)=>x(s,typeof e!="symbol"?e+"":e,t);var T={};k(T,{RecurCheckout:()=>c,default:()=>M,init:()=>g});var u=class{constructor(e){r(this,"config");if(!e.publishableKey)throw new Error("publishableKey is required");this.config={baseUrl:"https://api.recur.tw",...e}}async createSubscription(e){let{planId:t,customerName:o,customerEmail:i,customerPhone:n}=e;if(!t)throw new Error("planId is required");let a=await fetch(`${this.config.baseUrl}/v1/subscriptions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({planId:t,customerName:o,customerEmail:i,customerPhone:n})});if(!a.ok){let d=await a.json().catch(()=>({}));throw{code:d.error||"CHECKOUT_FAILED",message:d.message||"Failed to initiate checkout",details:d}}return await a.json()}async fetchPlans(){let e=await fetch(`${this.config.baseUrl}/v1/plans`,{method:"GET",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey}});if(!e.ok){let t=await e.json().catch(()=>({}));throw{code:t.error||"FETCH_PLANS_FAILED",message:t.message||"Failed to fetch plans",details:t}}return await e.json()}getConfig(){return{...this.config}}};var h=class{constructor(e){r(this,"overlay",null);r(this,"modal",null);r(this,"iframe",null);r(this,"onClose");r(this,"handleMessage",e=>{e.origin===window.location.origin&&(e.data?.type==="RECUR_PAYMENT_COMPLETE"?this.close():e.data?.type==="RECUR_PAYMENT_CANCEL"&&this.close())});this.onClose=e}open(e){this.overlay=document.createElement("div"),this.overlay.style.cssText=`
|
|
2
2
|
position: fixed;
|
|
3
3
|
top: 0;
|
|
4
4
|
left: 0;
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
height: 100%;
|
|
44
44
|
border: none;
|
|
45
45
|
border-radius: 8px;
|
|
46
|
-
`,this.iframe.setAttribute("allow","payment"),this.iframe.setAttribute("sandbox","allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"),this.modal.appendChild(t),this.modal.appendChild(this.iframe),this.overlay.appendChild(this.modal),document.body.appendChild(this.overlay),document.body.style.overflow="hidden",this.overlay.onclick=
|
|
46
|
+
`,this.iframe.setAttribute("allow","payment"),this.iframe.setAttribute("sandbox","allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"),this.modal.appendChild(t),this.modal.appendChild(this.iframe),this.overlay.appendChild(this.modal),document.body.appendChild(this.overlay),document.body.style.overflow="hidden",this.overlay.onclick=o=>{o.target===this.overlay&&this.close()},window.addEventListener("message",this.handleMessage)}close(){this.overlay&&this.overlay.parentNode&&this.overlay.parentNode.removeChild(this.overlay),document.body.style.overflow="",this.overlay=null,this.modal=null,this.iframe=null,window.removeEventListener("message",this.handleMessage),this.onClose&&this.onClose()}};var m=class{constructor(e,t){r(this,"container");r(this,"iframe",null);r(this,"onClose");r(this,"handleMessage",e=>{e.origin===window.location.origin&&(e.data?.type==="RECUR_PAYMENT_COMPLETE"?this.remove():e.data?.type==="RECUR_PAYMENT_CANCEL"&&this.remove())});if(typeof e=="string"){let o=document.querySelector(e);if(!o)throw new Error(`Container element not found: ${e}`);this.container=o}else this.container=e;this.onClose=t}embed(e){this.container.innerHTML="",this.iframe=document.createElement("iframe"),this.iframe.src=e,this.iframe.style.cssText=`
|
|
47
47
|
width: 100%;
|
|
48
48
|
height: 600px;
|
|
49
49
|
border: 1px solid #e5e7eb;
|
|
50
50
|
border-radius: 8px;
|
|
51
|
-
`,this.iframe.setAttribute("allow","payment"),this.iframe.setAttribute("sandbox","allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"),this.container.appendChild(this.iframe),window.addEventListener("message",this.handleMessage)}remove(){this.iframe&&this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,window.removeEventListener("message",this.handleMessage),this.onClose&&this.onClose()}};var
|
|
51
|
+
`,this.iframe.setAttribute("allow","payment"),this.iframe.setAttribute("sandbox","allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation"),this.container.appendChild(this.iframe),window.addEventListener("message",this.handleMessage)}remove(){this.iframe&&this.iframe.parentNode&&this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,window.removeEventListener("message",this.handleMessage),this.onClose&&this.onClose()}};var f=class{constructor(e,t){r(this,"config");r(this,"options");r(this,"container");r(this,"sdkToken",null);r(this,"sdkEnv","S");r(this,"payuniSDK",null);r(this,"isFormValid",!1);if(this.config=e,this.options=t,typeof t.container=="string"){let o=document.querySelector(t.container);if(!o)throw new Error(`Container not found: ${t.container}`);this.container=o}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)}}async initCheckout(){let e=()=>{if(typeof window<"u"){let n=window.location.hostname;if(n==="localhost"||n.includes(".test")||n.includes(".local")||n==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"},t=this.config.baseUrl||e(),o=await fetch(`${t}/v1/checkout/init`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({planId:this.options.planId,iframeDomain:window.location.origin})});if(!o.ok){let n=await o.json().catch(()=>({}));throw new Error(n.error||"Failed to initialize checkout")}let i=await o.json();this.sdkToken=i.sdkToken,this.sdkEnv=i.env}renderHTML(){this.container.innerHTML=`
|
|
52
52
|
<div class="recur-embedded-checkout" style="max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;">
|
|
53
53
|
<div class="recur-checkout-header" style="margin-bottom: 24px;">
|
|
54
54
|
<h2 style="margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;">Subscribe</h2>
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
</p>
|
|
131
131
|
</form>
|
|
132
132
|
</div>
|
|
133
|
-
`}async loadPayUniSDK(){return new Promise((e,t)=>{if(window.UniPayment){e();return}let
|
|
133
|
+
`}async loadPayUniSDK(){return new Promise((e,t)=>{if(window.UniPayment){e();return}let o=document.createElement("script");o.src=this.sdkEnv==="P"?"https://vendor.payuni.com.tw/sdk/uni-payment.js":"https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js",o.async=!0,o.onload=()=>e(),o.onerror=()=>t(new Error("Failed to load PAYUNi SDK")),document.head.appendChild(o)})}async initPayUniSDK(){if(!this.sdkToken)throw new Error("SDK token not initialized");let e=window.UniPayment;if(!e)throw new Error("PAYUNi SDK not loaded");let t={env:this.sdkEnv,useInst:!1,elements:{CardNo:"recur-card-number",CardExp:"recur-card-exp",CardCvc:"recur-card-cvc"},style:{color:"#111827",errorColor:"#dc2626",fontSize:"14px",fontWeight:"400",lineHeight:"24px"}};this.payuniSDK=e.createSession(this.sdkToken,t),await this.payuniSDK.start(),this.payuniSDK.onUpdate(o=>{let i=o?.status;i&&(this.isFormValid=i.CardNo===!0&&i.CardExp===!0&&i.CardCvc===!0,this.updateSubmitButton())})}updateSubmitButton(){let e=document.getElementById("recur-submit-btn");e&&(e.disabled=!this.isFormValid,e.style.opacity=this.isFormValid?"1":"0.5",e.style.cursor=this.isFormValid?"pointer":"not-allowed")}setupFormSubmit(){let e=document.getElementById("recur-checkout-form");e&&e.addEventListener("submit",async t=>{t.preventDefault(),await this.handleSubmit()})}async handleSubmit(){try{this.showLoading(!0),this.hideError();let e=document.getElementById("recur-email").value,t=document.getElementById("recur-name").value,o=document.getElementById("recur-phone").value;if(!e||!t)throw new Error("Please fill in all required fields");if(!this.payuniSDK)throw new Error("Payment system not initialized");let i=await this.payuniSDK.getTradeResult();if(i.Status!=="SUCCESS")throw new Error(i.Message||"Card validation failed");let n=()=>{if(typeof window<"u"){let l=window.location.hostname;if(l==="localhost"||l.includes(".test")||l.includes(".local")||l==="127.0.0.1")return`${window.location.protocol}//${window.location.host}/api`}return"https://api.recur.tw"},a=this.config.baseUrl||n(),d=await fetch(`${a}/v1/subscriptions`,{method:"POST",headers:{"Content-Type":"application/json","X-Recur-Publishable-Key":this.config.publishableKey},body:JSON.stringify({planId:this.options.planId,customerEmail:e,customerName:t,customerPhone:o||void 0})});if(!d.ok){let l=await d.json().catch(()=>({}));throw new Error(l.error||"Failed to create subscription")}let b=await d.json();this.options.onSuccess&&this.options.onSuccess(b),this.showSuccess()}catch(e){this.handleError(e)}finally{this.showLoading(!1)}}showLoading(e){let t=document.getElementById("recur-submit-btn");t&&(t.disabled=e,t.textContent=e?"Processing...":"Subscribe Now")}showError(e){let t=document.getElementById("recur-error");t&&(t.textContent=e,t.style.display="block")}hideError(){let e=document.getElementById("recur-error");e&&(e.style.display="none")}showSuccess(){this.container.innerHTML=`
|
|
134
134
|
<div style="max-width: 400px; margin: 0 auto; padding: 40px 20px; text-align: center; font-family: system-ui, -apple-system, sans-serif;">
|
|
135
135
|
<div style="width: 64px; height: 64px; margin: 0 auto 24px; background: #10b981; border-radius: 50%; display: flex; align-items: center; justify-content: center;">
|
|
136
136
|
<svg width="32" height="32" fill="none" stroke="white" stroke-width="3" viewBox="0 0 24 24">
|
|
@@ -140,6 +140,210 @@
|
|
|
140
140
|
<h2 style="margin: 0 0 8px 0; font-size: 24px; font-weight: 600; color: #111;">Subscription Complete!</h2>
|
|
141
141
|
<p style="margin: 0; font-size: 16px; color: #666;">Thank you for subscribing. You will receive a confirmation email shortly.</p>
|
|
142
142
|
</div>
|
|
143
|
-
`}handleError(e){let t=e?.message||"An error occurred";this.showError(t);let
|
|
143
|
+
`}handleError(e){let t=e?.message||"An error occurred";this.showError(t);let o={code:"CHECKOUT_ERROR",message:t};this.options.onError&&this.options.onError(o)}};var c=class{constructor(e){r(this,"core");r(this,"currentModal",null);r(this,"currentIframe",null);this.core=new u(e)}async fetchPlans(){return await this.core.fetchPlans()}async createEmbeddedCheckout(e){let t=this.core.getConfig();await new f(t,e).render()}async checkout(e){try{let t=await this.core.createSubscription(e);e.onSuccess&&e.onSuccess(t);let o=this.core.getConfig(),i=e.mode||"redirect";if(o.mockMode)console.log("[Recur SDK] \u{1F3AD} Mock mode: Showing mock payment UI"),this.showMockPaymentUI(i,t,e.container,e.onClose);else{let a=`${o.baseUrl||window.location.origin}/checkout/${t.subscription.id}`;switch(i){case"modal":this.openModal(a,e.onClose);break;case"iframe":this.embedIframe(a,e.container,e.onClose);break;case"redirect":default:window.location.href=a;break}}}catch(t){if(e.onError)e.onError(t);else throw console.error("Recur checkout error:",t),t}}openModal(e,t){this.closeModal(),this.currentModal=new h(()=>{this.currentModal=null,t&&t()}),this.currentModal.open(e)}embedIframe(e,t,o){if(!t)throw new Error("Container is required for iframe mode");this.removeIframe(),this.currentIframe=new m(t,()=>{this.currentIframe=null,o&&o()}),this.currentIframe.embed(e)}closeModal(){this.currentModal&&(this.currentModal.close(),this.currentModal=null)}removeIframe(){this.currentIframe&&(this.currentIframe.remove(),this.currentIframe=null)}close(){this.closeModal(),this.removeIframe()}showMockPaymentUI(e,t,o,i){let n=this.generateMockPaymentHTML(t);switch(e){case"modal":this.showMockModal(n,i);break;case"iframe":this.showMockIframe(n,o,i);break;case"redirect":default:let d=`${window.location.origin}/checkout/${t.subscription.id}`;console.log("[Recur SDK] \u{1F3AD} Mock mode: Would redirect to:",d),alert(`Mock \u6A21\u5F0F\uFF1A\u5728\u771F\u5BE6\u74B0\u5883\u4E2D\uFF0C\u7528\u6236\u6703\u88AB\u5C0E\u5411\u4ED8\u6B3E\u9801\u9762
|
|
144
|
+
\u4ED8\u6B3E\u7DB2\u5740: ${d}`);break}}generateMockPaymentHTML(e){return`
|
|
145
|
+
<!DOCTYPE html>
|
|
146
|
+
<html>
|
|
147
|
+
<head>
|
|
148
|
+
<meta charset="UTF-8">
|
|
149
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
150
|
+
<style>
|
|
151
|
+
body {
|
|
152
|
+
margin: 0;
|
|
153
|
+
padding: 20px;
|
|
154
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
155
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
156
|
+
min-height: 100vh;
|
|
157
|
+
display: flex;
|
|
158
|
+
align-items: center;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
}
|
|
161
|
+
.container {
|
|
162
|
+
background: white;
|
|
163
|
+
border-radius: 12px;
|
|
164
|
+
padding: 40px;
|
|
165
|
+
max-width: 500px;
|
|
166
|
+
width: 100%;
|
|
167
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
168
|
+
}
|
|
169
|
+
.badge {
|
|
170
|
+
display: inline-block;
|
|
171
|
+
padding: 4px 12px;
|
|
172
|
+
background: #fed7d7;
|
|
173
|
+
color: #c53030;
|
|
174
|
+
border-radius: 12px;
|
|
175
|
+
font-size: 12px;
|
|
176
|
+
font-weight: 600;
|
|
177
|
+
margin-bottom: 20px;
|
|
178
|
+
border: 2px solid #fc8181;
|
|
179
|
+
}
|
|
180
|
+
h1 {
|
|
181
|
+
color: #1a202c;
|
|
182
|
+
margin-bottom: 24px;
|
|
183
|
+
font-size: 24px;
|
|
184
|
+
}
|
|
185
|
+
.info {
|
|
186
|
+
background: #f7fafc;
|
|
187
|
+
border-radius: 8px;
|
|
188
|
+
padding: 16px;
|
|
189
|
+
margin-bottom: 24px;
|
|
190
|
+
}
|
|
191
|
+
.info-row {
|
|
192
|
+
display: flex;
|
|
193
|
+
justify-content: space-between;
|
|
194
|
+
padding: 8px 0;
|
|
195
|
+
border-bottom: 1px solid #e2e8f0;
|
|
196
|
+
}
|
|
197
|
+
.info-row:last-child {
|
|
198
|
+
border-bottom: none;
|
|
199
|
+
}
|
|
200
|
+
.label {
|
|
201
|
+
color: #718096;
|
|
202
|
+
font-size: 14px;
|
|
203
|
+
}
|
|
204
|
+
.value {
|
|
205
|
+
color: #2d3748;
|
|
206
|
+
font-weight: 500;
|
|
207
|
+
font-size: 14px;
|
|
208
|
+
}
|
|
209
|
+
.card-form {
|
|
210
|
+
margin-bottom: 24px;
|
|
211
|
+
}
|
|
212
|
+
.form-group {
|
|
213
|
+
margin-bottom: 16px;
|
|
214
|
+
}
|
|
215
|
+
label {
|
|
216
|
+
display: block;
|
|
217
|
+
margin-bottom: 8px;
|
|
218
|
+
color: #2d3748;
|
|
219
|
+
font-weight: 500;
|
|
220
|
+
font-size: 14px;
|
|
221
|
+
}
|
|
222
|
+
.mock-input {
|
|
223
|
+
width: 100%;
|
|
224
|
+
padding: 12px 16px;
|
|
225
|
+
border: 2px solid #e2e8f0;
|
|
226
|
+
border-radius: 8px;
|
|
227
|
+
background: #f7fafc;
|
|
228
|
+
color: #6b7280;
|
|
229
|
+
font-size: 14px;
|
|
230
|
+
}
|
|
231
|
+
button {
|
|
232
|
+
width: 100%;
|
|
233
|
+
padding: 14px;
|
|
234
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
235
|
+
color: white;
|
|
236
|
+
border: none;
|
|
237
|
+
border-radius: 8px;
|
|
238
|
+
font-size: 16px;
|
|
239
|
+
font-weight: 600;
|
|
240
|
+
cursor: pointer;
|
|
241
|
+
}
|
|
242
|
+
button:hover {
|
|
243
|
+
opacity: 0.9;
|
|
244
|
+
}
|
|
245
|
+
.note {
|
|
246
|
+
margin-top: 16px;
|
|
247
|
+
padding: 12px;
|
|
248
|
+
background: #fef5e7;
|
|
249
|
+
border-left: 4px solid #f59e0b;
|
|
250
|
+
border-radius: 4px;
|
|
251
|
+
font-size: 13px;
|
|
252
|
+
color: #78350f;
|
|
253
|
+
}
|
|
254
|
+
</style>
|
|
255
|
+
</head>
|
|
256
|
+
<body>
|
|
257
|
+
<div class="container">
|
|
258
|
+
<span class="badge">\u{1F3AD} MOCK MODE</span>
|
|
259
|
+
<h1>\u8A02\u95B1\u4ED8\u6B3E\uFF08\u6A21\u64EC\uFF09</h1>
|
|
260
|
+
|
|
261
|
+
<div class="info">
|
|
262
|
+
<div class="info-row">
|
|
263
|
+
<span class="label">\u65B9\u6848</span>
|
|
264
|
+
<span class="value">${e.subscription.planName}</span>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="info-row">
|
|
267
|
+
<span class="label">\u91D1\u984D</span>
|
|
268
|
+
<span class="value">NT$ ${e.subscription.amount}</span>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="info-row">
|
|
271
|
+
<span class="label">\u9031\u671F</span>
|
|
272
|
+
<span class="value">${e.subscription.billingPeriod}</span>
|
|
273
|
+
</div>
|
|
274
|
+
<div class="info-row">
|
|
275
|
+
<span class="label">\u8A66\u7528\u671F</span>
|
|
276
|
+
<span class="value">${e.subscription.trialDays} \u5929</span>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<div class="card-form">
|
|
281
|
+
<div class="form-group">
|
|
282
|
+
<label>\u4FE1\u7528\u5361\u865F</label>
|
|
283
|
+
<input type="text" class="mock-input" value="4242 4242 4242 4242 (Mock)" readonly>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="form-group">
|
|
286
|
+
<label>\u6709\u6548\u671F\u9650</label>
|
|
287
|
+
<input type="text" class="mock-input" value="12 / 25 (Mock)" readonly>
|
|
288
|
+
</div>
|
|
289
|
+
<div class="form-group">
|
|
290
|
+
<label>\u5B89\u5168\u78BC</label>
|
|
291
|
+
<input type="text" class="mock-input" value="123 (Mock)" readonly>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<button onclick="handleMockPayment()">\u78BA\u8A8D\u4ED8\u6B3E\uFF08\u6A21\u64EC\uFF09</button>
|
|
296
|
+
|
|
297
|
+
<div class="note">
|
|
298
|
+
<strong>\u{1F3AD} Mock \u6A21\u5F0F\u8AAA\u660E</strong><br>
|
|
299
|
+
\u9019\u662F\u6A21\u64EC\u7684\u4ED8\u6B3E\u8868\u55AE\u3002\u9EDE\u64CA\u300C\u78BA\u8A8D\u4ED8\u6B3E\u300D\u6703\u6A21\u64EC\u6210\u529F\u7684\u652F\u4ED8\u6D41\u7A0B\u3002
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<script>
|
|
304
|
+
function handleMockPayment() {
|
|
305
|
+
alert('\u{1F3AD} Mock \u6A21\u5F0F\uFF1A\u4ED8\u6B3E\u6210\u529F\uFF01\\n\\n\u5728\u771F\u5BE6\u74B0\u5883\u4E2D\uFF0C\u9019\u6703\u89F8\u767C\u5BE6\u969B\u7684\u652F\u4ED8\u8655\u7406\u3002');
|
|
306
|
+
// Close the modal/iframe
|
|
307
|
+
if (window.parent !== window) {
|
|
308
|
+
window.parent.postMessage({ type: 'recur-mock-payment-success' }, '*');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
</script>
|
|
312
|
+
</body>
|
|
313
|
+
</html>
|
|
314
|
+
`}showMockModal(e,t){this.closeModal();let o=document.createElement("div");o.id="recur-mock-modal",o.style.cssText=`
|
|
315
|
+
position: fixed;
|
|
316
|
+
top: 0;
|
|
317
|
+
left: 0;
|
|
318
|
+
right: 0;
|
|
319
|
+
bottom: 0;
|
|
320
|
+
background: rgba(0, 0, 0, 0.5);
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
justify-content: center;
|
|
324
|
+
z-index: 999999;
|
|
325
|
+
`;let i=document.createElement("div");i.style.cssText=`
|
|
326
|
+
width: 90%;
|
|
327
|
+
max-width: 600px;
|
|
328
|
+
height: 80%;
|
|
329
|
+
max-height: 700px;
|
|
330
|
+
background: white;
|
|
331
|
+
border-radius: 12px;
|
|
332
|
+
overflow: hidden;
|
|
333
|
+
position: relative;
|
|
334
|
+
`;let n=document.createElement("button");n.textContent="\u2715",n.style.cssText=`
|
|
335
|
+
position: absolute;
|
|
336
|
+
top: 16px;
|
|
337
|
+
right: 16px;
|
|
338
|
+
background: white;
|
|
339
|
+
border: none;
|
|
340
|
+
width: 32px;
|
|
341
|
+
height: 32px;
|
|
342
|
+
border-radius: 50%;
|
|
343
|
+
cursor: pointer;
|
|
344
|
+
font-size: 18px;
|
|
345
|
+
z-index: 1;
|
|
346
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
347
|
+
`,n.onclick=()=>{o.remove(),t&&t()};let a=document.createElement("iframe");a.style.cssText="width: 100%; height: 100%; border: none;",a.srcdoc=e,i.appendChild(n),i.appendChild(a),o.appendChild(i),document.body.appendChild(o),window.addEventListener("message",d=>{d.data.type==="recur-mock-payment-success"&&(o.remove(),t&&t())})}showMockIframe(e,t,o){if(!t)throw new Error("Container is required for iframe mode");let i=typeof t=="string"?document.getElementById(t)||document.querySelector(t):t;if(!i)throw new Error("Container element not found");i.innerHTML="";let n=document.createElement("iframe");n.style.cssText="width: 100%; height: 100%; border: none; min-height: 600px;",n.srcdoc=e,i.appendChild(n),window.addEventListener("message",a=>{a.data.type==="recur-mock-payment-success"&&o&&o()})}};function g(s){return new c(s)}var M={init:g,RecurCheckout:c};return C(T);})();
|
|
144
348
|
if (typeof window !== "undefined") { window.RecurCheckout = RecurCheckout.default; }
|
|
145
349
|
//# sourceMappingURL=recur.umd.js.map
|
package/dist/recur.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vanilla/index.ts","../src/vanilla/core.ts","../src/vanilla/modal.ts","../src/vanilla/iframe.ts","../src/vanilla/embedded-checkout.ts"],"sourcesContent":["/**\n * Recur Vanilla SDK\n *\n * Standalone JavaScript SDK for embedding subscription checkout\n * No dependencies, works with plain HTML/JavaScript\n */\n\nimport { RecurCore } from './core.js';\nimport { RecurModal } from './modal.js';\nimport { RecurIframe } from './iframe.js';\nimport { EmbeddedCheckout } from './embedded-checkout.js';\nimport type { VanillaRecurConfig, VanillaCheckoutOptions } from './types.js';\nimport type { EmbeddedCheckoutOptions } from './embedded-checkout.js';\n\nexport class RecurCheckout {\n private core: RecurCore;\n private currentModal: RecurModal | null = null;\n private currentIframe: RecurIframe | null = null;\n\n constructor(config: VanillaRecurConfig) {\n this.core = new RecurCore(config);\n }\n\n /**\n * Fetch available subscription plans\n */\n async fetchPlans() {\n return await this.core.fetchPlans();\n }\n\n /**\n * Create embedded checkout form with credit card fields\n * Renders a complete checkout UI in the specified container\n */\n async createEmbeddedCheckout(options: EmbeddedCheckoutOptions): Promise<void> {\n const config = this.core.getConfig();\n const embeddedCheckout = new EmbeddedCheckout(config, options);\n await embeddedCheckout.render();\n }\n\n /**\n * Initiate checkout flow\n */\n async checkout(options: VanillaCheckoutOptions): Promise<void> {\n try {\n // Create subscription\n const result = await this.core.createSubscription(options);\n\n // Call success callback\n if (options.onSuccess) {\n options.onSuccess(result);\n }\n\n // Handle payment URL based on mode\n const mode = options.mode || 'redirect';\n\n switch (mode) {\n case 'modal':\n this.openModal(result.paymentUrl, options.onClose);\n break;\n\n case 'iframe':\n this.embedIframe(result.paymentUrl, options.container, options.onClose);\n break;\n\n case 'redirect':\n default:\n window.location.href = result.paymentUrl;\n break;\n }\n } catch (error) {\n // Call error callback\n if (options.onError) {\n options.onError(error as any);\n } else {\n // Default error handling\n console.error('Recur checkout error:', error);\n throw error;\n }\n }\n }\n\n /**\n * Open payment in modal\n */\n private openModal(paymentUrl: string, onClose?: () => void): void {\n // Close existing modal if any\n this.closeModal();\n\n this.currentModal = new RecurModal(() => {\n this.currentModal = null;\n if (onClose) {\n onClose();\n }\n });\n\n this.currentModal.open(paymentUrl);\n }\n\n /**\n * Embed payment in iframe\n */\n private embedIframe(\n paymentUrl: string,\n container?: string | HTMLElement,\n onClose?: () => void\n ): void {\n if (!container) {\n throw new Error('Container is required for iframe mode');\n }\n\n // Remove existing iframe if any\n this.removeIframe();\n\n this.currentIframe = new RecurIframe(container, () => {\n this.currentIframe = null;\n if (onClose) {\n onClose();\n }\n });\n\n this.currentIframe.embed(paymentUrl);\n }\n\n /**\n * Close modal manually\n */\n closeModal(): void {\n if (this.currentModal) {\n this.currentModal.close();\n this.currentModal = null;\n }\n }\n\n /**\n * Remove iframe manually\n */\n removeIframe(): void {\n if (this.currentIframe) {\n this.currentIframe.remove();\n this.currentIframe = null;\n }\n }\n\n /**\n * Close any active checkout UI (modal or iframe)\n */\n close(): void {\n this.closeModal();\n this.removeIframe();\n }\n}\n\n// Global API for browser usage\nexport function init(config: VanillaRecurConfig): RecurCheckout {\n return new RecurCheckout(config);\n}\n\n// Default export\nexport default {\n init,\n RecurCheckout,\n};\n\n// Expose types for TypeScript users\nexport type * from './types.js';\n","/**\n * Vanilla SDK Core\n *\n * Core API logic for subscription checkout\n */\n\nimport type { VanillaRecurConfig, VanillaCheckoutOptions, CheckoutResult, CheckoutError, PlansResult } from './types.js';\n\nexport class RecurCore {\n private config: VanillaRecurConfig;\n\n constructor(config: VanillaRecurConfig) {\n if (!config.publishableKey) {\n throw new Error('publishableKey is required');\n }\n\n this.config = {\n baseUrl: typeof window !== 'undefined' ? window.location.origin : '',\n ...config,\n };\n }\n\n /**\n * Call the subscription API\n */\n async createSubscription(options: VanillaCheckoutOptions): Promise<CheckoutResult> {\n const { planId, customerName, customerEmail, customerPhone } = options;\n\n if (!planId) {\n throw new Error('planId is required');\n }\n\n const response = await fetch(`${this.config.baseUrl}/api/v1/subscriptions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Recur-Publishable-Key': this.config.publishableKey,\n },\n body: JSON.stringify({\n planId,\n customerName,\n customerEmail,\n customerPhone,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n const error: CheckoutError = {\n code: errorData.error || 'CHECKOUT_FAILED',\n message: errorData.message || 'Failed to initiate checkout',\n details: errorData,\n };\n throw error;\n }\n\n return await response.json();\n }\n\n /**\n * Fetch available subscription plans\n */\n async fetchPlans(): Promise<PlansResult> {\n const response = await fetch(`${this.config.baseUrl}/api/v1/plans`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Recur-Publishable-Key': this.config.publishableKey,\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n const error: CheckoutError = {\n code: errorData.error || 'FETCH_PLANS_FAILED',\n message: errorData.message || 'Failed to fetch plans',\n details: errorData,\n };\n throw error;\n }\n\n return await response.json();\n }\n\n getConfig(): VanillaRecurConfig {\n return { ...this.config };\n }\n}\n","/**\n * Vanilla SDK Modal\n *\n * Modal window for payment flow\n */\n\nexport class RecurModal {\n private overlay: HTMLDivElement | null = null;\n private modal: HTMLDivElement | null = null;\n private iframe: HTMLIFrameElement | null = null;\n private onClose?: () => void;\n\n constructor(onClose?: () => void) {\n this.onClose = onClose;\n }\n\n /**\n * Open modal with payment URL\n */\n open(paymentUrl: string): void {\n // Create overlay\n this.overlay = document.createElement('div');\n this.overlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9998;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n `;\n\n // Create modal container\n this.modal = document.createElement('div');\n this.modal.style.cssText = `\n position: relative;\n background: white;\n border-radius: 8px;\n width: 100%;\n max-width: 600px;\n height: 90vh;\n max-height: 700px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n z-index: 9999;\n `;\n\n // Create close button\n const closeButton = document.createElement('button');\n closeButton.innerHTML = '✕';\n closeButton.style.cssText = `\n position: absolute;\n top: 12px;\n right: 12px;\n width: 32px;\n height: 32px;\n border: none;\n background: rgba(0, 0, 0, 0.1);\n border-radius: 50%;\n cursor: pointer;\n font-size: 18px;\n line-height: 1;\n z-index: 10000;\n transition: background 0.2s;\n color: #333;\n `;\n closeButton.onmouseover = () => {\n closeButton.style.background = 'rgba(0, 0, 0, 0.2)';\n };\n closeButton.onmouseout = () => {\n closeButton.style.background = 'rgba(0, 0, 0, 0.1)';\n };\n closeButton.onclick = () => this.close();\n\n // Create iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = paymentUrl;\n this.iframe.style.cssText = `\n width: 100%;\n height: 100%;\n border: none;\n border-radius: 8px;\n `;\n\n // Allow payment provider features\n this.iframe.setAttribute('allow', 'payment');\n this.iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation');\n\n // Assemble\n this.modal.appendChild(closeButton);\n this.modal.appendChild(this.iframe);\n this.overlay.appendChild(this.modal);\n document.body.appendChild(this.overlay);\n\n // Prevent body scroll\n document.body.style.overflow = 'hidden';\n\n // Close on overlay click\n this.overlay.onclick = (e) => {\n if (e.target === this.overlay) {\n this.close();\n }\n };\n\n // Listen for payment completion (via postMessage)\n window.addEventListener('message', this.handleMessage);\n }\n\n /**\n * Close modal\n */\n close(): void {\n if (this.overlay && this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay);\n }\n\n // Restore body scroll\n document.body.style.overflow = '';\n\n // Clean up\n this.overlay = null;\n this.modal = null;\n this.iframe = null;\n\n // Remove message listener\n window.removeEventListener('message', this.handleMessage);\n\n // Call onClose callback\n if (this.onClose) {\n this.onClose();\n }\n }\n\n /**\n * Handle postMessage from iframe\n */\n private handleMessage = (event: MessageEvent): void => {\n // Only accept messages from same origin\n if (event.origin !== window.location.origin) {\n return;\n }\n\n // Check for payment completion signals\n if (event.data?.type === 'RECUR_PAYMENT_COMPLETE') {\n this.close();\n } else if (event.data?.type === 'RECUR_PAYMENT_CANCEL') {\n this.close();\n }\n };\n}\n","/**\n * Vanilla SDK iframe Embedding\n *\n * Embed payment flow in an iframe\n */\n\nexport class RecurIframe {\n private container: HTMLElement;\n private iframe: HTMLIFrameElement | null = null;\n private onClose?: () => void;\n\n constructor(container: string | HTMLElement, onClose?: () => void) {\n if (typeof container === 'string') {\n const element = document.querySelector<HTMLElement>(container);\n if (!element) {\n throw new Error(`Container element not found: ${container}`);\n }\n this.container = element;\n } else {\n this.container = container;\n }\n\n this.onClose = onClose;\n }\n\n /**\n * Embed iframe with payment URL\n */\n embed(paymentUrl: string): void {\n // Clear existing content\n this.container.innerHTML = '';\n\n // Create iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = paymentUrl;\n this.iframe.style.cssText = `\n width: 100%;\n height: 600px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n `;\n\n // Allow payment provider features\n this.iframe.setAttribute('allow', 'payment');\n this.iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation');\n\n // Append to container\n this.container.appendChild(this.iframe);\n\n // Listen for payment completion\n window.addEventListener('message', this.handleMessage);\n }\n\n /**\n * Remove iframe\n */\n remove(): void {\n if (this.iframe && this.iframe.parentNode) {\n this.iframe.parentNode.removeChild(this.iframe);\n }\n\n this.iframe = null;\n\n // Remove message listener\n window.removeEventListener('message', this.handleMessage);\n\n // Call onClose callback\n if (this.onClose) {\n this.onClose();\n }\n }\n\n /**\n * Handle postMessage from iframe\n */\n private handleMessage = (event: MessageEvent): void => {\n // Only accept messages from same origin\n if (event.origin !== window.location.origin) {\n return;\n }\n\n // Check for payment completion signals\n if (event.data?.type === 'RECUR_PAYMENT_COMPLETE') {\n this.remove();\n } else if (event.data?.type === 'RECUR_PAYMENT_CANCEL') {\n this.remove();\n }\n };\n}\n","/**\n * Embedded Checkout\n *\n * Renders a complete checkout form with embedded credit card fields\n * Powered by PAYUNi SDK for secure card input\n */\n\nimport type { VanillaRecurConfig, CheckoutError, CheckoutResult } from './types.js';\n\nexport interface EmbeddedCheckoutOptions {\n planId: string;\n container: string | HTMLElement;\n customerEmail?: string;\n customerName?: string;\n customerPhone?: string;\n onSuccess?: (result: CheckoutResult) => void;\n onError?: (error: CheckoutError) => void;\n}\n\ninterface InitCheckoutResponse {\n sdkToken: string;\n env: 'S' | 'P';\n timestamp: number;\n plan?: {\n id: string;\n name: string;\n price: number;\n billingPeriod: string;\n description?: string;\n trialDays?: number;\n };\n}\n\nexport class EmbeddedCheckout {\n private config: VanillaRecurConfig;\n private options: EmbeddedCheckoutOptions;\n private container: HTMLElement;\n private sdkToken: string | null = null;\n private sdkEnv: 'S' | 'P' = 'S';\n private payuniSDK: any = null;\n private isFormValid: boolean = false;\n\n constructor(config: VanillaRecurConfig, options: EmbeddedCheckoutOptions) {\n this.config = config;\n this.options = options;\n\n // Resolve container\n if (typeof options.container === 'string') {\n const element = document.querySelector(options.container);\n if (!element) {\n throw new Error(`Container not found: ${options.container}`);\n }\n this.container = element as HTMLElement;\n } else {\n this.container = options.container;\n }\n }\n\n /**\n * Initialize and render the checkout form\n */\n async render(): Promise<void> {\n try {\n // 1. Fetch SDK token and plan info\n await this.initCheckout();\n\n // 2. Render HTML structure\n this.renderHTML();\n\n // 3. Load PAYUNi SDK script\n await this.loadPayUniSDK();\n\n // 4. Initialize PAYUNi SDK with card fields\n await this.initPayUniSDK();\n\n // 5. Setup form submission\n this.setupFormSubmit();\n } catch (error: any) {\n this.handleError(error);\n }\n }\n\n /**\n * Initialize checkout session\n */\n private async initCheckout(): Promise<void> {\n const baseUrl = this.config.baseUrl || (typeof window !== 'undefined' ? window.location.origin : '');\n\n const response = await fetch(`${baseUrl}/api/v1/checkout/init`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Recur-Publishable-Key': this.config.publishableKey,\n },\n body: JSON.stringify({\n planId: this.options.planId,\n iframeDomain: window.location.origin,\n }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error || 'Failed to initialize checkout');\n }\n\n const data: InitCheckoutResponse = await response.json();\n this.sdkToken = data.sdkToken;\n this.sdkEnv = data.env;\n }\n\n /**\n * Render checkout form HTML\n */\n private renderHTML(): void {\n this.container.innerHTML = `\n <div class=\"recur-embedded-checkout\" style=\"max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;\">\n <div class=\"recur-checkout-header\" style=\"margin-bottom: 24px;\">\n <h2 style=\"margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;\">Subscribe</h2>\n <p style=\"margin: 0; font-size: 14px; color: #666;\">Complete your subscription</p>\n </div>\n\n <form id=\"recur-checkout-form\" style=\"display: flex; flex-direction: column; gap: 16px;\">\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Email</label>\n <input\n type=\"email\"\n id=\"recur-email\"\n required\n value=\"${this.options.customerEmail || ''}\"\n style=\"width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; outline: none; transition: border-color 0.2s;\"\n onfocus=\"this.style.borderColor='#3b82f6'\"\n onblur=\"this.style.borderColor='#d1d5db'\"\n />\n </div>\n\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Name</label>\n <input\n type=\"text\"\n id=\"recur-name\"\n required\n value=\"${this.options.customerName || ''}\"\n style=\"width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; outline: none; transition: border-color 0.2s;\"\n onfocus=\"this.style.borderColor='#3b82f6'\"\n onblur=\"this.style.borderColor='#d1d5db'\"\n />\n </div>\n\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Phone (Optional)</label>\n <input\n type=\"tel\"\n id=\"recur-phone\"\n value=\"${this.options.customerPhone || ''}\"\n style=\"width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; outline: none; transition: border-color 0.2s;\"\n onfocus=\"this.style.borderColor='#3b82f6'\"\n onblur=\"this.style.borderColor='#d1d5db'\"\n />\n </div>\n\n <div class=\"recur-divider\" style=\"height: 1px; background: #e5e7eb; margin: 8px 0;\"></div>\n\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Card Number</label>\n <div id=\"recur-card-number\" style=\"padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; min-height: 38px;\"></div>\n </div>\n\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Expiry</label>\n <div id=\"recur-card-exp\" style=\"padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; min-height: 38px;\"></div>\n </div>\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">CVC</label>\n <div id=\"recur-card-cvc\" style=\"padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; min-height: 38px;\"></div>\n </div>\n </div>\n\n <div id=\"recur-error\" style=\"display: none; padding: 12px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 6px; color: #dc2626; font-size: 14px;\"></div>\n\n <button\n type=\"submit\"\n id=\"recur-submit-btn\"\n style=\"width: 100%; padding: 12px; background: #3b82f6; color: white; border: none; border-radius: 6px; font-size: 16px; font-weight: 500; cursor: pointer; transition: background 0.2s;\"\n onmouseover=\"this.style.background='#2563eb'\"\n onmouseout=\"this.style.background='#3b82f6'\"\n disabled\n >\n Subscribe Now\n </button>\n\n <p style=\"margin: 12px 0 0 0; text-align: center; font-size: 12px; color: #9ca3af;\">\n Secure payment powered by PAYUNi\n </p>\n </form>\n </div>\n `;\n }\n\n /**\n * Load PAYUNi SDK script dynamically\n */\n private async loadPayUniSDK(): Promise<void> {\n return new Promise((resolve, reject) => {\n // Check if already loaded\n if ((window as any).UniPayment) {\n resolve();\n return;\n }\n\n const script = document.createElement('script');\n script.src = this.sdkEnv === 'P'\n ? 'https://vendor.payuni.com.tw/sdk/uni-payment.js'\n : 'https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js';\n script.async = true;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('Failed to load PAYUNi SDK'));\n document.head.appendChild(script);\n });\n }\n\n /**\n * Initialize PAYUNi SDK with credit card fields\n */\n private async initPayUniSDK(): Promise<void> {\n if (!this.sdkToken) {\n throw new Error('SDK token not initialized');\n }\n\n const UniPayment = (window as any).UniPayment;\n if (!UniPayment) {\n throw new Error('PAYUNi SDK not loaded');\n }\n\n const options = {\n env: this.sdkEnv,\n useInst: false,\n elements: {\n CardNo: 'recur-card-number',\n CardExp: 'recur-card-exp',\n CardCvc: 'recur-card-cvc',\n },\n style: {\n color: '#111827',\n errorColor: '#dc2626',\n fontSize: '14px',\n fontWeight: '400',\n lineHeight: '24px',\n },\n };\n\n this.payuniSDK = UniPayment.createSession(this.sdkToken, options);\n await this.payuniSDK.start();\n\n // Listen for card validation updates\n this.payuniSDK.onUpdate((update: any) => {\n const status = update?.status;\n if (status) {\n this.isFormValid = status.CardNo === true && status.CardExp === true && status.CardCvc === true;\n this.updateSubmitButton();\n }\n });\n }\n\n /**\n * Update submit button state\n */\n private updateSubmitButton(): void {\n const submitBtn = document.getElementById('recur-submit-btn') as HTMLButtonElement;\n if (submitBtn) {\n submitBtn.disabled = !this.isFormValid;\n submitBtn.style.opacity = this.isFormValid ? '1' : '0.5';\n submitBtn.style.cursor = this.isFormValid ? 'pointer' : 'not-allowed';\n }\n }\n\n /**\n * Setup form submission handler\n */\n private setupFormSubmit(): void {\n const form = document.getElementById('recur-checkout-form') as HTMLFormElement;\n if (!form) return;\n\n form.addEventListener('submit', async (e) => {\n e.preventDefault();\n await this.handleSubmit();\n });\n }\n\n /**\n * Handle form submission\n */\n private async handleSubmit(): Promise<void> {\n try {\n this.showLoading(true);\n this.hideError();\n\n // Get form data\n const email = (document.getElementById('recur-email') as HTMLInputElement).value;\n const name = (document.getElementById('recur-name') as HTMLInputElement).value;\n const phone = (document.getElementById('recur-phone') as HTMLInputElement).value;\n\n // Validate\n if (!email || !name) {\n throw new Error('Please fill in all required fields');\n }\n\n // Bind card data to SDK token\n if (!this.payuniSDK) {\n throw new Error('Payment system not initialized');\n }\n\n const cardResult = await this.payuniSDK.getTradeResult();\n if (cardResult.Status !== 'SUCCESS') {\n throw new Error(cardResult.Message || 'Card validation failed');\n }\n\n // Create subscription\n const baseUrl = this.config.baseUrl || window.location.origin;\n const response = await fetch(`${baseUrl}/api/v1/subscriptions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Recur-Publishable-Key': this.config.publishableKey,\n },\n body: JSON.stringify({\n planId: this.options.planId,\n customerEmail: email,\n customerName: name,\n customerPhone: phone || undefined,\n }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error || 'Failed to create subscription');\n }\n\n const result: CheckoutResult = await response.json();\n\n // Call success callback\n if (this.options.onSuccess) {\n this.options.onSuccess(result);\n }\n\n // Show success message\n this.showSuccess();\n } catch (error: any) {\n this.handleError(error);\n } finally {\n this.showLoading(false);\n }\n }\n\n /**\n * Show loading state\n */\n private showLoading(loading: boolean): void {\n const submitBtn = document.getElementById('recur-submit-btn') as HTMLButtonElement;\n if (submitBtn) {\n submitBtn.disabled = loading;\n submitBtn.textContent = loading ? 'Processing...' : 'Subscribe Now';\n }\n }\n\n /**\n * Show error message\n */\n private showError(message: string): void {\n const errorDiv = document.getElementById('recur-error');\n if (errorDiv) {\n errorDiv.textContent = message;\n errorDiv.style.display = 'block';\n }\n }\n\n /**\n * Hide error message\n */\n private hideError(): void {\n const errorDiv = document.getElementById('recur-error');\n if (errorDiv) {\n errorDiv.style.display = 'none';\n }\n }\n\n /**\n * Show success message\n */\n private showSuccess(): void {\n this.container.innerHTML = `\n <div style=\"max-width: 400px; margin: 0 auto; padding: 40px 20px; text-align: center; font-family: system-ui, -apple-system, sans-serif;\">\n <div style=\"width: 64px; height: 64px; margin: 0 auto 24px; background: #10b981; border-radius: 50%; display: flex; align-items: center; justify-content: center;\">\n <svg width=\"32\" height=\"32\" fill=\"none\" stroke=\"white\" stroke-width=\"3\" viewBox=\"0 0 24 24\">\n <path d=\"M5 13l4 4L19 7\"/>\n </svg>\n </div>\n <h2 style=\"margin: 0 0 8px 0; font-size: 24px; font-weight: 600; color: #111;\">Subscription Complete!</h2>\n <p style=\"margin: 0; font-size: 16px; color: #666;\">Thank you for subscribing. You will receive a confirmation email shortly.</p>\n </div>\n `;\n }\n\n /**\n * Handle errors\n */\n private handleError(error: any): void {\n const message = error?.message || 'An error occurred';\n this.showError(message);\n\n const checkoutError: CheckoutError = {\n code: 'CHECKOUT_ERROR',\n message,\n };\n\n if (this.options.onError) {\n this.options.onError(checkoutError);\n }\n }\n}\n"],"mappings":"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","names":["vanilla_exports","__export","RecurCheckout","vanilla_default","init","RecurCore","config","__publicField","options","planId","customerName","customerEmail","customerPhone","response","errorData","RecurModal","onClose","__publicField","event","paymentUrl","closeButton","e","RecurIframe","container","onClose","__publicField","event","element","paymentUrl","EmbeddedCheckout","config","options","__publicField","element","error","baseUrl","response","data","resolve","reject","script","UniPayment","update","status","submitBtn","form","e","email","name","phone","cardResult","result","loading","message","errorDiv","checkoutError","RecurCheckout","config","__publicField","RecurCore","options","EmbeddedCheckout","result","error","paymentUrl","onClose","RecurModal","container","RecurIframe","init","vanilla_default"]}
|
|
1
|
+
{"version":3,"sources":["../src/vanilla/index.ts","../src/vanilla/core.ts","../src/vanilla/modal.ts","../src/vanilla/iframe.ts","../src/vanilla/embedded-checkout.ts"],"sourcesContent":["/**\n * Recur Vanilla SDK\n *\n * Standalone JavaScript SDK for embedding subscription checkout\n * No dependencies, works with plain HTML/JavaScript\n */\n\nimport { RecurCore } from './core.js';\nimport { RecurModal } from './modal.js';\nimport { RecurIframe } from './iframe.js';\nimport { EmbeddedCheckout } from './embedded-checkout.js';\nimport type { VanillaRecurConfig, VanillaCheckoutOptions } from './types.js';\nimport type { EmbeddedCheckoutOptions } from './embedded-checkout.js';\n\nexport class RecurCheckout {\n private core: RecurCore;\n private currentModal: RecurModal | null = null;\n private currentIframe: RecurIframe | null = null;\n\n constructor(config: VanillaRecurConfig) {\n this.core = new RecurCore(config);\n }\n\n /**\n * Fetch available subscription plans\n */\n async fetchPlans() {\n return await this.core.fetchPlans();\n }\n\n /**\n * Create embedded checkout form with credit card fields\n * Renders a complete checkout UI in the specified container\n */\n async createEmbeddedCheckout(options: EmbeddedCheckoutOptions): Promise<void> {\n const config = this.core.getConfig();\n const embeddedCheckout = new EmbeddedCheckout(config, options);\n await embeddedCheckout.render();\n }\n\n /**\n * Initiate checkout flow\n */\n async checkout(options: VanillaCheckoutOptions): Promise<void> {\n try {\n // Create subscription\n const result = await this.core.createSubscription(options);\n\n // Call success callback\n if (options.onSuccess) {\n options.onSuccess(result);\n }\n\n // Handle payment based on mode\n const config = this.core.getConfig();\n const mode = options.mode || 'redirect';\n\n if (config.mockMode) {\n // Mock mode: show mock payment UI instead of loading real URL\n console.log('[Recur SDK] 🎭 Mock mode: Showing mock payment UI');\n this.showMockPaymentUI(mode, result, options.container, options.onClose);\n } else {\n // Real mode: generate payment URL and load it\n const baseUrl = config.baseUrl || window.location.origin;\n const paymentUrl = `${baseUrl}/checkout/${result.subscription.id}`;\n\n switch (mode) {\n case 'modal':\n this.openModal(paymentUrl, options.onClose);\n break;\n\n case 'iframe':\n this.embedIframe(paymentUrl, options.container, options.onClose);\n break;\n\n case 'redirect':\n default:\n window.location.href = paymentUrl;\n break;\n }\n }\n } catch (error) {\n // Call error callback\n if (options.onError) {\n options.onError(error as any);\n } else {\n // Default error handling\n console.error('Recur checkout error:', error);\n throw error;\n }\n }\n }\n\n /**\n * Open payment in modal\n */\n private openModal(paymentUrl: string, onClose?: () => void): void {\n // Close existing modal if any\n this.closeModal();\n\n this.currentModal = new RecurModal(() => {\n this.currentModal = null;\n if (onClose) {\n onClose();\n }\n });\n\n this.currentModal.open(paymentUrl);\n }\n\n /**\n * Embed payment in iframe\n */\n private embedIframe(\n paymentUrl: string,\n container?: string | HTMLElement,\n onClose?: () => void\n ): void {\n if (!container) {\n throw new Error('Container is required for iframe mode');\n }\n\n // Remove existing iframe if any\n this.removeIframe();\n\n this.currentIframe = new RecurIframe(container, () => {\n this.currentIframe = null;\n if (onClose) {\n onClose();\n }\n });\n\n this.currentIframe.embed(paymentUrl);\n }\n\n /**\n * Close modal manually\n */\n closeModal(): void {\n if (this.currentModal) {\n this.currentModal.close();\n this.currentModal = null;\n }\n }\n\n /**\n * Remove iframe manually\n */\n removeIframe(): void {\n if (this.currentIframe) {\n this.currentIframe.remove();\n this.currentIframe = null;\n }\n }\n\n /**\n * Close any active checkout UI (modal or iframe)\n */\n close(): void {\n this.closeModal();\n this.removeIframe();\n }\n\n /**\n * Show mock payment UI for testing\n */\n private showMockPaymentUI(\n mode: string,\n result: any,\n container?: string | HTMLElement,\n onClose?: () => void\n ): void {\n const mockHTML = this.generateMockPaymentHTML(result);\n\n switch (mode) {\n case 'modal':\n this.showMockModal(mockHTML, onClose);\n break;\n\n case 'iframe':\n this.showMockIframe(mockHTML, container, onClose);\n break;\n\n case 'redirect':\n default:\n const baseUrl = window.location.origin;\n const paymentUrl = `${baseUrl}/checkout/${result.subscription.id}`;\n console.log('[Recur SDK] 🎭 Mock mode: Would redirect to:', paymentUrl);\n alert(`Mock 模式:在真實環境中,用戶會被導向付款頁面\\n付款網址: ${paymentUrl}`);\n break;\n }\n }\n\n /**\n * Generate mock payment form HTML\n */\n private generateMockPaymentHTML(result: any): string {\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <style>\n body {\n margin: 0;\n padding: 20px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .container {\n background: white;\n border-radius: 12px;\n padding: 40px;\n max-width: 500px;\n width: 100%;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n }\n .badge {\n display: inline-block;\n padding: 4px 12px;\n background: #fed7d7;\n color: #c53030;\n border-radius: 12px;\n font-size: 12px;\n font-weight: 600;\n margin-bottom: 20px;\n border: 2px solid #fc8181;\n }\n h1 {\n color: #1a202c;\n margin-bottom: 24px;\n font-size: 24px;\n }\n .info {\n background: #f7fafc;\n border-radius: 8px;\n padding: 16px;\n margin-bottom: 24px;\n }\n .info-row {\n display: flex;\n justify-content: space-between;\n padding: 8px 0;\n border-bottom: 1px solid #e2e8f0;\n }\n .info-row:last-child {\n border-bottom: none;\n }\n .label {\n color: #718096;\n font-size: 14px;\n }\n .value {\n color: #2d3748;\n font-weight: 500;\n font-size: 14px;\n }\n .card-form {\n margin-bottom: 24px;\n }\n .form-group {\n margin-bottom: 16px;\n }\n label {\n display: block;\n margin-bottom: 8px;\n color: #2d3748;\n font-weight: 500;\n font-size: 14px;\n }\n .mock-input {\n width: 100%;\n padding: 12px 16px;\n border: 2px solid #e2e8f0;\n border-radius: 8px;\n background: #f7fafc;\n color: #6b7280;\n font-size: 14px;\n }\n button {\n width: 100%;\n padding: 14px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 16px;\n font-weight: 600;\n cursor: pointer;\n }\n button:hover {\n opacity: 0.9;\n }\n .note {\n margin-top: 16px;\n padding: 12px;\n background: #fef5e7;\n border-left: 4px solid #f59e0b;\n border-radius: 4px;\n font-size: 13px;\n color: #78350f;\n }\n </style>\n </head>\n <body>\n <div class=\"container\">\n <span class=\"badge\">🎭 MOCK MODE</span>\n <h1>訂閱付款(模擬)</h1>\n\n <div class=\"info\">\n <div class=\"info-row\">\n <span class=\"label\">方案</span>\n <span class=\"value\">${result.subscription.planName}</span>\n </div>\n <div class=\"info-row\">\n <span class=\"label\">金額</span>\n <span class=\"value\">NT$ ${result.subscription.amount}</span>\n </div>\n <div class=\"info-row\">\n <span class=\"label\">週期</span>\n <span class=\"value\">${result.subscription.billingPeriod}</span>\n </div>\n <div class=\"info-row\">\n <span class=\"label\">試用期</span>\n <span class=\"value\">${result.subscription.trialDays} 天</span>\n </div>\n </div>\n\n <div class=\"card-form\">\n <div class=\"form-group\">\n <label>信用卡號</label>\n <input type=\"text\" class=\"mock-input\" value=\"4242 4242 4242 4242 (Mock)\" readonly>\n </div>\n <div class=\"form-group\">\n <label>有效期限</label>\n <input type=\"text\" class=\"mock-input\" value=\"12 / 25 (Mock)\" readonly>\n </div>\n <div class=\"form-group\">\n <label>安全碼</label>\n <input type=\"text\" class=\"mock-input\" value=\"123 (Mock)\" readonly>\n </div>\n </div>\n\n <button onclick=\"handleMockPayment()\">確認付款(模擬)</button>\n\n <div class=\"note\">\n <strong>🎭 Mock 模式說明</strong><br>\n 這是模擬的付款表單。點擊「確認付款」會模擬成功的支付流程。\n </div>\n </div>\n\n <script>\n function handleMockPayment() {\n alert('🎭 Mock 模式:付款成功!\\\\n\\\\n在真實環境中,這會觸發實際的支付處理。');\n // Close the modal/iframe\n if (window.parent !== window) {\n window.parent.postMessage({ type: 'recur-mock-payment-success' }, '*');\n }\n }\n </script>\n </body>\n </html>\n `;\n }\n\n /**\n * Show mock payment in modal\n */\n private showMockModal(html: string, onClose?: () => void): void {\n this.closeModal();\n\n // Create modal overlay\n const overlay = document.createElement('div');\n overlay.id = 'recur-mock-modal';\n overlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n `;\n\n // Create iframe container\n const container = document.createElement('div');\n container.style.cssText = `\n width: 90%;\n max-width: 600px;\n height: 80%;\n max-height: 700px;\n background: white;\n border-radius: 12px;\n overflow: hidden;\n position: relative;\n `;\n\n // Create close button\n const closeBtn = document.createElement('button');\n closeBtn.textContent = '✕';\n closeBtn.style.cssText = `\n position: absolute;\n top: 16px;\n right: 16px;\n background: white;\n border: none;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n cursor: pointer;\n font-size: 18px;\n z-index: 1;\n box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n `;\n closeBtn.onclick = () => {\n overlay.remove();\n if (onClose) onClose();\n };\n\n // Create iframe\n const iframe = document.createElement('iframe');\n iframe.style.cssText = 'width: 100%; height: 100%; border: none;';\n iframe.srcdoc = html;\n\n container.appendChild(closeBtn);\n container.appendChild(iframe);\n overlay.appendChild(container);\n document.body.appendChild(overlay);\n\n // Listen for mock payment success\n window.addEventListener('message', (event) => {\n if (event.data.type === 'recur-mock-payment-success') {\n overlay.remove();\n if (onClose) onClose();\n }\n });\n }\n\n /**\n * Show mock payment in iframe\n */\n private showMockIframe(\n html: string,\n container?: string | HTMLElement,\n onClose?: () => void\n ): void {\n if (!container) {\n throw new Error('Container is required for iframe mode');\n }\n\n const containerEl = typeof container === 'string'\n ? document.getElementById(container) || document.querySelector(container)\n : container;\n\n if (!containerEl) {\n throw new Error('Container element not found');\n }\n\n // Clear container\n containerEl.innerHTML = '';\n\n // Create iframe\n const iframe = document.createElement('iframe');\n iframe.style.cssText = 'width: 100%; height: 100%; border: none; min-height: 600px;';\n iframe.srcdoc = html;\n\n containerEl.appendChild(iframe);\n\n // Listen for mock payment success\n window.addEventListener('message', (event) => {\n if (event.data.type === 'recur-mock-payment-success') {\n if (onClose) onClose();\n }\n });\n }\n}\n\n// Global API for browser usage\nexport function init(config: VanillaRecurConfig): RecurCheckout {\n return new RecurCheckout(config);\n}\n\n// Default export\nexport default {\n init,\n RecurCheckout,\n};\n\n// Expose types for TypeScript users\nexport type * from './types.js';\n","/**\n * Vanilla SDK Core\n *\n * Core API logic for subscription checkout\n */\n\nimport type { VanillaRecurConfig, VanillaCheckoutOptions, CheckoutResult, CheckoutError, PlansResult } from './types.js';\n\nexport class RecurCore {\n private config: VanillaRecurConfig;\n\n constructor(config: VanillaRecurConfig) {\n if (!config.publishableKey) {\n throw new Error('publishableKey is required');\n }\n\n // Use configured baseUrl or default to production API\n this.config = {\n baseUrl: 'https://api.recur.tw',\n ...config,\n };\n }\n\n /**\n * Call the subscription API\n */\n async createSubscription(options: VanillaCheckoutOptions): Promise<CheckoutResult> {\n const { planId, customerName, customerEmail, customerPhone } = options;\n\n if (!planId) {\n throw new Error('planId is required');\n }\n\n const response = await fetch(`${this.config.baseUrl}/v1/subscriptions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Recur-Publishable-Key': this.config.publishableKey,\n },\n body: JSON.stringify({\n planId,\n customerName,\n customerEmail,\n customerPhone,\n }),\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n const error: CheckoutError = {\n code: errorData.error || 'CHECKOUT_FAILED',\n message: errorData.message || 'Failed to initiate checkout',\n details: errorData,\n };\n throw error;\n }\n\n return await response.json();\n }\n\n /**\n * Fetch available subscription plans\n */\n async fetchPlans(): Promise<PlansResult> {\n const response = await fetch(`${this.config.baseUrl}/v1/plans`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Recur-Publishable-Key': this.config.publishableKey,\n },\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n const error: CheckoutError = {\n code: errorData.error || 'FETCH_PLANS_FAILED',\n message: errorData.message || 'Failed to fetch plans',\n details: errorData,\n };\n throw error;\n }\n\n return await response.json();\n }\n\n getConfig(): VanillaRecurConfig {\n return { ...this.config };\n }\n}\n","/**\n * Vanilla SDK Modal\n *\n * Modal window for payment flow\n */\n\nexport class RecurModal {\n private overlay: HTMLDivElement | null = null;\n private modal: HTMLDivElement | null = null;\n private iframe: HTMLIFrameElement | null = null;\n private onClose?: () => void;\n\n constructor(onClose?: () => void) {\n this.onClose = onClose;\n }\n\n /**\n * Open modal with payment URL\n */\n open(paymentUrl: string): void {\n // Create overlay\n this.overlay = document.createElement('div');\n this.overlay.style.cssText = `\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n z-index: 9998;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n `;\n\n // Create modal container\n this.modal = document.createElement('div');\n this.modal.style.cssText = `\n position: relative;\n background: white;\n border-radius: 8px;\n width: 100%;\n max-width: 600px;\n height: 90vh;\n max-height: 700px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n z-index: 9999;\n `;\n\n // Create close button\n const closeButton = document.createElement('button');\n closeButton.innerHTML = '✕';\n closeButton.style.cssText = `\n position: absolute;\n top: 12px;\n right: 12px;\n width: 32px;\n height: 32px;\n border: none;\n background: rgba(0, 0, 0, 0.1);\n border-radius: 50%;\n cursor: pointer;\n font-size: 18px;\n line-height: 1;\n z-index: 10000;\n transition: background 0.2s;\n color: #333;\n `;\n closeButton.onmouseover = () => {\n closeButton.style.background = 'rgba(0, 0, 0, 0.2)';\n };\n closeButton.onmouseout = () => {\n closeButton.style.background = 'rgba(0, 0, 0, 0.1)';\n };\n closeButton.onclick = () => this.close();\n\n // Create iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = paymentUrl;\n this.iframe.style.cssText = `\n width: 100%;\n height: 100%;\n border: none;\n border-radius: 8px;\n `;\n\n // Allow payment provider features\n this.iframe.setAttribute('allow', 'payment');\n this.iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation');\n\n // Assemble\n this.modal.appendChild(closeButton);\n this.modal.appendChild(this.iframe);\n this.overlay.appendChild(this.modal);\n document.body.appendChild(this.overlay);\n\n // Prevent body scroll\n document.body.style.overflow = 'hidden';\n\n // Close on overlay click\n this.overlay.onclick = (e) => {\n if (e.target === this.overlay) {\n this.close();\n }\n };\n\n // Listen for payment completion (via postMessage)\n window.addEventListener('message', this.handleMessage);\n }\n\n /**\n * Close modal\n */\n close(): void {\n if (this.overlay && this.overlay.parentNode) {\n this.overlay.parentNode.removeChild(this.overlay);\n }\n\n // Restore body scroll\n document.body.style.overflow = '';\n\n // Clean up\n this.overlay = null;\n this.modal = null;\n this.iframe = null;\n\n // Remove message listener\n window.removeEventListener('message', this.handleMessage);\n\n // Call onClose callback\n if (this.onClose) {\n this.onClose();\n }\n }\n\n /**\n * Handle postMessage from iframe\n */\n private handleMessage = (event: MessageEvent): void => {\n // Only accept messages from same origin\n if (event.origin !== window.location.origin) {\n return;\n }\n\n // Check for payment completion signals\n if (event.data?.type === 'RECUR_PAYMENT_COMPLETE') {\n this.close();\n } else if (event.data?.type === 'RECUR_PAYMENT_CANCEL') {\n this.close();\n }\n };\n}\n","/**\n * Vanilla SDK iframe Embedding\n *\n * Embed payment flow in an iframe\n */\n\nexport class RecurIframe {\n private container: HTMLElement;\n private iframe: HTMLIFrameElement | null = null;\n private onClose?: () => void;\n\n constructor(container: string | HTMLElement, onClose?: () => void) {\n if (typeof container === 'string') {\n const element = document.querySelector<HTMLElement>(container);\n if (!element) {\n throw new Error(`Container element not found: ${container}`);\n }\n this.container = element;\n } else {\n this.container = container;\n }\n\n this.onClose = onClose;\n }\n\n /**\n * Embed iframe with payment URL\n */\n embed(paymentUrl: string): void {\n // Clear existing content\n this.container.innerHTML = '';\n\n // Create iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = paymentUrl;\n this.iframe.style.cssText = `\n width: 100%;\n height: 600px;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n `;\n\n // Allow payment provider features\n this.iframe.setAttribute('allow', 'payment');\n this.iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-forms allow-popups allow-top-navigation');\n\n // Append to container\n this.container.appendChild(this.iframe);\n\n // Listen for payment completion\n window.addEventListener('message', this.handleMessage);\n }\n\n /**\n * Remove iframe\n */\n remove(): void {\n if (this.iframe && this.iframe.parentNode) {\n this.iframe.parentNode.removeChild(this.iframe);\n }\n\n this.iframe = null;\n\n // Remove message listener\n window.removeEventListener('message', this.handleMessage);\n\n // Call onClose callback\n if (this.onClose) {\n this.onClose();\n }\n }\n\n /**\n * Handle postMessage from iframe\n */\n private handleMessage = (event: MessageEvent): void => {\n // Only accept messages from same origin\n if (event.origin !== window.location.origin) {\n return;\n }\n\n // Check for payment completion signals\n if (event.data?.type === 'RECUR_PAYMENT_COMPLETE') {\n this.remove();\n } else if (event.data?.type === 'RECUR_PAYMENT_CANCEL') {\n this.remove();\n }\n };\n}\n","/**\n * Embedded Checkout\n *\n * Renders a complete checkout form with embedded credit card fields\n * Powered by PAYUNi SDK for secure card input\n */\n\nimport type { VanillaRecurConfig, CheckoutError, CheckoutResult } from './types.js';\n\nexport interface EmbeddedCheckoutOptions {\n planId: string;\n container: string | HTMLElement;\n customerEmail?: string;\n customerName?: string;\n customerPhone?: string;\n onSuccess?: (result: CheckoutResult) => void;\n onError?: (error: CheckoutError) => void;\n}\n\ninterface InitCheckoutResponse {\n sdkToken: string;\n env: 'S' | 'P';\n timestamp: number;\n plan?: {\n id: string;\n name: string;\n price: number;\n billingPeriod: string;\n description?: string;\n trialDays?: number;\n };\n}\n\nexport class EmbeddedCheckout {\n private config: VanillaRecurConfig;\n private options: EmbeddedCheckoutOptions;\n private container: HTMLElement;\n private sdkToken: string | null = null;\n private sdkEnv: 'S' | 'P' = 'S';\n private payuniSDK: any = null;\n private isFormValid: boolean = false;\n\n constructor(config: VanillaRecurConfig, options: EmbeddedCheckoutOptions) {\n this.config = config;\n this.options = options;\n\n // Resolve container\n if (typeof options.container === 'string') {\n const element = document.querySelector(options.container);\n if (!element) {\n throw new Error(`Container not found: ${options.container}`);\n }\n this.container = element as HTMLElement;\n } else {\n this.container = options.container;\n }\n }\n\n /**\n * Initialize and render the checkout form\n */\n async render(): Promise<void> {\n try {\n // 1. Fetch SDK token and plan info\n await this.initCheckout();\n\n // 2. Render HTML structure\n this.renderHTML();\n\n // 3. Load PAYUNi SDK script\n await this.loadPayUniSDK();\n\n // 4. Initialize PAYUNi SDK with card fields\n await this.initPayUniSDK();\n\n // 5. Setup form submission\n this.setupFormSubmit();\n } catch (error: any) {\n this.handleError(error);\n }\n }\n\n /**\n * Initialize checkout session\n */\n private async initCheckout(): Promise<void> {\n // Auto-detect base URL based on environment\n const getDefaultBaseUrl = () => {\n // Development environment: use relative path /api\n if (typeof window !== 'undefined') {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' ||\n hostname.includes('.test') ||\n hostname.includes('.local') ||\n hostname === '127.0.0.1') {\n return `${window.location.protocol}//${window.location.host}/api`;\n }\n }\n\n // Production environment: use API subdomain\n return 'https://api.recur.tw';\n };\n\n const baseUrl = this.config.baseUrl || getDefaultBaseUrl();\n\n const response = await fetch(`${baseUrl}/v1/checkout/init`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Recur-Publishable-Key': this.config.publishableKey,\n },\n body: JSON.stringify({\n planId: this.options.planId,\n iframeDomain: window.location.origin,\n }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error || 'Failed to initialize checkout');\n }\n\n const data: InitCheckoutResponse = await response.json();\n this.sdkToken = data.sdkToken;\n this.sdkEnv = data.env;\n }\n\n /**\n * Render checkout form HTML\n */\n private renderHTML(): void {\n this.container.innerHTML = `\n <div class=\"recur-embedded-checkout\" style=\"max-width: 400px; margin: 0 auto; padding: 20px; font-family: system-ui, -apple-system, sans-serif;\">\n <div class=\"recur-checkout-header\" style=\"margin-bottom: 24px;\">\n <h2 style=\"margin: 0 0 8px 0; font-size: 20px; font-weight: 600; color: #111;\">Subscribe</h2>\n <p style=\"margin: 0; font-size: 14px; color: #666;\">Complete your subscription</p>\n </div>\n\n <form id=\"recur-checkout-form\" style=\"display: flex; flex-direction: column; gap: 16px;\">\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Email</label>\n <input\n type=\"email\"\n id=\"recur-email\"\n required\n value=\"${this.options.customerEmail || ''}\"\n style=\"width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; outline: none; transition: border-color 0.2s;\"\n onfocus=\"this.style.borderColor='#3b82f6'\"\n onblur=\"this.style.borderColor='#d1d5db'\"\n />\n </div>\n\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Name</label>\n <input\n type=\"text\"\n id=\"recur-name\"\n required\n value=\"${this.options.customerName || ''}\"\n style=\"width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; outline: none; transition: border-color 0.2s;\"\n onfocus=\"this.style.borderColor='#3b82f6'\"\n onblur=\"this.style.borderColor='#d1d5db'\"\n />\n </div>\n\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Phone (Optional)</label>\n <input\n type=\"tel\"\n id=\"recur-phone\"\n value=\"${this.options.customerPhone || ''}\"\n style=\"width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; outline: none; transition: border-color 0.2s;\"\n onfocus=\"this.style.borderColor='#3b82f6'\"\n onblur=\"this.style.borderColor='#d1d5db'\"\n />\n </div>\n\n <div class=\"recur-divider\" style=\"height: 1px; background: #e5e7eb; margin: 8px 0;\"></div>\n\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Card Number</label>\n <div id=\"recur-card-number\" style=\"padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; min-height: 38px;\"></div>\n </div>\n\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 12px;\">\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">Expiry</label>\n <div id=\"recur-card-exp\" style=\"padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; min-height: 38px;\"></div>\n </div>\n <div class=\"recur-field\">\n <label style=\"display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: #374151;\">CVC</label>\n <div id=\"recur-card-cvc\" style=\"padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; min-height: 38px;\"></div>\n </div>\n </div>\n\n <div id=\"recur-error\" style=\"display: none; padding: 12px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 6px; color: #dc2626; font-size: 14px;\"></div>\n\n <button\n type=\"submit\"\n id=\"recur-submit-btn\"\n style=\"width: 100%; padding: 12px; background: #3b82f6; color: white; border: none; border-radius: 6px; font-size: 16px; font-weight: 500; cursor: pointer; transition: background 0.2s;\"\n onmouseover=\"this.style.background='#2563eb'\"\n onmouseout=\"this.style.background='#3b82f6'\"\n disabled\n >\n Subscribe Now\n </button>\n\n <p style=\"margin: 12px 0 0 0; text-align: center; font-size: 12px; color: #9ca3af;\">\n Secure payment powered by PAYUNi\n </p>\n </form>\n </div>\n `;\n }\n\n /**\n * Load PAYUNi SDK script dynamically\n */\n private async loadPayUniSDK(): Promise<void> {\n return new Promise((resolve, reject) => {\n // Check if already loaded\n if ((window as any).UniPayment) {\n resolve();\n return;\n }\n\n const script = document.createElement('script');\n script.src = this.sdkEnv === 'P'\n ? 'https://vendor.payuni.com.tw/sdk/uni-payment.js'\n : 'https://sandbox-vendor.payuni.com.tw/sdk/uni-payment.js';\n script.async = true;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('Failed to load PAYUNi SDK'));\n document.head.appendChild(script);\n });\n }\n\n /**\n * Initialize PAYUNi SDK with credit card fields\n */\n private async initPayUniSDK(): Promise<void> {\n if (!this.sdkToken) {\n throw new Error('SDK token not initialized');\n }\n\n const UniPayment = (window as any).UniPayment;\n if (!UniPayment) {\n throw new Error('PAYUNi SDK not loaded');\n }\n\n const options = {\n env: this.sdkEnv,\n useInst: false,\n elements: {\n CardNo: 'recur-card-number',\n CardExp: 'recur-card-exp',\n CardCvc: 'recur-card-cvc',\n },\n style: {\n color: '#111827',\n errorColor: '#dc2626',\n fontSize: '14px',\n fontWeight: '400',\n lineHeight: '24px',\n },\n };\n\n this.payuniSDK = UniPayment.createSession(this.sdkToken, options);\n await this.payuniSDK.start();\n\n // Listen for card validation updates\n this.payuniSDK.onUpdate((update: any) => {\n const status = update?.status;\n if (status) {\n this.isFormValid = status.CardNo === true && status.CardExp === true && status.CardCvc === true;\n this.updateSubmitButton();\n }\n });\n }\n\n /**\n * Update submit button state\n */\n private updateSubmitButton(): void {\n const submitBtn = document.getElementById('recur-submit-btn') as HTMLButtonElement;\n if (submitBtn) {\n submitBtn.disabled = !this.isFormValid;\n submitBtn.style.opacity = this.isFormValid ? '1' : '0.5';\n submitBtn.style.cursor = this.isFormValid ? 'pointer' : 'not-allowed';\n }\n }\n\n /**\n * Setup form submission handler\n */\n private setupFormSubmit(): void {\n const form = document.getElementById('recur-checkout-form') as HTMLFormElement;\n if (!form) return;\n\n form.addEventListener('submit', async (e) => {\n e.preventDefault();\n await this.handleSubmit();\n });\n }\n\n /**\n * Handle form submission\n */\n private async handleSubmit(): Promise<void> {\n try {\n this.showLoading(true);\n this.hideError();\n\n // Get form data\n const email = (document.getElementById('recur-email') as HTMLInputElement).value;\n const name = (document.getElementById('recur-name') as HTMLInputElement).value;\n const phone = (document.getElementById('recur-phone') as HTMLInputElement).value;\n\n // Validate\n if (!email || !name) {\n throw new Error('Please fill in all required fields');\n }\n\n // Bind card data to SDK token\n if (!this.payuniSDK) {\n throw new Error('Payment system not initialized');\n }\n\n const cardResult = await this.payuniSDK.getTradeResult();\n if (cardResult.Status !== 'SUCCESS') {\n throw new Error(cardResult.Message || 'Card validation failed');\n }\n\n // Create subscription\n // Auto-detect base URL based on environment\n const getDefaultBaseUrl = () => {\n // Development environment: use relative path /api\n if (typeof window !== 'undefined') {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' ||\n hostname.includes('.test') ||\n hostname.includes('.local') ||\n hostname === '127.0.0.1') {\n return `${window.location.protocol}//${window.location.host}/api`;\n }\n }\n\n // Production environment: use API subdomain\n return 'https://api.recur.tw';\n };\n\n const baseUrl = this.config.baseUrl || getDefaultBaseUrl();\n const response = await fetch(`${baseUrl}/v1/subscriptions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Recur-Publishable-Key': this.config.publishableKey,\n },\n body: JSON.stringify({\n planId: this.options.planId,\n customerEmail: email,\n customerName: name,\n customerPhone: phone || undefined,\n }),\n });\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}));\n throw new Error(error.error || 'Failed to create subscription');\n }\n\n const result: CheckoutResult = await response.json();\n\n // Call success callback\n if (this.options.onSuccess) {\n this.options.onSuccess(result);\n }\n\n // Show success message\n this.showSuccess();\n } catch (error: any) {\n this.handleError(error);\n } finally {\n this.showLoading(false);\n }\n }\n\n /**\n * Show loading state\n */\n private showLoading(loading: boolean): void {\n const submitBtn = document.getElementById('recur-submit-btn') as HTMLButtonElement;\n if (submitBtn) {\n submitBtn.disabled = loading;\n submitBtn.textContent = loading ? 'Processing...' : 'Subscribe Now';\n }\n }\n\n /**\n * Show error message\n */\n private showError(message: string): void {\n const errorDiv = document.getElementById('recur-error');\n if (errorDiv) {\n errorDiv.textContent = message;\n errorDiv.style.display = 'block';\n }\n }\n\n /**\n * Hide error message\n */\n private hideError(): void {\n const errorDiv = document.getElementById('recur-error');\n if (errorDiv) {\n errorDiv.style.display = 'none';\n }\n }\n\n /**\n * Show success message\n */\n private showSuccess(): void {\n this.container.innerHTML = `\n <div style=\"max-width: 400px; margin: 0 auto; padding: 40px 20px; text-align: center; font-family: system-ui, -apple-system, sans-serif;\">\n <div style=\"width: 64px; height: 64px; margin: 0 auto 24px; background: #10b981; border-radius: 50%; display: flex; align-items: center; justify-content: center;\">\n <svg width=\"32\" height=\"32\" fill=\"none\" stroke=\"white\" stroke-width=\"3\" viewBox=\"0 0 24 24\">\n <path d=\"M5 13l4 4L19 7\"/>\n </svg>\n </div>\n <h2 style=\"margin: 0 0 8px 0; font-size: 24px; font-weight: 600; color: #111;\">Subscription Complete!</h2>\n <p style=\"margin: 0; font-size: 16px; color: #666;\">Thank you for subscribing. You will receive a confirmation email shortly.</p>\n </div>\n `;\n }\n\n /**\n * Handle errors\n */\n private handleError(error: any): void {\n const message = error?.message || 'An error occurred';\n this.showError(message);\n\n const checkoutError: CheckoutError = {\n code: 'CHECKOUT_ERROR',\n message,\n };\n\n if (this.options.onError) {\n this.options.onError(checkoutError);\n }\n }\n}\n"],"mappings":"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","names":["vanilla_exports","__export","RecurCheckout","vanilla_default","init","RecurCore","config","__publicField","options","planId","customerName","customerEmail","customerPhone","response","errorData","RecurModal","onClose","__publicField","event","paymentUrl","closeButton","e","RecurIframe","container","onClose","__publicField","event","element","paymentUrl","EmbeddedCheckout","config","options","__publicField","element","error","getDefaultBaseUrl","hostname","baseUrl","response","data","resolve","reject","script","UniPayment","update","status","submitBtn","form","e","email","name","phone","cardResult","result","loading","message","errorDiv","checkoutError","RecurCheckout","config","__publicField","RecurCore","options","EmbeddedCheckout","result","mode","paymentUrl","error","onClose","RecurModal","container","RecurIframe","mockHTML","html","overlay","closeBtn","iframe","event","containerEl","init","vanilla_default"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "recur-tw",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3-beta.3",
|
|
4
4
|
"description": "React & Vanilla JS SDK for embedding subscription checkout flows (Taiwan / PAYUNi)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -27,12 +27,6 @@
|
|
|
27
27
|
"url": "https://github.com/your-org/recur-sdk/issues"
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/your-org/recur-sdk#readme",
|
|
30
|
-
"scripts": {
|
|
31
|
-
"build": "tsup",
|
|
32
|
-
"dev": "tsup --watch",
|
|
33
|
-
"lint": "eslint . --max-warnings 0",
|
|
34
|
-
"type-check": "tsc --noEmit"
|
|
35
|
-
},
|
|
36
30
|
"main": "./dist/index.js",
|
|
37
31
|
"module": "./dist/index.js",
|
|
38
32
|
"types": "./dist/index.d.ts",
|
|
@@ -69,8 +63,6 @@
|
|
|
69
63
|
"@types/node": "^20.19.9",
|
|
70
64
|
"@types/react": "^19.1.9",
|
|
71
65
|
"@types/react-dom": "^19.1.7",
|
|
72
|
-
"@workspace/eslint-config": "workspace:*",
|
|
73
|
-
"@workspace/typescript-config": "workspace:*",
|
|
74
66
|
"autoprefixer": "^10.4.22",
|
|
75
67
|
"eslint": "^9.32.0",
|
|
76
68
|
"postcss": "^8.5.6",
|
|
@@ -78,6 +70,15 @@
|
|
|
78
70
|
"react-dom": "^19.1.1",
|
|
79
71
|
"tailwindcss": "^4.1.11",
|
|
80
72
|
"tsup": "^8.3.5",
|
|
81
|
-
"typescript": "^5.9.2"
|
|
73
|
+
"typescript": "^5.9.2",
|
|
74
|
+
"@workspace/eslint-config": "0.0.0",
|
|
75
|
+
"@workspace/typescript-config": "0.0.0"
|
|
76
|
+
},
|
|
77
|
+
"scripts": {
|
|
78
|
+
"build": "tsup",
|
|
79
|
+
"dev": "tsup --watch",
|
|
80
|
+
"lint": "eslint . --max-warnings 0",
|
|
81
|
+
"type-check": "tsc --noEmit",
|
|
82
|
+
"examples": "npx http-server examples -p 8080 -o"
|
|
82
83
|
}
|
|
83
|
-
}
|
|
84
|
+
}
|