@churnback/sdk 0.1.3 → 0.1.4
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.global.js +3 -3
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.global.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var ChurnBack=(()=>{var u=Object.defineProperty;var
|
|
1
|
+
"use strict";var ChurnBack=(()=>{var u=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var k=(o,e)=>{for(var t in e)u(o,t,{get:e[t],enumerable:!0})},S=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of w(e))!v.call(o,s)&&s!==t&&u(o,s,{get:()=>e[s],enumerable:!(n=y(e,s))||n.enumerable});return o};var E=o=>S(u({},"__esModule",{value:!0}),o);var D={};k(D,{ChurnBackClient:()=>h,default:()=>I});function f(o){return{apiKey:o.apiKey,apiBaseUrl:(o.apiBaseUrl||"https://churnback.ai/api/sdk").replace(/\/$/,"")}}var l=class{constructor(e){this.config=e}async fetchCancelFlow(e,t,n){let s=new URLSearchParams({customer_id:e,subscription_id:t});n&&s.set("price_id",n);let i=await fetch(`${this.config.apiBaseUrl}/cancel-flow?${s}`,{headers:{"x-churnback-key":this.config.apiKey}});if(!i.ok){let a=await i.json().catch(()=>({}));throw new Error(a.error||`Failed to fetch cancel flow (${i.status})`)}return i.json()}async submitResult(e){let t=await fetch(`${this.config.apiBaseUrl}/submit`,{method:"POST",headers:{"Content-Type":"application/json","x-churnback-key":this.config.apiKey},body:JSON.stringify(e)});if(!t.ok){let n=await t.json().catch(()=>({}));throw new Error(n.error||`Failed to submit result (${t.status})`)}}async checkDunning(e){let t=new URLSearchParams({customer_id:e}),n=await fetch(`${this.config.apiBaseUrl}/dunning?${t}`,{headers:{"x-churnback-key":this.config.apiKey}});return n.ok?n.json():{hasFailedPayment:!1}}};var b=`
|
|
2
2
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
3
3
|
|
|
4
4
|
.cb-overlay {
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
.cb-modal-inner {
|
|
194
194
|
position: relative;
|
|
195
195
|
}
|
|
196
|
-
`;function g(o,e,t){let n=document.createElement("div");switch(o.type){case"reason_survey":R(n,o.config,e,t);break;case"offer":
|
|
196
|
+
`;function g(o,e,t){let n=document.createElement("div");switch(o.type){case"reason_survey":R(n,o.config,e,t);break;case"offer":B(n,o.config,t);break;case"custom_message":N(n,o.config,{onNext:t.onNext,onClose:t.onGoBack});break;case"confirmation":F(n,o.config,t);break}if(e.totalSteps>1){let s=document.createElement("div");s.className="cb-step-dots";for(let i=0;i<e.totalSteps;i++){let a=document.createElement("div");a.className=`cb-dot${i===e.currentIndex?" active":""}`,s.appendChild(a)}n.appendChild(s)}return n}function R(o,e,t,n){let s=document.createElement("h2");s.className="cb-headline",s.textContent=e.headline||"Why are you cancelling?",o.appendChild(s);let i=e.reasons||[],a=document.createElement("div");if(a.className="cb-reasons",i.forEach(r=>{let c=document.createElement("button");c.className=`cb-reason${t.selectedReason===r.id?" selected":""}`,c.textContent=r.label,c.onclick=()=>{n.onSelectReason(r.id),a.querySelectorAll(".cb-reason").forEach(C=>C.classList.remove("selected")),c.classList.add("selected")},a.appendChild(c)}),o.appendChild(a),e.allow_comment){let r=document.createElement("textarea");r.className="cb-textarea",r.placeholder=e.comment_placeholder||"Tell us more...",r.value=t.comment,r.oninput=()=>n.onComment(r.value),o.appendChild(r)}let d=document.createElement("button");d.className="cb-btn cb-btn-primary",d.textContent="Continue",d.onclick=n.onNext,o.appendChild(d)}function B(o,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"Special offer for you",o.appendChild(n);let s=document.createElement("p");s.className="cb-body",s.textContent=e.body||"",o.appendChild(s);let i=document.createElement("button");i.className="cb-btn cb-btn-primary",i.textContent=e.cta_accept||"Accept Offer",i.onclick=()=>t.onAcceptOffer(e),o.appendChild(i);let a=document.createElement("button");a.className="cb-btn cb-btn-ghost",a.textContent=e.cta_decline||"No thanks",a.onclick=t.onDeclineOffer,o.appendChild(a)}function N(o,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"",o.appendChild(n);let s=document.createElement("p");if(s.className="cb-body",s.textContent=e.body||"",o.appendChild(s),e.cta_text){let i=document.createElement("button");i.className="cb-btn cb-btn-primary",i.textContent=e.cta_text,i.onclick=e.cta_action==="close"?t.onClose:t.onNext,o.appendChild(i)}}function F(o,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"Are you sure?",o.appendChild(n);let s=document.createElement("p");s.className="cb-body",s.textContent=e.body||"",o.appendChild(s);let i=document.createElement("button");i.className="cb-btn cb-btn-primary",i.textContent=e.go_back_text||"Keep my subscription",i.onclick=t.onGoBack,o.appendChild(i);let a=document.createElement("button");a.className="cb-btn cb-btn-danger",a.textContent=e.confirm_cancel_text||"Yes, cancel",a.onclick=t.onConfirmCancel,o.appendChild(a)}var p=class{constructor(e,t){this.api=e;this.options=t;this.shadowHost=null;this.shadowRoot=null;this.steps=[];this.sessionId="";this.destroyed=!1;this.state={currentIndex:0,selectedReason:null,comment:"",totalSteps:0}}async open(){let e;try{e=await this.api.fetchCancelFlow(this.options.stripeCustomerId,this.options.stripeSubscriptionId,this.options.stripePriceId)}catch(n){console.error("[ChurnBack] Failed to load cancel flow:",n);let s=n instanceof Error?n:new Error(String(n));throw this.options.onError?.(s),n}if(this.destroyed)return;this.sessionId=e.sessionId,this.steps=e.steps,this.state.totalSteps=e.steps.length,this.shadowHost=document.createElement("div"),this.shadowHost.id="churnback-modal",document.body.appendChild(this.shadowHost),this.shadowRoot=this.shadowHost.attachShadow({mode:"closed"});let t=document.createElement("style");t.textContent=b,this.shadowRoot.appendChild(t),this.renderCurrentStep()}renderCurrentStep(){if(!this.shadowRoot||this.destroyed)return;let e=this.shadowRoot.querySelector(".cb-overlay");e&&e.remove();let t=this.steps[this.state.currentIndex];if(!t)return;let n=document.createElement("div");n.className="cb-overlay",n.onclick=r=>{r.target===n&&this.dismiss()};let s=document.createElement("div");s.className="cb-modal";let i=document.createElement("div");i.className="cb-modal-inner";let a=document.createElement("button");a.className="cb-close",a.innerHTML="×",a.onclick=()=>this.dismiss(),i.appendChild(a);let d=g(t,this.state,{onNext:()=>this.nextStep(),onSelectReason:r=>{this.state.selectedReason=r},onComment:r=>{this.state.comment=r},onAcceptOffer:r=>this.acceptOffer(r),onDeclineOffer:()=>{console.log("[ChurnBack] Decline offer clicked"),this.nextStep()},onConfirmCancel:()=>this.confirmCancel(),onGoBack:()=>this.dismiss()});i.appendChild(d),s.appendChild(i),n.appendChild(s),this.shadowRoot.appendChild(n)}nextStep(){console.log(`[ChurnBack] nextStep: index=${this.state.currentIndex}, total=${this.steps.length}`),this.state.currentIndex<this.steps.length-1?(this.state.currentIndex++,console.log("[ChurnBack] Advancing to step",this.state.currentIndex),this.renderCurrentStep()):(console.log("[ChurnBack] Last step \u2014 calling confirmCancel"),this.confirmCancel())}acceptOffer(e){this.close(),this.options.onComplete?.({action:"saved",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0,offerAccepted:e}),this.api.submitResult({sessionId:this.sessionId,action:"offer_accepted",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0,acceptedOffer:e}).catch(t=>console.error("[ChurnBack] Failed to submit offer acceptance:",t))}confirmCancel(){console.log("[ChurnBack] confirmCancel called"),this.close(),console.log("[ChurnBack] modal closed"),this.options.onComplete?.({action:"cancelled",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0}),this.api.submitResult({sessionId:this.sessionId,action:"cancelled",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0}).catch(e=>console.error("[ChurnBack] Failed to submit cancellation:",e))}dismiss(){this.close(),this.options.onDismiss?.(),this.options.onComplete?.({action:"dismissed"}),this.api.submitResult({sessionId:this.sessionId,action:"abandoned"}).catch(()=>{})}close(){this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null,this.shadowRoot=null}destroy(){this.destroyed=!0,this.close()}};var x=`
|
|
197
197
|
.cb-dunning-banner {
|
|
198
198
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
199
199
|
background: #fef2f2;
|
|
@@ -247,6 +247,6 @@
|
|
|
247
247
|
.cb-dunning-hidden {
|
|
248
248
|
display: none;
|
|
249
249
|
}
|
|
250
|
-
`;var m=class{constructor(e,t){this.api=e;this.options=t;this.shadowHost=null;this.destroyed=!1}async mount(){let e;try{e=await this.api.checkDunning(this.options.stripeCustomerId)}catch{return}if(this.destroyed||!e.hasFailedPayment)return;let t=typeof this.options.targetElement=="string"?document.querySelector(this.options.targetElement):this.options.targetElement;if(!t){console.warn("[ChurnBack] Dunning banner target element not found");return}this.shadowHost=document.createElement("div");let n=this.shadowHost.attachShadow({mode:"closed"}),s=document.createElement("style");s.textContent=x,n.appendChild(s);let i=document.createElement("div");i.className="cb-dunning-banner";let a=document.createElement("div");a.className="cb-dunning-content";let d=document.createElement("div");d.className="cb-dunning-headline",d.textContent=e.bannerConfig?.headline||"Payment failed";let r=document.createElement("div");if(r.className="cb-dunning-body",r.textContent=e.bannerConfig?.body||`We were unable to process your payment${e.amountDueCents?` of $${(e.amountDueCents/100).toFixed(2)}`:""}. Please update your payment method to avoid service interruption.`,a.appendChild(d),a.appendChild(r),i.appendChild(a),e.updatePaymentUrl){let c=document.createElement("a");c.className="cb-dunning-cta",c.href=e.updatePaymentUrl,c.target="_blank",c.rel="noopener noreferrer",c.textContent=e.bannerConfig?.ctaText||"Update Payment",i.appendChild(c)}n.appendChild(i),t.appendChild(this.shadowHost)}destroy(){this.destroyed=!0,this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null}};var h=class{constructor(e){this.activeModal=null;this.activeBanners=[];if(!e.apiKey)throw new Error("[ChurnBack] apiKey is required");let t=f(e);this.api=new l(t)}async triggerCancelFlow(e){this.activeModal?.destroy();let t=new p(this.api,e);this.activeModal=t;try{await t.open()}catch(n){if(e.onError)return;throw n}}async checkDunning(e){return this.api.checkDunning(e.stripeCustomerId)}mountDunningBanner(e){let t=new m(this.api,e);this.activeBanners.push(t),t.mount()}destroy(){this.activeModal?.destroy(),this.activeModal=null,this.activeBanners.forEach(e=>e.destroy()),this.activeBanners=[]}},
|
|
250
|
+
`;var m=class{constructor(e,t){this.api=e;this.options=t;this.shadowHost=null;this.destroyed=!1}async mount(){let e;try{e=await this.api.checkDunning(this.options.stripeCustomerId)}catch{return}if(this.destroyed||!e.hasFailedPayment)return;let t=typeof this.options.targetElement=="string"?document.querySelector(this.options.targetElement):this.options.targetElement;if(!t){console.warn("[ChurnBack] Dunning banner target element not found");return}this.shadowHost=document.createElement("div");let n=this.shadowHost.attachShadow({mode:"closed"}),s=document.createElement("style");s.textContent=x,n.appendChild(s);let i=document.createElement("div");i.className="cb-dunning-banner";let a=document.createElement("div");a.className="cb-dunning-content";let d=document.createElement("div");d.className="cb-dunning-headline",d.textContent=e.bannerConfig?.headline||"Payment failed";let r=document.createElement("div");if(r.className="cb-dunning-body",r.textContent=e.bannerConfig?.body||`We were unable to process your payment${e.amountDueCents?` of $${(e.amountDueCents/100).toFixed(2)}`:""}. Please update your payment method to avoid service interruption.`,a.appendChild(d),a.appendChild(r),i.appendChild(a),e.updatePaymentUrl){let c=document.createElement("a");c.className="cb-dunning-cta",c.href=e.updatePaymentUrl,c.target="_blank",c.rel="noopener noreferrer",c.textContent=e.bannerConfig?.ctaText||"Update Payment",i.appendChild(c)}n.appendChild(i),t.appendChild(this.shadowHost)}destroy(){this.destroyed=!0,this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null}};var h=class{constructor(e){this.activeModal=null;this.activeBanners=[];if(!e.apiKey)throw new Error("[ChurnBack] apiKey is required");let t=f(e);this.api=new l(t)}async triggerCancelFlow(e){this.activeModal?.destroy();let t=new p(this.api,e);this.activeModal=t;try{await t.open()}catch(n){if(e.onError)return;throw n}}async checkDunning(e){return this.api.checkDunning(e.stripeCustomerId)}mountDunningBanner(e){let t=new m(this.api,e);this.activeBanners.push(t),t.mount()}destroy(){this.activeModal?.destroy(),this.activeModal=null,this.activeBanners.forEach(e=>e.destroy()),this.activeBanners=[]}},I=h;return E(D);})();
|
|
251
251
|
if(typeof ChurnBack!=='undefined'&&ChurnBack.default){ChurnBack=ChurnBack.default;}
|
|
252
252
|
//# sourceMappingURL=index.global.js.map
|
package/dist/index.global.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/config.ts","../src/core/api.ts","../src/cancel-flow/styles.ts","../src/cancel-flow/steps.ts","../src/cancel-flow/modal.ts","../src/dunning/styles.ts","../src/dunning/banner.ts"],"sourcesContent":["import { resolveConfig, type ChurnBackConfig } from \"./core/config\";\nimport { ApiClient, type DunningResponse } from \"./core/api\";\nimport { CancelFlowModal, type CancelFlowOptions, type CancelFlowResult } from \"./cancel-flow/modal\";\nimport { DunningBanner, type DunningBannerOptions } from \"./dunning/banner\";\n\nexport type { ChurnBackConfig, CancelFlowOptions, CancelFlowResult, DunningResponse };\n\nexport class ChurnBackClient {\n private api: ApiClient;\n private activeModal: CancelFlowModal | null = null;\n private activeBanners: DunningBanner[] = [];\n\n constructor(config: ChurnBackConfig) {\n if (!config.apiKey) {\n throw new Error(\"[ChurnBack] apiKey is required\");\n }\n const resolved = resolveConfig(config);\n this.api = new ApiClient(resolved);\n }\n\n async triggerCancelFlow(options: CancelFlowOptions): Promise<void> {\n this.activeModal?.destroy();\n\n const modal = new CancelFlowModal(this.api, options);\n this.activeModal = modal;\n\n try {\n await modal.open();\n } catch (err) {\n // If onError is provided, the consumer handles it — don't re-throw\n if (options.onError) return;\n throw err;\n }\n }\n\n async checkDunning(options: { stripeCustomerId: string }): Promise<DunningResponse> {\n return this.api.checkDunning(options.stripeCustomerId);\n }\n\n mountDunningBanner(options: DunningBannerOptions): void {\n const banner = new DunningBanner(this.api, options);\n this.activeBanners.push(banner);\n banner.mount();\n }\n\n destroy(): void {\n this.activeModal?.destroy();\n this.activeModal = null;\n this.activeBanners.forEach((b) => b.destroy());\n this.activeBanners = [];\n }\n}\n\nexport default ChurnBackClient;\n","export interface ChurnBackConfig {\n apiKey: string;\n apiBaseUrl?: string;\n}\n\nexport interface ResolvedConfig {\n apiKey: string;\n apiBaseUrl: string;\n}\n\nexport function resolveConfig(config: ChurnBackConfig): ResolvedConfig {\n return {\n apiKey: config.apiKey,\n apiBaseUrl: (config.apiBaseUrl || \"https://churnback.ai/api/sdk\").replace(/\\/$/, \"\"),\n };\n}\n","import type { ResolvedConfig } from \"./config\";\n\nexport interface FlowResponse {\n sessionId: string;\n steps: FlowStep[];\n style: Record<string, string>;\n}\n\nexport interface FlowStep {\n id: string;\n type: \"reason_survey\" | \"offer\" | \"custom_message\" | \"confirmation\";\n config: Record<string, unknown>;\n}\n\nexport interface SubmitPayload {\n sessionId: string;\n action: \"offer_accepted\" | \"cancelled\" | \"abandoned\";\n reason?: string;\n comment?: string;\n acceptedOffer?: Record<string, unknown>;\n}\n\nexport interface DunningResponse {\n hasFailedPayment: boolean;\n amountDueCents?: number;\n updatePaymentUrl?: string;\n bannerConfig?: {\n headline: string;\n body: string;\n ctaText: string;\n };\n}\n\nexport class ApiClient {\n constructor(private config: ResolvedConfig) {}\n\n async fetchCancelFlow(\n customerId: string,\n subscriptionId: string,\n priceId?: string\n ): Promise<FlowResponse> {\n const params = new URLSearchParams({\n customer_id: customerId,\n subscription_id: subscriptionId,\n });\n if (priceId) params.set(\"price_id\", priceId);\n\n const res = await fetch(`${this.config.apiBaseUrl}/cancel-flow?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to fetch cancel flow (${res.status})`);\n }\n\n return res.json();\n }\n\n async submitResult(payload: SubmitPayload): Promise<void> {\n const res = await fetch(`${this.config.apiBaseUrl}/submit`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-churnback-key\": this.config.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to submit result (${res.status})`);\n }\n }\n\n async checkDunning(customerId: string): Promise<DunningResponse> {\n const params = new URLSearchParams({ customer_id: customerId });\n\n const res = await fetch(`${this.config.apiBaseUrl}/dunning?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n return { hasFailedPayment: false };\n }\n\n return res.json();\n }\n}\n","export const MODAL_STYLES = `\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n .cb-overlay {\n position: fixed;\n inset: 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 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n animation: cb-fade-in 0.2s ease;\n }\n\n @keyframes cb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes cb-slide-up {\n from { transform: translateY(20px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n }\n\n .cb-modal {\n background: white;\n border-radius: 16px;\n padding: 32px;\n max-width: 480px;\n width: 90%;\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n animation: cb-slide-up 0.3s ease;\n }\n\n .cb-headline {\n font-size: 22px;\n font-weight: 700;\n color: #1a1a1a;\n margin-bottom: 12px;\n line-height: 1.3;\n }\n\n .cb-body {\n font-size: 15px;\n color: #555;\n line-height: 1.6;\n margin-bottom: 20px;\n }\n\n .cb-reasons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 16px;\n }\n\n .cb-reason {\n padding: 12px 16px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n transition: all 0.15s ease;\n background: white;\n text-align: left;\n }\n\n .cb-reason:hover {\n border-color: #7c3aed;\n background: #f5f3ff;\n }\n\n .cb-reason.selected {\n border-color: #7c3aed;\n background: #f5f3ff;\n color: #7c3aed;\n font-weight: 500;\n }\n\n .cb-textarea {\n width: 100%;\n min-height: 80px;\n padding: 12px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n font-size: 14px;\n font-family: inherit;\n resize: vertical;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .cb-textarea:focus {\n border-color: #7c3aed;\n }\n\n .cb-btn {\n display: block;\n width: 100%;\n padding: 12px 20px;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n border: none;\n transition: all 0.15s ease;\n margin-bottom: 8px;\n }\n\n .cb-btn-primary {\n background: #7c3aed;\n color: white;\n }\n\n .cb-btn-primary:hover {\n background: #6d28d9;\n }\n\n .cb-btn-danger {\n background: #ef4444;\n color: white;\n }\n\n .cb-btn-danger:hover {\n background: #dc2626;\n }\n\n .cb-btn-secondary {\n background: transparent;\n color: #7c3aed;\n border: 2px solid #7c3aed;\n }\n\n .cb-btn-secondary:hover {\n background: #f5f3ff;\n }\n\n .cb-btn-ghost {\n background: transparent;\n color: #888;\n }\n\n .cb-btn-ghost:hover {\n color: #555;\n }\n\n .cb-step-dots {\n display: flex;\n justify-content: center;\n gap: 6px;\n margin-top: 16px;\n }\n\n .cb-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #e0e0e0;\n transition: background 0.2s ease;\n }\n\n .cb-dot.active {\n background: #7c3aed;\n }\n\n .cb-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n font-size: 24px;\n color: #888;\n cursor: pointer;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n }\n\n .cb-close:hover {\n background: #f0f0f0;\n color: #333;\n }\n\n .cb-modal-inner {\n position: relative;\n }\n`;\n","import type { FlowStep } from \"../core/api\";\n\nexport interface StepState {\n currentIndex: number;\n selectedReason: string | null;\n comment: string;\n totalSteps: number;\n}\n\nexport function renderStep(\n step: FlowStep,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n): HTMLElement {\n const container = document.createElement(\"div\");\n\n switch (step.type) {\n case \"reason_survey\":\n renderReasonSurvey(container, step.config, state, callbacks);\n break;\n case \"offer\":\n renderOffer(container, step.config, callbacks);\n break;\n case \"custom_message\":\n renderCustomMessage(container, step.config, {\n onNext: callbacks.onNext,\n onClose: callbacks.onGoBack,\n });\n break;\n case \"confirmation\":\n renderConfirmation(container, step.config, callbacks);\n break;\n }\n\n // Step dots\n if (state.totalSteps > 1) {\n const dots = document.createElement(\"div\");\n dots.className = \"cb-step-dots\";\n for (let i = 0; i < state.totalSteps; i++) {\n const dot = document.createElement(\"div\");\n dot.className = `cb-dot${i === state.currentIndex ? \" active\" : \"\"}`;\n dots.appendChild(dot);\n }\n container.appendChild(dots);\n }\n\n return container;\n}\n\nfunction renderReasonSurvey(\n container: HTMLElement,\n config: Record<string, unknown>,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Why are you cancelling?\";\n container.appendChild(headline);\n\n const reasons = (config.reasons as Array<{ id: string; label: string }>) || [];\n const reasonsDiv = document.createElement(\"div\");\n reasonsDiv.className = \"cb-reasons\";\n\n reasons.forEach((r) => {\n const btn = document.createElement(\"button\");\n btn.className = `cb-reason${state.selectedReason === r.id ? \" selected\" : \"\"}`;\n btn.textContent = r.label;\n btn.onclick = () => {\n callbacks.onSelectReason(r.id);\n // Update selection visually\n reasonsDiv.querySelectorAll(\".cb-reason\").forEach((el) => el.classList.remove(\"selected\"));\n btn.classList.add(\"selected\");\n };\n reasonsDiv.appendChild(btn);\n });\n container.appendChild(reasonsDiv);\n\n if (config.allow_comment) {\n const textarea = document.createElement(\"textarea\");\n textarea.className = \"cb-textarea\";\n textarea.placeholder = (config.comment_placeholder as string) || \"Tell us more...\";\n textarea.value = state.comment;\n textarea.oninput = () => callbacks.onComment(textarea.value);\n container.appendChild(textarea);\n }\n\n const nextBtn = document.createElement(\"button\");\n nextBtn.className = \"cb-btn cb-btn-primary\";\n nextBtn.textContent = \"Continue\";\n nextBtn.onclick = callbacks.onNext;\n container.appendChild(nextBtn);\n}\n\nfunction renderOffer(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Special offer for you\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const acceptBtn = document.createElement(\"button\");\n acceptBtn.className = \"cb-btn cb-btn-primary\";\n acceptBtn.textContent = (config.cta_accept as string) || \"Accept Offer\";\n acceptBtn.onclick = () => callbacks.onAcceptOffer(config);\n container.appendChild(acceptBtn);\n\n const declineBtn = document.createElement(\"button\");\n declineBtn.className = \"cb-btn cb-btn-ghost\";\n declineBtn.textContent = (config.cta_decline as string) || \"No thanks\";\n declineBtn.onclick = callbacks.onDeclineOffer;\n container.appendChild(declineBtn);\n}\n\nfunction renderCustomMessage(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: { onNext: () => void; onClose: () => void }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n if (config.cta_text) {\n const btn = document.createElement(\"button\");\n btn.className = \"cb-btn cb-btn-primary\";\n btn.textContent = config.cta_text as string;\n btn.onclick = config.cta_action === \"close\" ? callbacks.onClose : callbacks.onNext;\n container.appendChild(btn);\n }\n}\n\nfunction renderConfirmation(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Are you sure?\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const goBackBtn = document.createElement(\"button\");\n goBackBtn.className = \"cb-btn cb-btn-primary\";\n goBackBtn.textContent = (config.go_back_text as string) || \"Keep my subscription\";\n goBackBtn.onclick = callbacks.onGoBack;\n container.appendChild(goBackBtn);\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.className = \"cb-btn cb-btn-danger\";\n cancelBtn.textContent = (config.confirm_cancel_text as string) || \"Yes, cancel\";\n cancelBtn.onclick = callbacks.onConfirmCancel;\n container.appendChild(cancelBtn);\n}\n","import type { ApiClient, FlowStep, FlowResponse } from \"../core/api\";\nimport { MODAL_STYLES } from \"./styles\";\nimport { renderStep, type StepState } from \"./steps\";\n\nexport interface CancelFlowOptions {\n stripeCustomerId: string;\n stripeSubscriptionId: string;\n stripePriceId?: string;\n onComplete?: (result: CancelFlowResult) => void;\n onDismiss?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface CancelFlowResult {\n action: \"saved\" | \"cancelled\" | \"dismissed\";\n reason?: string;\n comment?: string;\n offerAccepted?: Record<string, unknown>;\n}\n\nexport class CancelFlowModal {\n private shadowHost: HTMLElement | null = null;\n private shadowRoot: ShadowRoot | null = null;\n private state: StepState;\n private steps: FlowStep[] = [];\n private sessionId = \"\";\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: CancelFlowOptions\n ) {\n this.state = {\n currentIndex: 0,\n selectedReason: null,\n comment: \"\",\n totalSteps: 0,\n };\n }\n\n async open(): Promise<void> {\n // Fetch flow config\n let flow: FlowResponse;\n try {\n flow = await this.api.fetchCancelFlow(\n this.options.stripeCustomerId,\n this.options.stripeSubscriptionId,\n this.options.stripePriceId\n );\n } catch (err) {\n console.error(\"[ChurnBack] Failed to load cancel flow:\", err);\n const error = err instanceof Error ? err : new Error(String(err));\n this.options.onError?.(error);\n throw err;\n }\n\n if (this.destroyed) return;\n\n this.sessionId = flow.sessionId;\n this.steps = flow.steps;\n this.state.totalSteps = flow.steps.length;\n\n // Create Shadow DOM host\n this.shadowHost = document.createElement(\"div\");\n this.shadowHost.id = \"churnback-modal\";\n document.body.appendChild(this.shadowHost);\n this.shadowRoot = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n // Inject styles\n const style = document.createElement(\"style\");\n style.textContent = MODAL_STYLES;\n this.shadowRoot.appendChild(style);\n\n this.renderCurrentStep();\n }\n\n private renderCurrentStep(): void {\n if (!this.shadowRoot || this.destroyed) return;\n\n // Remove old content (keep style)\n const existing = this.shadowRoot.querySelector(\".cb-overlay\");\n if (existing) existing.remove();\n\n const step = this.steps[this.state.currentIndex];\n if (!step) return;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"cb-overlay\";\n overlay.onclick = (e) => {\n if (e.target === overlay) this.dismiss();\n };\n\n const modal = document.createElement(\"div\");\n modal.className = \"cb-modal\";\n\n const inner = document.createElement(\"div\");\n inner.className = \"cb-modal-inner\";\n\n // Close button\n const closeBtn = document.createElement(\"button\");\n closeBtn.className = \"cb-close\";\n closeBtn.innerHTML = \"×\";\n closeBtn.onclick = () => this.dismiss();\n inner.appendChild(closeBtn);\n\n const stepContent = renderStep(step, this.state, {\n onNext: () => this.nextStep(),\n onSelectReason: (id) => {\n this.state.selectedReason = id;\n },\n onComment: (text) => {\n this.state.comment = text;\n },\n onAcceptOffer: (offer) => this.acceptOffer(offer),\n onDeclineOffer: () => this.nextStep(),\n onConfirmCancel: () => this.confirmCancel(),\n onGoBack: () => this.dismiss(),\n });\n\n inner.appendChild(stepContent);\n modal.appendChild(inner);\n overlay.appendChild(modal);\n this.shadowRoot.appendChild(overlay);\n }\n\n private nextStep(): void {\n if (this.state.currentIndex < this.steps.length - 1) {\n this.state.currentIndex++;\n this.renderCurrentStep();\n } else {\n this.confirmCancel();\n }\n }\n\n private acceptOffer(offer: Record<string, unknown>): void {\n this.close();\n this.options.onComplete?.({\n action: \"saved\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n offerAccepted: offer,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"offer_accepted\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n acceptedOffer: offer,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit offer acceptance:\", err));\n }\n\n private confirmCancel(): void {\n this.close();\n this.options.onComplete?.({\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit cancellation:\", err));\n }\n\n private dismiss(): void {\n this.close();\n this.options.onDismiss?.();\n this.options.onComplete?.({ action: \"dismissed\" });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"abandoned\",\n }).catch(() => { /* Silent fail on dismiss */ });\n }\n\n private close(): void {\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n this.shadowRoot = null;\n }\n\n destroy(): void {\n this.destroyed = true;\n this.close();\n }\n}\n","export const BANNER_STYLES = `\n .cb-dunning-banner {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 10px;\n padding: 16px 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n }\n\n .cb-dunning-content {\n flex: 1;\n min-width: 200px;\n }\n\n .cb-dunning-headline {\n font-size: 15px;\n font-weight: 600;\n color: #991b1b;\n margin-bottom: 4px;\n }\n\n .cb-dunning-body {\n font-size: 13px;\n color: #b91c1c;\n line-height: 1.4;\n }\n\n .cb-dunning-cta {\n padding: 8px 16px;\n background: #ef4444;\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.15s ease;\n text-decoration: none;\n display: inline-block;\n }\n\n .cb-dunning-cta:hover {\n background: #dc2626;\n }\n\n .cb-dunning-hidden {\n display: none;\n }\n`;\n","import type { ApiClient, DunningResponse } from \"../core/api\";\nimport { BANNER_STYLES } from \"./styles\";\n\nexport interface DunningBannerOptions {\n stripeCustomerId: string;\n targetElement: string | HTMLElement;\n}\n\nexport class DunningBanner {\n private shadowHost: HTMLElement | null = null;\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: DunningBannerOptions\n ) {}\n\n async mount(): Promise<void> {\n let dunning: DunningResponse;\n try {\n dunning = await this.api.checkDunning(this.options.stripeCustomerId);\n } catch {\n return; // Silent fail\n }\n\n if (this.destroyed || !dunning.hasFailedPayment) return;\n\n const target =\n typeof this.options.targetElement === \"string\"\n ? document.querySelector(this.options.targetElement)\n : this.options.targetElement;\n\n if (!target) {\n console.warn(\"[ChurnBack] Dunning banner target element not found\");\n return;\n }\n\n this.shadowHost = document.createElement(\"div\");\n const shadow = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n const style = document.createElement(\"style\");\n style.textContent = BANNER_STYLES;\n shadow.appendChild(style);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cb-dunning-banner\";\n\n const content = document.createElement(\"div\");\n content.className = \"cb-dunning-content\";\n\n const headline = document.createElement(\"div\");\n headline.className = \"cb-dunning-headline\";\n headline.textContent = dunning.bannerConfig?.headline || \"Payment failed\";\n\n const body = document.createElement(\"div\");\n body.className = \"cb-dunning-body\";\n body.textContent =\n dunning.bannerConfig?.body ||\n `We were unable to process your payment${dunning.amountDueCents ? ` of $${(dunning.amountDueCents / 100).toFixed(2)}` : \"\"}. Please update your payment method to avoid service interruption.`;\n\n content.appendChild(headline);\n content.appendChild(body);\n banner.appendChild(content);\n\n if (dunning.updatePaymentUrl) {\n const cta = document.createElement(\"a\");\n cta.className = \"cb-dunning-cta\";\n cta.href = dunning.updatePaymentUrl;\n cta.target = \"_blank\";\n cta.rel = \"noopener noreferrer\";\n cta.textContent = dunning.bannerConfig?.ctaText || \"Update Payment\";\n banner.appendChild(cta);\n }\n\n shadow.appendChild(banner);\n target.appendChild(this.shadowHost);\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n }\n}\n"],"mappings":"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","names":["index_exports","__export","ChurnBackClient","index_default","resolveConfig","config","ApiClient","config","customerId","subscriptionId","priceId","params","res","body","payload","MODAL_STYLES","renderStep","step","state","callbacks","container","renderReasonSurvey","renderOffer","renderCustomMessage","renderConfirmation","dots","dot","config","headline","reasons","reasonsDiv","btn","el","textarea","nextBtn","body","acceptBtn","declineBtn","goBackBtn","cancelBtn","CancelFlowModal","api","options","flow","err","error","style","MODAL_STYLES","existing","step","overlay","e","modal","inner","closeBtn","stepContent","renderStep","id","text","offer","BANNER_STYLES","DunningBanner","api","options","dunning","target","shadow","style","BANNER_STYLES","banner","content","headline","body","cta","ChurnBackClient","config","resolved","resolveConfig","ApiClient","options","modal","CancelFlowModal","err","banner","DunningBanner","b","index_default"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/config.ts","../src/core/api.ts","../src/cancel-flow/styles.ts","../src/cancel-flow/steps.ts","../src/cancel-flow/modal.ts","../src/dunning/styles.ts","../src/dunning/banner.ts"],"sourcesContent":["import { resolveConfig, type ChurnBackConfig } from \"./core/config\";\nimport { ApiClient, type DunningResponse } from \"./core/api\";\nimport { CancelFlowModal, type CancelFlowOptions, type CancelFlowResult } from \"./cancel-flow/modal\";\nimport { DunningBanner, type DunningBannerOptions } from \"./dunning/banner\";\n\nexport type { ChurnBackConfig, CancelFlowOptions, CancelFlowResult, DunningResponse };\n\nexport class ChurnBackClient {\n private api: ApiClient;\n private activeModal: CancelFlowModal | null = null;\n private activeBanners: DunningBanner[] = [];\n\n constructor(config: ChurnBackConfig) {\n if (!config.apiKey) {\n throw new Error(\"[ChurnBack] apiKey is required\");\n }\n const resolved = resolveConfig(config);\n this.api = new ApiClient(resolved);\n }\n\n async triggerCancelFlow(options: CancelFlowOptions): Promise<void> {\n this.activeModal?.destroy();\n\n const modal = new CancelFlowModal(this.api, options);\n this.activeModal = modal;\n\n try {\n await modal.open();\n } catch (err) {\n // If onError is provided, the consumer handles it — don't re-throw\n if (options.onError) return;\n throw err;\n }\n }\n\n async checkDunning(options: { stripeCustomerId: string }): Promise<DunningResponse> {\n return this.api.checkDunning(options.stripeCustomerId);\n }\n\n mountDunningBanner(options: DunningBannerOptions): void {\n const banner = new DunningBanner(this.api, options);\n this.activeBanners.push(banner);\n banner.mount();\n }\n\n destroy(): void {\n this.activeModal?.destroy();\n this.activeModal = null;\n this.activeBanners.forEach((b) => b.destroy());\n this.activeBanners = [];\n }\n}\n\nexport default ChurnBackClient;\n","export interface ChurnBackConfig {\n apiKey: string;\n apiBaseUrl?: string;\n}\n\nexport interface ResolvedConfig {\n apiKey: string;\n apiBaseUrl: string;\n}\n\nexport function resolveConfig(config: ChurnBackConfig): ResolvedConfig {\n return {\n apiKey: config.apiKey,\n apiBaseUrl: (config.apiBaseUrl || \"https://churnback.ai/api/sdk\").replace(/\\/$/, \"\"),\n };\n}\n","import type { ResolvedConfig } from \"./config\";\n\nexport interface FlowResponse {\n sessionId: string;\n steps: FlowStep[];\n style: Record<string, string>;\n}\n\nexport interface FlowStep {\n id: string;\n type: \"reason_survey\" | \"offer\" | \"custom_message\" | \"confirmation\";\n config: Record<string, unknown>;\n}\n\nexport interface SubmitPayload {\n sessionId: string;\n action: \"offer_accepted\" | \"cancelled\" | \"abandoned\";\n reason?: string;\n comment?: string;\n acceptedOffer?: Record<string, unknown>;\n}\n\nexport interface DunningResponse {\n hasFailedPayment: boolean;\n amountDueCents?: number;\n updatePaymentUrl?: string;\n bannerConfig?: {\n headline: string;\n body: string;\n ctaText: string;\n };\n}\n\nexport class ApiClient {\n constructor(private config: ResolvedConfig) {}\n\n async fetchCancelFlow(\n customerId: string,\n subscriptionId: string,\n priceId?: string\n ): Promise<FlowResponse> {\n const params = new URLSearchParams({\n customer_id: customerId,\n subscription_id: subscriptionId,\n });\n if (priceId) params.set(\"price_id\", priceId);\n\n const res = await fetch(`${this.config.apiBaseUrl}/cancel-flow?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to fetch cancel flow (${res.status})`);\n }\n\n return res.json();\n }\n\n async submitResult(payload: SubmitPayload): Promise<void> {\n const res = await fetch(`${this.config.apiBaseUrl}/submit`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-churnback-key\": this.config.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to submit result (${res.status})`);\n }\n }\n\n async checkDunning(customerId: string): Promise<DunningResponse> {\n const params = new URLSearchParams({ customer_id: customerId });\n\n const res = await fetch(`${this.config.apiBaseUrl}/dunning?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n return { hasFailedPayment: false };\n }\n\n return res.json();\n }\n}\n","export const MODAL_STYLES = `\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n .cb-overlay {\n position: fixed;\n inset: 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 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n animation: cb-fade-in 0.2s ease;\n }\n\n @keyframes cb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes cb-slide-up {\n from { transform: translateY(20px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n }\n\n .cb-modal {\n background: white;\n border-radius: 16px;\n padding: 32px;\n max-width: 480px;\n width: 90%;\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n animation: cb-slide-up 0.3s ease;\n }\n\n .cb-headline {\n font-size: 22px;\n font-weight: 700;\n color: #1a1a1a;\n margin-bottom: 12px;\n line-height: 1.3;\n }\n\n .cb-body {\n font-size: 15px;\n color: #555;\n line-height: 1.6;\n margin-bottom: 20px;\n }\n\n .cb-reasons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 16px;\n }\n\n .cb-reason {\n padding: 12px 16px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n transition: all 0.15s ease;\n background: white;\n text-align: left;\n }\n\n .cb-reason:hover {\n border-color: #7c3aed;\n background: #f5f3ff;\n }\n\n .cb-reason.selected {\n border-color: #7c3aed;\n background: #f5f3ff;\n color: #7c3aed;\n font-weight: 500;\n }\n\n .cb-textarea {\n width: 100%;\n min-height: 80px;\n padding: 12px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n font-size: 14px;\n font-family: inherit;\n resize: vertical;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .cb-textarea:focus {\n border-color: #7c3aed;\n }\n\n .cb-btn {\n display: block;\n width: 100%;\n padding: 12px 20px;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n border: none;\n transition: all 0.15s ease;\n margin-bottom: 8px;\n }\n\n .cb-btn-primary {\n background: #7c3aed;\n color: white;\n }\n\n .cb-btn-primary:hover {\n background: #6d28d9;\n }\n\n .cb-btn-danger {\n background: #ef4444;\n color: white;\n }\n\n .cb-btn-danger:hover {\n background: #dc2626;\n }\n\n .cb-btn-secondary {\n background: transparent;\n color: #7c3aed;\n border: 2px solid #7c3aed;\n }\n\n .cb-btn-secondary:hover {\n background: #f5f3ff;\n }\n\n .cb-btn-ghost {\n background: transparent;\n color: #888;\n }\n\n .cb-btn-ghost:hover {\n color: #555;\n }\n\n .cb-step-dots {\n display: flex;\n justify-content: center;\n gap: 6px;\n margin-top: 16px;\n }\n\n .cb-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #e0e0e0;\n transition: background 0.2s ease;\n }\n\n .cb-dot.active {\n background: #7c3aed;\n }\n\n .cb-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n font-size: 24px;\n color: #888;\n cursor: pointer;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n }\n\n .cb-close:hover {\n background: #f0f0f0;\n color: #333;\n }\n\n .cb-modal-inner {\n position: relative;\n }\n`;\n","import type { FlowStep } from \"../core/api\";\n\nexport interface StepState {\n currentIndex: number;\n selectedReason: string | null;\n comment: string;\n totalSteps: number;\n}\n\nexport function renderStep(\n step: FlowStep,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n): HTMLElement {\n const container = document.createElement(\"div\");\n\n switch (step.type) {\n case \"reason_survey\":\n renderReasonSurvey(container, step.config, state, callbacks);\n break;\n case \"offer\":\n renderOffer(container, step.config, callbacks);\n break;\n case \"custom_message\":\n renderCustomMessage(container, step.config, {\n onNext: callbacks.onNext,\n onClose: callbacks.onGoBack,\n });\n break;\n case \"confirmation\":\n renderConfirmation(container, step.config, callbacks);\n break;\n }\n\n // Step dots\n if (state.totalSteps > 1) {\n const dots = document.createElement(\"div\");\n dots.className = \"cb-step-dots\";\n for (let i = 0; i < state.totalSteps; i++) {\n const dot = document.createElement(\"div\");\n dot.className = `cb-dot${i === state.currentIndex ? \" active\" : \"\"}`;\n dots.appendChild(dot);\n }\n container.appendChild(dots);\n }\n\n return container;\n}\n\nfunction renderReasonSurvey(\n container: HTMLElement,\n config: Record<string, unknown>,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Why are you cancelling?\";\n container.appendChild(headline);\n\n const reasons = (config.reasons as Array<{ id: string; label: string }>) || [];\n const reasonsDiv = document.createElement(\"div\");\n reasonsDiv.className = \"cb-reasons\";\n\n reasons.forEach((r) => {\n const btn = document.createElement(\"button\");\n btn.className = `cb-reason${state.selectedReason === r.id ? \" selected\" : \"\"}`;\n btn.textContent = r.label;\n btn.onclick = () => {\n callbacks.onSelectReason(r.id);\n // Update selection visually\n reasonsDiv.querySelectorAll(\".cb-reason\").forEach((el) => el.classList.remove(\"selected\"));\n btn.classList.add(\"selected\");\n };\n reasonsDiv.appendChild(btn);\n });\n container.appendChild(reasonsDiv);\n\n if (config.allow_comment) {\n const textarea = document.createElement(\"textarea\");\n textarea.className = \"cb-textarea\";\n textarea.placeholder = (config.comment_placeholder as string) || \"Tell us more...\";\n textarea.value = state.comment;\n textarea.oninput = () => callbacks.onComment(textarea.value);\n container.appendChild(textarea);\n }\n\n const nextBtn = document.createElement(\"button\");\n nextBtn.className = \"cb-btn cb-btn-primary\";\n nextBtn.textContent = \"Continue\";\n nextBtn.onclick = callbacks.onNext;\n container.appendChild(nextBtn);\n}\n\nfunction renderOffer(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Special offer for you\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const acceptBtn = document.createElement(\"button\");\n acceptBtn.className = \"cb-btn cb-btn-primary\";\n acceptBtn.textContent = (config.cta_accept as string) || \"Accept Offer\";\n acceptBtn.onclick = () => callbacks.onAcceptOffer(config);\n container.appendChild(acceptBtn);\n\n const declineBtn = document.createElement(\"button\");\n declineBtn.className = \"cb-btn cb-btn-ghost\";\n declineBtn.textContent = (config.cta_decline as string) || \"No thanks\";\n declineBtn.onclick = callbacks.onDeclineOffer;\n container.appendChild(declineBtn);\n}\n\nfunction renderCustomMessage(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: { onNext: () => void; onClose: () => void }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n if (config.cta_text) {\n const btn = document.createElement(\"button\");\n btn.className = \"cb-btn cb-btn-primary\";\n btn.textContent = config.cta_text as string;\n btn.onclick = config.cta_action === \"close\" ? callbacks.onClose : callbacks.onNext;\n container.appendChild(btn);\n }\n}\n\nfunction renderConfirmation(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Are you sure?\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const goBackBtn = document.createElement(\"button\");\n goBackBtn.className = \"cb-btn cb-btn-primary\";\n goBackBtn.textContent = (config.go_back_text as string) || \"Keep my subscription\";\n goBackBtn.onclick = callbacks.onGoBack;\n container.appendChild(goBackBtn);\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.className = \"cb-btn cb-btn-danger\";\n cancelBtn.textContent = (config.confirm_cancel_text as string) || \"Yes, cancel\";\n cancelBtn.onclick = callbacks.onConfirmCancel;\n container.appendChild(cancelBtn);\n}\n","import type { ApiClient, FlowStep, FlowResponse } from \"../core/api\";\nimport { MODAL_STYLES } from \"./styles\";\nimport { renderStep, type StepState } from \"./steps\";\n\nexport interface CancelFlowOptions {\n stripeCustomerId: string;\n stripeSubscriptionId: string;\n stripePriceId?: string;\n onComplete?: (result: CancelFlowResult) => void;\n onDismiss?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface CancelFlowResult {\n action: \"saved\" | \"cancelled\" | \"dismissed\";\n reason?: string;\n comment?: string;\n offerAccepted?: Record<string, unknown>;\n}\n\nexport class CancelFlowModal {\n private shadowHost: HTMLElement | null = null;\n private shadowRoot: ShadowRoot | null = null;\n private state: StepState;\n private steps: FlowStep[] = [];\n private sessionId = \"\";\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: CancelFlowOptions\n ) {\n this.state = {\n currentIndex: 0,\n selectedReason: null,\n comment: \"\",\n totalSteps: 0,\n };\n }\n\n async open(): Promise<void> {\n // Fetch flow config\n let flow: FlowResponse;\n try {\n flow = await this.api.fetchCancelFlow(\n this.options.stripeCustomerId,\n this.options.stripeSubscriptionId,\n this.options.stripePriceId\n );\n } catch (err) {\n console.error(\"[ChurnBack] Failed to load cancel flow:\", err);\n const error = err instanceof Error ? err : new Error(String(err));\n this.options.onError?.(error);\n throw err;\n }\n\n if (this.destroyed) return;\n\n this.sessionId = flow.sessionId;\n this.steps = flow.steps;\n this.state.totalSteps = flow.steps.length;\n\n // Create Shadow DOM host\n this.shadowHost = document.createElement(\"div\");\n this.shadowHost.id = \"churnback-modal\";\n document.body.appendChild(this.shadowHost);\n this.shadowRoot = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n // Inject styles\n const style = document.createElement(\"style\");\n style.textContent = MODAL_STYLES;\n this.shadowRoot.appendChild(style);\n\n this.renderCurrentStep();\n }\n\n private renderCurrentStep(): void {\n if (!this.shadowRoot || this.destroyed) return;\n\n // Remove old content (keep style)\n const existing = this.shadowRoot.querySelector(\".cb-overlay\");\n if (existing) existing.remove();\n\n const step = this.steps[this.state.currentIndex];\n if (!step) return;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"cb-overlay\";\n overlay.onclick = (e) => {\n if (e.target === overlay) this.dismiss();\n };\n\n const modal = document.createElement(\"div\");\n modal.className = \"cb-modal\";\n\n const inner = document.createElement(\"div\");\n inner.className = \"cb-modal-inner\";\n\n // Close button\n const closeBtn = document.createElement(\"button\");\n closeBtn.className = \"cb-close\";\n closeBtn.innerHTML = \"×\";\n closeBtn.onclick = () => this.dismiss();\n inner.appendChild(closeBtn);\n\n const stepContent = renderStep(step, this.state, {\n onNext: () => this.nextStep(),\n onSelectReason: (id) => {\n this.state.selectedReason = id;\n },\n onComment: (text) => {\n this.state.comment = text;\n },\n onAcceptOffer: (offer) => this.acceptOffer(offer),\n onDeclineOffer: () => { console.log(\"[ChurnBack] Decline offer clicked\"); this.nextStep(); },\n onConfirmCancel: () => this.confirmCancel(),\n onGoBack: () => this.dismiss(),\n });\n\n inner.appendChild(stepContent);\n modal.appendChild(inner);\n overlay.appendChild(modal);\n this.shadowRoot.appendChild(overlay);\n }\n\n private nextStep(): void {\n console.log(`[ChurnBack] nextStep: index=${this.state.currentIndex}, total=${this.steps.length}`);\n if (this.state.currentIndex < this.steps.length - 1) {\n this.state.currentIndex++;\n console.log(\"[ChurnBack] Advancing to step\", this.state.currentIndex);\n this.renderCurrentStep();\n } else {\n console.log(\"[ChurnBack] Last step — calling confirmCancel\");\n this.confirmCancel();\n }\n }\n\n private acceptOffer(offer: Record<string, unknown>): void {\n this.close();\n this.options.onComplete?.({\n action: \"saved\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n offerAccepted: offer,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"offer_accepted\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n acceptedOffer: offer,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit offer acceptance:\", err));\n }\n\n private confirmCancel(): void {\n console.log(\"[ChurnBack] confirmCancel called\");\n this.close();\n console.log(\"[ChurnBack] modal closed\");\n this.options.onComplete?.({\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit cancellation:\", err));\n }\n\n private dismiss(): void {\n this.close();\n this.options.onDismiss?.();\n this.options.onComplete?.({ action: \"dismissed\" });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"abandoned\",\n }).catch(() => { /* Silent fail on dismiss */ });\n }\n\n private close(): void {\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n this.shadowRoot = null;\n }\n\n destroy(): void {\n this.destroyed = true;\n this.close();\n }\n}\n","export const BANNER_STYLES = `\n .cb-dunning-banner {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 10px;\n padding: 16px 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n }\n\n .cb-dunning-content {\n flex: 1;\n min-width: 200px;\n }\n\n .cb-dunning-headline {\n font-size: 15px;\n font-weight: 600;\n color: #991b1b;\n margin-bottom: 4px;\n }\n\n .cb-dunning-body {\n font-size: 13px;\n color: #b91c1c;\n line-height: 1.4;\n }\n\n .cb-dunning-cta {\n padding: 8px 16px;\n background: #ef4444;\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.15s ease;\n text-decoration: none;\n display: inline-block;\n }\n\n .cb-dunning-cta:hover {\n background: #dc2626;\n }\n\n .cb-dunning-hidden {\n display: none;\n }\n`;\n","import type { ApiClient, DunningResponse } from \"../core/api\";\nimport { BANNER_STYLES } from \"./styles\";\n\nexport interface DunningBannerOptions {\n stripeCustomerId: string;\n targetElement: string | HTMLElement;\n}\n\nexport class DunningBanner {\n private shadowHost: HTMLElement | null = null;\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: DunningBannerOptions\n ) {}\n\n async mount(): Promise<void> {\n let dunning: DunningResponse;\n try {\n dunning = await this.api.checkDunning(this.options.stripeCustomerId);\n } catch {\n return; // Silent fail\n }\n\n if (this.destroyed || !dunning.hasFailedPayment) return;\n\n const target =\n typeof this.options.targetElement === \"string\"\n ? document.querySelector(this.options.targetElement)\n : this.options.targetElement;\n\n if (!target) {\n console.warn(\"[ChurnBack] Dunning banner target element not found\");\n return;\n }\n\n this.shadowHost = document.createElement(\"div\");\n const shadow = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n const style = document.createElement(\"style\");\n style.textContent = BANNER_STYLES;\n shadow.appendChild(style);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cb-dunning-banner\";\n\n const content = document.createElement(\"div\");\n content.className = \"cb-dunning-content\";\n\n const headline = document.createElement(\"div\");\n headline.className = \"cb-dunning-headline\";\n headline.textContent = dunning.bannerConfig?.headline || \"Payment failed\";\n\n const body = document.createElement(\"div\");\n body.className = \"cb-dunning-body\";\n body.textContent =\n dunning.bannerConfig?.body ||\n `We were unable to process your payment${dunning.amountDueCents ? ` of $${(dunning.amountDueCents / 100).toFixed(2)}` : \"\"}. Please update your payment method to avoid service interruption.`;\n\n content.appendChild(headline);\n content.appendChild(body);\n banner.appendChild(content);\n\n if (dunning.updatePaymentUrl) {\n const cta = document.createElement(\"a\");\n cta.className = \"cb-dunning-cta\";\n cta.href = dunning.updatePaymentUrl;\n cta.target = \"_blank\";\n cta.rel = \"noopener noreferrer\";\n cta.textContent = dunning.bannerConfig?.ctaText || \"Update Payment\";\n banner.appendChild(cta);\n }\n\n shadow.appendChild(banner);\n target.appendChild(this.shadowHost);\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n }\n}\n"],"mappings":"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","names":["index_exports","__export","ChurnBackClient","index_default","resolveConfig","config","ApiClient","config","customerId","subscriptionId","priceId","params","res","body","payload","MODAL_STYLES","renderStep","step","state","callbacks","container","renderReasonSurvey","renderOffer","renderCustomMessage","renderConfirmation","dots","dot","config","headline","reasons","reasonsDiv","btn","el","textarea","nextBtn","body","acceptBtn","declineBtn","goBackBtn","cancelBtn","CancelFlowModal","api","options","flow","err","error","style","MODAL_STYLES","existing","step","overlay","e","modal","inner","closeBtn","stepContent","renderStep","id","text","offer","BANNER_STYLES","DunningBanner","api","options","dunning","target","shadow","style","BANNER_STYLES","banner","content","headline","body","cta","ChurnBackClient","config","resolved","resolveConfig","ApiClient","options","modal","CancelFlowModal","err","banner","DunningBanner","b","index_default"]}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var u=Object.defineProperty;var
|
|
1
|
+
"use strict";var u=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var k=(o,e)=>{for(var t in e)u(o,t,{get:e[t],enumerable:!0})},S=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of w(e))!v.call(o,s)&&s!==t&&u(o,s,{get:()=>e[s],enumerable:!(n=y(e,s))||n.enumerable});return o};var E=o=>S(u({},"__esModule",{value:!0}),o);var D={};k(D,{ChurnBackClient:()=>h,default:()=>I});module.exports=E(D);function f(o){return{apiKey:o.apiKey,apiBaseUrl:(o.apiBaseUrl||"https://churnback.ai/api/sdk").replace(/\/$/,"")}}var l=class{constructor(e){this.config=e}async fetchCancelFlow(e,t,n){let s=new URLSearchParams({customer_id:e,subscription_id:t});n&&s.set("price_id",n);let i=await fetch(`${this.config.apiBaseUrl}/cancel-flow?${s}`,{headers:{"x-churnback-key":this.config.apiKey}});if(!i.ok){let a=await i.json().catch(()=>({}));throw new Error(a.error||`Failed to fetch cancel flow (${i.status})`)}return i.json()}async submitResult(e){let t=await fetch(`${this.config.apiBaseUrl}/submit`,{method:"POST",headers:{"Content-Type":"application/json","x-churnback-key":this.config.apiKey},body:JSON.stringify(e)});if(!t.ok){let n=await t.json().catch(()=>({}));throw new Error(n.error||`Failed to submit result (${t.status})`)}}async checkDunning(e){let t=new URLSearchParams({customer_id:e}),n=await fetch(`${this.config.apiBaseUrl}/dunning?${t}`,{headers:{"x-churnback-key":this.config.apiKey}});return n.ok?n.json():{hasFailedPayment:!1}}};var b=`
|
|
2
2
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
3
3
|
|
|
4
4
|
.cb-overlay {
|
|
@@ -193,7 +193,7 @@
|
|
|
193
193
|
.cb-modal-inner {
|
|
194
194
|
position: relative;
|
|
195
195
|
}
|
|
196
|
-
`;function g(o,e,t){let n=document.createElement("div");switch(o.type){case"reason_survey":R(n,o.config,e,t);break;case"offer":
|
|
196
|
+
`;function g(o,e,t){let n=document.createElement("div");switch(o.type){case"reason_survey":R(n,o.config,e,t);break;case"offer":B(n,o.config,t);break;case"custom_message":N(n,o.config,{onNext:t.onNext,onClose:t.onGoBack});break;case"confirmation":F(n,o.config,t);break}if(e.totalSteps>1){let s=document.createElement("div");s.className="cb-step-dots";for(let i=0;i<e.totalSteps;i++){let a=document.createElement("div");a.className=`cb-dot${i===e.currentIndex?" active":""}`,s.appendChild(a)}n.appendChild(s)}return n}function R(o,e,t,n){let s=document.createElement("h2");s.className="cb-headline",s.textContent=e.headline||"Why are you cancelling?",o.appendChild(s);let i=e.reasons||[],a=document.createElement("div");if(a.className="cb-reasons",i.forEach(r=>{let c=document.createElement("button");c.className=`cb-reason${t.selectedReason===r.id?" selected":""}`,c.textContent=r.label,c.onclick=()=>{n.onSelectReason(r.id),a.querySelectorAll(".cb-reason").forEach(C=>C.classList.remove("selected")),c.classList.add("selected")},a.appendChild(c)}),o.appendChild(a),e.allow_comment){let r=document.createElement("textarea");r.className="cb-textarea",r.placeholder=e.comment_placeholder||"Tell us more...",r.value=t.comment,r.oninput=()=>n.onComment(r.value),o.appendChild(r)}let d=document.createElement("button");d.className="cb-btn cb-btn-primary",d.textContent="Continue",d.onclick=n.onNext,o.appendChild(d)}function B(o,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"Special offer for you",o.appendChild(n);let s=document.createElement("p");s.className="cb-body",s.textContent=e.body||"",o.appendChild(s);let i=document.createElement("button");i.className="cb-btn cb-btn-primary",i.textContent=e.cta_accept||"Accept Offer",i.onclick=()=>t.onAcceptOffer(e),o.appendChild(i);let a=document.createElement("button");a.className="cb-btn cb-btn-ghost",a.textContent=e.cta_decline||"No thanks",a.onclick=t.onDeclineOffer,o.appendChild(a)}function N(o,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"",o.appendChild(n);let s=document.createElement("p");if(s.className="cb-body",s.textContent=e.body||"",o.appendChild(s),e.cta_text){let i=document.createElement("button");i.className="cb-btn cb-btn-primary",i.textContent=e.cta_text,i.onclick=e.cta_action==="close"?t.onClose:t.onNext,o.appendChild(i)}}function F(o,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"Are you sure?",o.appendChild(n);let s=document.createElement("p");s.className="cb-body",s.textContent=e.body||"",o.appendChild(s);let i=document.createElement("button");i.className="cb-btn cb-btn-primary",i.textContent=e.go_back_text||"Keep my subscription",i.onclick=t.onGoBack,o.appendChild(i);let a=document.createElement("button");a.className="cb-btn cb-btn-danger",a.textContent=e.confirm_cancel_text||"Yes, cancel",a.onclick=t.onConfirmCancel,o.appendChild(a)}var p=class{constructor(e,t){this.api=e;this.options=t;this.shadowHost=null;this.shadowRoot=null;this.steps=[];this.sessionId="";this.destroyed=!1;this.state={currentIndex:0,selectedReason:null,comment:"",totalSteps:0}}async open(){let e;try{e=await this.api.fetchCancelFlow(this.options.stripeCustomerId,this.options.stripeSubscriptionId,this.options.stripePriceId)}catch(n){console.error("[ChurnBack] Failed to load cancel flow:",n);let s=n instanceof Error?n:new Error(String(n));throw this.options.onError?.(s),n}if(this.destroyed)return;this.sessionId=e.sessionId,this.steps=e.steps,this.state.totalSteps=e.steps.length,this.shadowHost=document.createElement("div"),this.shadowHost.id="churnback-modal",document.body.appendChild(this.shadowHost),this.shadowRoot=this.shadowHost.attachShadow({mode:"closed"});let t=document.createElement("style");t.textContent=b,this.shadowRoot.appendChild(t),this.renderCurrentStep()}renderCurrentStep(){if(!this.shadowRoot||this.destroyed)return;let e=this.shadowRoot.querySelector(".cb-overlay");e&&e.remove();let t=this.steps[this.state.currentIndex];if(!t)return;let n=document.createElement("div");n.className="cb-overlay",n.onclick=r=>{r.target===n&&this.dismiss()};let s=document.createElement("div");s.className="cb-modal";let i=document.createElement("div");i.className="cb-modal-inner";let a=document.createElement("button");a.className="cb-close",a.innerHTML="×",a.onclick=()=>this.dismiss(),i.appendChild(a);let d=g(t,this.state,{onNext:()=>this.nextStep(),onSelectReason:r=>{this.state.selectedReason=r},onComment:r=>{this.state.comment=r},onAcceptOffer:r=>this.acceptOffer(r),onDeclineOffer:()=>{console.log("[ChurnBack] Decline offer clicked"),this.nextStep()},onConfirmCancel:()=>this.confirmCancel(),onGoBack:()=>this.dismiss()});i.appendChild(d),s.appendChild(i),n.appendChild(s),this.shadowRoot.appendChild(n)}nextStep(){console.log(`[ChurnBack] nextStep: index=${this.state.currentIndex}, total=${this.steps.length}`),this.state.currentIndex<this.steps.length-1?(this.state.currentIndex++,console.log("[ChurnBack] Advancing to step",this.state.currentIndex),this.renderCurrentStep()):(console.log("[ChurnBack] Last step \u2014 calling confirmCancel"),this.confirmCancel())}acceptOffer(e){this.close(),this.options.onComplete?.({action:"saved",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0,offerAccepted:e}),this.api.submitResult({sessionId:this.sessionId,action:"offer_accepted",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0,acceptedOffer:e}).catch(t=>console.error("[ChurnBack] Failed to submit offer acceptance:",t))}confirmCancel(){console.log("[ChurnBack] confirmCancel called"),this.close(),console.log("[ChurnBack] modal closed"),this.options.onComplete?.({action:"cancelled",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0}),this.api.submitResult({sessionId:this.sessionId,action:"cancelled",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0}).catch(e=>console.error("[ChurnBack] Failed to submit cancellation:",e))}dismiss(){this.close(),this.options.onDismiss?.(),this.options.onComplete?.({action:"dismissed"}),this.api.submitResult({sessionId:this.sessionId,action:"abandoned"}).catch(()=>{})}close(){this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null,this.shadowRoot=null}destroy(){this.destroyed=!0,this.close()}};var x=`
|
|
197
197
|
.cb-dunning-banner {
|
|
198
198
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
199
199
|
background: #fef2f2;
|
|
@@ -247,6 +247,6 @@
|
|
|
247
247
|
.cb-dunning-hidden {
|
|
248
248
|
display: none;
|
|
249
249
|
}
|
|
250
|
-
`;var m=class{constructor(e,t){this.api=e;this.options=t;this.shadowHost=null;this.destroyed=!1}async mount(){let e;try{e=await this.api.checkDunning(this.options.stripeCustomerId)}catch{return}if(this.destroyed||!e.hasFailedPayment)return;let t=typeof this.options.targetElement=="string"?document.querySelector(this.options.targetElement):this.options.targetElement;if(!t){console.warn("[ChurnBack] Dunning banner target element not found");return}this.shadowHost=document.createElement("div");let n=this.shadowHost.attachShadow({mode:"closed"}),s=document.createElement("style");s.textContent=x,n.appendChild(s);let i=document.createElement("div");i.className="cb-dunning-banner";let a=document.createElement("div");a.className="cb-dunning-content";let d=document.createElement("div");d.className="cb-dunning-headline",d.textContent=e.bannerConfig?.headline||"Payment failed";let r=document.createElement("div");if(r.className="cb-dunning-body",r.textContent=e.bannerConfig?.body||`We were unable to process your payment${e.amountDueCents?` of $${(e.amountDueCents/100).toFixed(2)}`:""}. Please update your payment method to avoid service interruption.`,a.appendChild(d),a.appendChild(r),i.appendChild(a),e.updatePaymentUrl){let c=document.createElement("a");c.className="cb-dunning-cta",c.href=e.updatePaymentUrl,c.target="_blank",c.rel="noopener noreferrer",c.textContent=e.bannerConfig?.ctaText||"Update Payment",i.appendChild(c)}n.appendChild(i),t.appendChild(this.shadowHost)}destroy(){this.destroyed=!0,this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null}};var h=class{constructor(e){this.activeModal=null;this.activeBanners=[];if(!e.apiKey)throw new Error("[ChurnBack] apiKey is required");let t=f(e);this.api=new l(t)}async triggerCancelFlow(e){this.activeModal?.destroy();let t=new p(this.api,e);this.activeModal=t;try{await t.open()}catch(n){if(e.onError)return;throw n}}async checkDunning(e){return this.api.checkDunning(e.stripeCustomerId)}mountDunningBanner(e){let t=new m(this.api,e);this.activeBanners.push(t),t.mount()}destroy(){this.activeModal?.destroy(),this.activeModal=null,this.activeBanners.forEach(e=>e.destroy()),this.activeBanners=[]}},
|
|
250
|
+
`;var m=class{constructor(e,t){this.api=e;this.options=t;this.shadowHost=null;this.destroyed=!1}async mount(){let e;try{e=await this.api.checkDunning(this.options.stripeCustomerId)}catch{return}if(this.destroyed||!e.hasFailedPayment)return;let t=typeof this.options.targetElement=="string"?document.querySelector(this.options.targetElement):this.options.targetElement;if(!t){console.warn("[ChurnBack] Dunning banner target element not found");return}this.shadowHost=document.createElement("div");let n=this.shadowHost.attachShadow({mode:"closed"}),s=document.createElement("style");s.textContent=x,n.appendChild(s);let i=document.createElement("div");i.className="cb-dunning-banner";let a=document.createElement("div");a.className="cb-dunning-content";let d=document.createElement("div");d.className="cb-dunning-headline",d.textContent=e.bannerConfig?.headline||"Payment failed";let r=document.createElement("div");if(r.className="cb-dunning-body",r.textContent=e.bannerConfig?.body||`We were unable to process your payment${e.amountDueCents?` of $${(e.amountDueCents/100).toFixed(2)}`:""}. Please update your payment method to avoid service interruption.`,a.appendChild(d),a.appendChild(r),i.appendChild(a),e.updatePaymentUrl){let c=document.createElement("a");c.className="cb-dunning-cta",c.href=e.updatePaymentUrl,c.target="_blank",c.rel="noopener noreferrer",c.textContent=e.bannerConfig?.ctaText||"Update Payment",i.appendChild(c)}n.appendChild(i),t.appendChild(this.shadowHost)}destroy(){this.destroyed=!0,this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null}};var h=class{constructor(e){this.activeModal=null;this.activeBanners=[];if(!e.apiKey)throw new Error("[ChurnBack] apiKey is required");let t=f(e);this.api=new l(t)}async triggerCancelFlow(e){this.activeModal?.destroy();let t=new p(this.api,e);this.activeModal=t;try{await t.open()}catch(n){if(e.onError)return;throw n}}async checkDunning(e){return this.api.checkDunning(e.stripeCustomerId)}mountDunningBanner(e){let t=new m(this.api,e);this.activeBanners.push(t),t.mount()}destroy(){this.activeModal?.destroy(),this.activeModal=null,this.activeBanners.forEach(e=>e.destroy()),this.activeBanners=[]}},I=h;0&&(module.exports={ChurnBackClient});
|
|
251
251
|
if(typeof ChurnBack!=='undefined'&&ChurnBack.default){ChurnBack=ChurnBack.default;}
|
|
252
252
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/config.ts","../src/core/api.ts","../src/cancel-flow/styles.ts","../src/cancel-flow/steps.ts","../src/cancel-flow/modal.ts","../src/dunning/styles.ts","../src/dunning/banner.ts"],"sourcesContent":["import { resolveConfig, type ChurnBackConfig } from \"./core/config\";\nimport { ApiClient, type DunningResponse } from \"./core/api\";\nimport { CancelFlowModal, type CancelFlowOptions, type CancelFlowResult } from \"./cancel-flow/modal\";\nimport { DunningBanner, type DunningBannerOptions } from \"./dunning/banner\";\n\nexport type { ChurnBackConfig, CancelFlowOptions, CancelFlowResult, DunningResponse };\n\nexport class ChurnBackClient {\n private api: ApiClient;\n private activeModal: CancelFlowModal | null = null;\n private activeBanners: DunningBanner[] = [];\n\n constructor(config: ChurnBackConfig) {\n if (!config.apiKey) {\n throw new Error(\"[ChurnBack] apiKey is required\");\n }\n const resolved = resolveConfig(config);\n this.api = new ApiClient(resolved);\n }\n\n async triggerCancelFlow(options: CancelFlowOptions): Promise<void> {\n this.activeModal?.destroy();\n\n const modal = new CancelFlowModal(this.api, options);\n this.activeModal = modal;\n\n try {\n await modal.open();\n } catch (err) {\n // If onError is provided, the consumer handles it — don't re-throw\n if (options.onError) return;\n throw err;\n }\n }\n\n async checkDunning(options: { stripeCustomerId: string }): Promise<DunningResponse> {\n return this.api.checkDunning(options.stripeCustomerId);\n }\n\n mountDunningBanner(options: DunningBannerOptions): void {\n const banner = new DunningBanner(this.api, options);\n this.activeBanners.push(banner);\n banner.mount();\n }\n\n destroy(): void {\n this.activeModal?.destroy();\n this.activeModal = null;\n this.activeBanners.forEach((b) => b.destroy());\n this.activeBanners = [];\n }\n}\n\nexport default ChurnBackClient;\n","export interface ChurnBackConfig {\n apiKey: string;\n apiBaseUrl?: string;\n}\n\nexport interface ResolvedConfig {\n apiKey: string;\n apiBaseUrl: string;\n}\n\nexport function resolveConfig(config: ChurnBackConfig): ResolvedConfig {\n return {\n apiKey: config.apiKey,\n apiBaseUrl: (config.apiBaseUrl || \"https://churnback.ai/api/sdk\").replace(/\\/$/, \"\"),\n };\n}\n","import type { ResolvedConfig } from \"./config\";\n\nexport interface FlowResponse {\n sessionId: string;\n steps: FlowStep[];\n style: Record<string, string>;\n}\n\nexport interface FlowStep {\n id: string;\n type: \"reason_survey\" | \"offer\" | \"custom_message\" | \"confirmation\";\n config: Record<string, unknown>;\n}\n\nexport interface SubmitPayload {\n sessionId: string;\n action: \"offer_accepted\" | \"cancelled\" | \"abandoned\";\n reason?: string;\n comment?: string;\n acceptedOffer?: Record<string, unknown>;\n}\n\nexport interface DunningResponse {\n hasFailedPayment: boolean;\n amountDueCents?: number;\n updatePaymentUrl?: string;\n bannerConfig?: {\n headline: string;\n body: string;\n ctaText: string;\n };\n}\n\nexport class ApiClient {\n constructor(private config: ResolvedConfig) {}\n\n async fetchCancelFlow(\n customerId: string,\n subscriptionId: string,\n priceId?: string\n ): Promise<FlowResponse> {\n const params = new URLSearchParams({\n customer_id: customerId,\n subscription_id: subscriptionId,\n });\n if (priceId) params.set(\"price_id\", priceId);\n\n const res = await fetch(`${this.config.apiBaseUrl}/cancel-flow?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to fetch cancel flow (${res.status})`);\n }\n\n return res.json();\n }\n\n async submitResult(payload: SubmitPayload): Promise<void> {\n const res = await fetch(`${this.config.apiBaseUrl}/submit`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-churnback-key\": this.config.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to submit result (${res.status})`);\n }\n }\n\n async checkDunning(customerId: string): Promise<DunningResponse> {\n const params = new URLSearchParams({ customer_id: customerId });\n\n const res = await fetch(`${this.config.apiBaseUrl}/dunning?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n return { hasFailedPayment: false };\n }\n\n return res.json();\n }\n}\n","export const MODAL_STYLES = `\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n .cb-overlay {\n position: fixed;\n inset: 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 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n animation: cb-fade-in 0.2s ease;\n }\n\n @keyframes cb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes cb-slide-up {\n from { transform: translateY(20px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n }\n\n .cb-modal {\n background: white;\n border-radius: 16px;\n padding: 32px;\n max-width: 480px;\n width: 90%;\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n animation: cb-slide-up 0.3s ease;\n }\n\n .cb-headline {\n font-size: 22px;\n font-weight: 700;\n color: #1a1a1a;\n margin-bottom: 12px;\n line-height: 1.3;\n }\n\n .cb-body {\n font-size: 15px;\n color: #555;\n line-height: 1.6;\n margin-bottom: 20px;\n }\n\n .cb-reasons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 16px;\n }\n\n .cb-reason {\n padding: 12px 16px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n transition: all 0.15s ease;\n background: white;\n text-align: left;\n }\n\n .cb-reason:hover {\n border-color: #7c3aed;\n background: #f5f3ff;\n }\n\n .cb-reason.selected {\n border-color: #7c3aed;\n background: #f5f3ff;\n color: #7c3aed;\n font-weight: 500;\n }\n\n .cb-textarea {\n width: 100%;\n min-height: 80px;\n padding: 12px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n font-size: 14px;\n font-family: inherit;\n resize: vertical;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .cb-textarea:focus {\n border-color: #7c3aed;\n }\n\n .cb-btn {\n display: block;\n width: 100%;\n padding: 12px 20px;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n border: none;\n transition: all 0.15s ease;\n margin-bottom: 8px;\n }\n\n .cb-btn-primary {\n background: #7c3aed;\n color: white;\n }\n\n .cb-btn-primary:hover {\n background: #6d28d9;\n }\n\n .cb-btn-danger {\n background: #ef4444;\n color: white;\n }\n\n .cb-btn-danger:hover {\n background: #dc2626;\n }\n\n .cb-btn-secondary {\n background: transparent;\n color: #7c3aed;\n border: 2px solid #7c3aed;\n }\n\n .cb-btn-secondary:hover {\n background: #f5f3ff;\n }\n\n .cb-btn-ghost {\n background: transparent;\n color: #888;\n }\n\n .cb-btn-ghost:hover {\n color: #555;\n }\n\n .cb-step-dots {\n display: flex;\n justify-content: center;\n gap: 6px;\n margin-top: 16px;\n }\n\n .cb-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #e0e0e0;\n transition: background 0.2s ease;\n }\n\n .cb-dot.active {\n background: #7c3aed;\n }\n\n .cb-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n font-size: 24px;\n color: #888;\n cursor: pointer;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n }\n\n .cb-close:hover {\n background: #f0f0f0;\n color: #333;\n }\n\n .cb-modal-inner {\n position: relative;\n }\n`;\n","import type { FlowStep } from \"../core/api\";\n\nexport interface StepState {\n currentIndex: number;\n selectedReason: string | null;\n comment: string;\n totalSteps: number;\n}\n\nexport function renderStep(\n step: FlowStep,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n): HTMLElement {\n const container = document.createElement(\"div\");\n\n switch (step.type) {\n case \"reason_survey\":\n renderReasonSurvey(container, step.config, state, callbacks);\n break;\n case \"offer\":\n renderOffer(container, step.config, callbacks);\n break;\n case \"custom_message\":\n renderCustomMessage(container, step.config, {\n onNext: callbacks.onNext,\n onClose: callbacks.onGoBack,\n });\n break;\n case \"confirmation\":\n renderConfirmation(container, step.config, callbacks);\n break;\n }\n\n // Step dots\n if (state.totalSteps > 1) {\n const dots = document.createElement(\"div\");\n dots.className = \"cb-step-dots\";\n for (let i = 0; i < state.totalSteps; i++) {\n const dot = document.createElement(\"div\");\n dot.className = `cb-dot${i === state.currentIndex ? \" active\" : \"\"}`;\n dots.appendChild(dot);\n }\n container.appendChild(dots);\n }\n\n return container;\n}\n\nfunction renderReasonSurvey(\n container: HTMLElement,\n config: Record<string, unknown>,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Why are you cancelling?\";\n container.appendChild(headline);\n\n const reasons = (config.reasons as Array<{ id: string; label: string }>) || [];\n const reasonsDiv = document.createElement(\"div\");\n reasonsDiv.className = \"cb-reasons\";\n\n reasons.forEach((r) => {\n const btn = document.createElement(\"button\");\n btn.className = `cb-reason${state.selectedReason === r.id ? \" selected\" : \"\"}`;\n btn.textContent = r.label;\n btn.onclick = () => {\n callbacks.onSelectReason(r.id);\n // Update selection visually\n reasonsDiv.querySelectorAll(\".cb-reason\").forEach((el) => el.classList.remove(\"selected\"));\n btn.classList.add(\"selected\");\n };\n reasonsDiv.appendChild(btn);\n });\n container.appendChild(reasonsDiv);\n\n if (config.allow_comment) {\n const textarea = document.createElement(\"textarea\");\n textarea.className = \"cb-textarea\";\n textarea.placeholder = (config.comment_placeholder as string) || \"Tell us more...\";\n textarea.value = state.comment;\n textarea.oninput = () => callbacks.onComment(textarea.value);\n container.appendChild(textarea);\n }\n\n const nextBtn = document.createElement(\"button\");\n nextBtn.className = \"cb-btn cb-btn-primary\";\n nextBtn.textContent = \"Continue\";\n nextBtn.onclick = callbacks.onNext;\n container.appendChild(nextBtn);\n}\n\nfunction renderOffer(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Special offer for you\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const acceptBtn = document.createElement(\"button\");\n acceptBtn.className = \"cb-btn cb-btn-primary\";\n acceptBtn.textContent = (config.cta_accept as string) || \"Accept Offer\";\n acceptBtn.onclick = () => callbacks.onAcceptOffer(config);\n container.appendChild(acceptBtn);\n\n const declineBtn = document.createElement(\"button\");\n declineBtn.className = \"cb-btn cb-btn-ghost\";\n declineBtn.textContent = (config.cta_decline as string) || \"No thanks\";\n declineBtn.onclick = callbacks.onDeclineOffer;\n container.appendChild(declineBtn);\n}\n\nfunction renderCustomMessage(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: { onNext: () => void; onClose: () => void }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n if (config.cta_text) {\n const btn = document.createElement(\"button\");\n btn.className = \"cb-btn cb-btn-primary\";\n btn.textContent = config.cta_text as string;\n btn.onclick = config.cta_action === \"close\" ? callbacks.onClose : callbacks.onNext;\n container.appendChild(btn);\n }\n}\n\nfunction renderConfirmation(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Are you sure?\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const goBackBtn = document.createElement(\"button\");\n goBackBtn.className = \"cb-btn cb-btn-primary\";\n goBackBtn.textContent = (config.go_back_text as string) || \"Keep my subscription\";\n goBackBtn.onclick = callbacks.onGoBack;\n container.appendChild(goBackBtn);\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.className = \"cb-btn cb-btn-danger\";\n cancelBtn.textContent = (config.confirm_cancel_text as string) || \"Yes, cancel\";\n cancelBtn.onclick = callbacks.onConfirmCancel;\n container.appendChild(cancelBtn);\n}\n","import type { ApiClient, FlowStep, FlowResponse } from \"../core/api\";\nimport { MODAL_STYLES } from \"./styles\";\nimport { renderStep, type StepState } from \"./steps\";\n\nexport interface CancelFlowOptions {\n stripeCustomerId: string;\n stripeSubscriptionId: string;\n stripePriceId?: string;\n onComplete?: (result: CancelFlowResult) => void;\n onDismiss?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface CancelFlowResult {\n action: \"saved\" | \"cancelled\" | \"dismissed\";\n reason?: string;\n comment?: string;\n offerAccepted?: Record<string, unknown>;\n}\n\nexport class CancelFlowModal {\n private shadowHost: HTMLElement | null = null;\n private shadowRoot: ShadowRoot | null = null;\n private state: StepState;\n private steps: FlowStep[] = [];\n private sessionId = \"\";\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: CancelFlowOptions\n ) {\n this.state = {\n currentIndex: 0,\n selectedReason: null,\n comment: \"\",\n totalSteps: 0,\n };\n }\n\n async open(): Promise<void> {\n // Fetch flow config\n let flow: FlowResponse;\n try {\n flow = await this.api.fetchCancelFlow(\n this.options.stripeCustomerId,\n this.options.stripeSubscriptionId,\n this.options.stripePriceId\n );\n } catch (err) {\n console.error(\"[ChurnBack] Failed to load cancel flow:\", err);\n const error = err instanceof Error ? err : new Error(String(err));\n this.options.onError?.(error);\n throw err;\n }\n\n if (this.destroyed) return;\n\n this.sessionId = flow.sessionId;\n this.steps = flow.steps;\n this.state.totalSteps = flow.steps.length;\n\n // Create Shadow DOM host\n this.shadowHost = document.createElement(\"div\");\n this.shadowHost.id = \"churnback-modal\";\n document.body.appendChild(this.shadowHost);\n this.shadowRoot = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n // Inject styles\n const style = document.createElement(\"style\");\n style.textContent = MODAL_STYLES;\n this.shadowRoot.appendChild(style);\n\n this.renderCurrentStep();\n }\n\n private renderCurrentStep(): void {\n if (!this.shadowRoot || this.destroyed) return;\n\n // Remove old content (keep style)\n const existing = this.shadowRoot.querySelector(\".cb-overlay\");\n if (existing) existing.remove();\n\n const step = this.steps[this.state.currentIndex];\n if (!step) return;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"cb-overlay\";\n overlay.onclick = (e) => {\n if (e.target === overlay) this.dismiss();\n };\n\n const modal = document.createElement(\"div\");\n modal.className = \"cb-modal\";\n\n const inner = document.createElement(\"div\");\n inner.className = \"cb-modal-inner\";\n\n // Close button\n const closeBtn = document.createElement(\"button\");\n closeBtn.className = \"cb-close\";\n closeBtn.innerHTML = \"×\";\n closeBtn.onclick = () => this.dismiss();\n inner.appendChild(closeBtn);\n\n const stepContent = renderStep(step, this.state, {\n onNext: () => this.nextStep(),\n onSelectReason: (id) => {\n this.state.selectedReason = id;\n },\n onComment: (text) => {\n this.state.comment = text;\n },\n onAcceptOffer: (offer) => this.acceptOffer(offer),\n onDeclineOffer: () => this.nextStep(),\n onConfirmCancel: () => this.confirmCancel(),\n onGoBack: () => this.dismiss(),\n });\n\n inner.appendChild(stepContent);\n modal.appendChild(inner);\n overlay.appendChild(modal);\n this.shadowRoot.appendChild(overlay);\n }\n\n private nextStep(): void {\n if (this.state.currentIndex < this.steps.length - 1) {\n this.state.currentIndex++;\n this.renderCurrentStep();\n } else {\n this.confirmCancel();\n }\n }\n\n private acceptOffer(offer: Record<string, unknown>): void {\n this.close();\n this.options.onComplete?.({\n action: \"saved\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n offerAccepted: offer,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"offer_accepted\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n acceptedOffer: offer,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit offer acceptance:\", err));\n }\n\n private confirmCancel(): void {\n this.close();\n this.options.onComplete?.({\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit cancellation:\", err));\n }\n\n private dismiss(): void {\n this.close();\n this.options.onDismiss?.();\n this.options.onComplete?.({ action: \"dismissed\" });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"abandoned\",\n }).catch(() => { /* Silent fail on dismiss */ });\n }\n\n private close(): void {\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n this.shadowRoot = null;\n }\n\n destroy(): void {\n this.destroyed = true;\n this.close();\n }\n}\n","export const BANNER_STYLES = `\n .cb-dunning-banner {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 10px;\n padding: 16px 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n }\n\n .cb-dunning-content {\n flex: 1;\n min-width: 200px;\n }\n\n .cb-dunning-headline {\n font-size: 15px;\n font-weight: 600;\n color: #991b1b;\n margin-bottom: 4px;\n }\n\n .cb-dunning-body {\n font-size: 13px;\n color: #b91c1c;\n line-height: 1.4;\n }\n\n .cb-dunning-cta {\n padding: 8px 16px;\n background: #ef4444;\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.15s ease;\n text-decoration: none;\n display: inline-block;\n }\n\n .cb-dunning-cta:hover {\n background: #dc2626;\n }\n\n .cb-dunning-hidden {\n display: none;\n }\n`;\n","import type { ApiClient, DunningResponse } from \"../core/api\";\nimport { BANNER_STYLES } from \"./styles\";\n\nexport interface DunningBannerOptions {\n stripeCustomerId: string;\n targetElement: string | HTMLElement;\n}\n\nexport class DunningBanner {\n private shadowHost: HTMLElement | null = null;\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: DunningBannerOptions\n ) {}\n\n async mount(): Promise<void> {\n let dunning: DunningResponse;\n try {\n dunning = await this.api.checkDunning(this.options.stripeCustomerId);\n } catch {\n return; // Silent fail\n }\n\n if (this.destroyed || !dunning.hasFailedPayment) return;\n\n const target =\n typeof this.options.targetElement === \"string\"\n ? document.querySelector(this.options.targetElement)\n : this.options.targetElement;\n\n if (!target) {\n console.warn(\"[ChurnBack] Dunning banner target element not found\");\n return;\n }\n\n this.shadowHost = document.createElement(\"div\");\n const shadow = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n const style = document.createElement(\"style\");\n style.textContent = BANNER_STYLES;\n shadow.appendChild(style);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cb-dunning-banner\";\n\n const content = document.createElement(\"div\");\n content.className = \"cb-dunning-content\";\n\n const headline = document.createElement(\"div\");\n headline.className = \"cb-dunning-headline\";\n headline.textContent = dunning.bannerConfig?.headline || \"Payment failed\";\n\n const body = document.createElement(\"div\");\n body.className = \"cb-dunning-body\";\n body.textContent =\n dunning.bannerConfig?.body ||\n `We were unable to process your payment${dunning.amountDueCents ? ` of $${(dunning.amountDueCents / 100).toFixed(2)}` : \"\"}. Please update your payment method to avoid service interruption.`;\n\n content.appendChild(headline);\n content.appendChild(body);\n banner.appendChild(content);\n\n if (dunning.updatePaymentUrl) {\n const cta = document.createElement(\"a\");\n cta.className = \"cb-dunning-cta\";\n cta.href = dunning.updatePaymentUrl;\n cta.target = \"_blank\";\n cta.rel = \"noopener noreferrer\";\n cta.textContent = dunning.bannerConfig?.ctaText || \"Update Payment\";\n banner.appendChild(cta);\n }\n\n shadow.appendChild(banner);\n target.appendChild(this.shadowHost);\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n }\n}\n"],"mappings":"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","names":["index_exports","__export","ChurnBackClient","index_default","__toCommonJS","resolveConfig","config","ApiClient","config","customerId","subscriptionId","priceId","params","res","body","payload","MODAL_STYLES","renderStep","step","state","callbacks","container","renderReasonSurvey","renderOffer","renderCustomMessage","renderConfirmation","dots","dot","config","headline","reasons","reasonsDiv","btn","el","textarea","nextBtn","body","acceptBtn","declineBtn","goBackBtn","cancelBtn","CancelFlowModal","api","options","flow","err","error","style","MODAL_STYLES","existing","step","overlay","e","modal","inner","closeBtn","stepContent","renderStep","id","text","offer","BANNER_STYLES","DunningBanner","api","options","dunning","target","shadow","style","BANNER_STYLES","banner","content","headline","body","cta","ChurnBackClient","config","resolved","resolveConfig","ApiClient","options","modal","CancelFlowModal","err","banner","DunningBanner","b","index_default"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/config.ts","../src/core/api.ts","../src/cancel-flow/styles.ts","../src/cancel-flow/steps.ts","../src/cancel-flow/modal.ts","../src/dunning/styles.ts","../src/dunning/banner.ts"],"sourcesContent":["import { resolveConfig, type ChurnBackConfig } from \"./core/config\";\nimport { ApiClient, type DunningResponse } from \"./core/api\";\nimport { CancelFlowModal, type CancelFlowOptions, type CancelFlowResult } from \"./cancel-flow/modal\";\nimport { DunningBanner, type DunningBannerOptions } from \"./dunning/banner\";\n\nexport type { ChurnBackConfig, CancelFlowOptions, CancelFlowResult, DunningResponse };\n\nexport class ChurnBackClient {\n private api: ApiClient;\n private activeModal: CancelFlowModal | null = null;\n private activeBanners: DunningBanner[] = [];\n\n constructor(config: ChurnBackConfig) {\n if (!config.apiKey) {\n throw new Error(\"[ChurnBack] apiKey is required\");\n }\n const resolved = resolveConfig(config);\n this.api = new ApiClient(resolved);\n }\n\n async triggerCancelFlow(options: CancelFlowOptions): Promise<void> {\n this.activeModal?.destroy();\n\n const modal = new CancelFlowModal(this.api, options);\n this.activeModal = modal;\n\n try {\n await modal.open();\n } catch (err) {\n // If onError is provided, the consumer handles it — don't re-throw\n if (options.onError) return;\n throw err;\n }\n }\n\n async checkDunning(options: { stripeCustomerId: string }): Promise<DunningResponse> {\n return this.api.checkDunning(options.stripeCustomerId);\n }\n\n mountDunningBanner(options: DunningBannerOptions): void {\n const banner = new DunningBanner(this.api, options);\n this.activeBanners.push(banner);\n banner.mount();\n }\n\n destroy(): void {\n this.activeModal?.destroy();\n this.activeModal = null;\n this.activeBanners.forEach((b) => b.destroy());\n this.activeBanners = [];\n }\n}\n\nexport default ChurnBackClient;\n","export interface ChurnBackConfig {\n apiKey: string;\n apiBaseUrl?: string;\n}\n\nexport interface ResolvedConfig {\n apiKey: string;\n apiBaseUrl: string;\n}\n\nexport function resolveConfig(config: ChurnBackConfig): ResolvedConfig {\n return {\n apiKey: config.apiKey,\n apiBaseUrl: (config.apiBaseUrl || \"https://churnback.ai/api/sdk\").replace(/\\/$/, \"\"),\n };\n}\n","import type { ResolvedConfig } from \"./config\";\n\nexport interface FlowResponse {\n sessionId: string;\n steps: FlowStep[];\n style: Record<string, string>;\n}\n\nexport interface FlowStep {\n id: string;\n type: \"reason_survey\" | \"offer\" | \"custom_message\" | \"confirmation\";\n config: Record<string, unknown>;\n}\n\nexport interface SubmitPayload {\n sessionId: string;\n action: \"offer_accepted\" | \"cancelled\" | \"abandoned\";\n reason?: string;\n comment?: string;\n acceptedOffer?: Record<string, unknown>;\n}\n\nexport interface DunningResponse {\n hasFailedPayment: boolean;\n amountDueCents?: number;\n updatePaymentUrl?: string;\n bannerConfig?: {\n headline: string;\n body: string;\n ctaText: string;\n };\n}\n\nexport class ApiClient {\n constructor(private config: ResolvedConfig) {}\n\n async fetchCancelFlow(\n customerId: string,\n subscriptionId: string,\n priceId?: string\n ): Promise<FlowResponse> {\n const params = new URLSearchParams({\n customer_id: customerId,\n subscription_id: subscriptionId,\n });\n if (priceId) params.set(\"price_id\", priceId);\n\n const res = await fetch(`${this.config.apiBaseUrl}/cancel-flow?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to fetch cancel flow (${res.status})`);\n }\n\n return res.json();\n }\n\n async submitResult(payload: SubmitPayload): Promise<void> {\n const res = await fetch(`${this.config.apiBaseUrl}/submit`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-churnback-key\": this.config.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to submit result (${res.status})`);\n }\n }\n\n async checkDunning(customerId: string): Promise<DunningResponse> {\n const params = new URLSearchParams({ customer_id: customerId });\n\n const res = await fetch(`${this.config.apiBaseUrl}/dunning?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n return { hasFailedPayment: false };\n }\n\n return res.json();\n }\n}\n","export const MODAL_STYLES = `\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n .cb-overlay {\n position: fixed;\n inset: 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 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n animation: cb-fade-in 0.2s ease;\n }\n\n @keyframes cb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes cb-slide-up {\n from { transform: translateY(20px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n }\n\n .cb-modal {\n background: white;\n border-radius: 16px;\n padding: 32px;\n max-width: 480px;\n width: 90%;\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n animation: cb-slide-up 0.3s ease;\n }\n\n .cb-headline {\n font-size: 22px;\n font-weight: 700;\n color: #1a1a1a;\n margin-bottom: 12px;\n line-height: 1.3;\n }\n\n .cb-body {\n font-size: 15px;\n color: #555;\n line-height: 1.6;\n margin-bottom: 20px;\n }\n\n .cb-reasons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 16px;\n }\n\n .cb-reason {\n padding: 12px 16px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n transition: all 0.15s ease;\n background: white;\n text-align: left;\n }\n\n .cb-reason:hover {\n border-color: #7c3aed;\n background: #f5f3ff;\n }\n\n .cb-reason.selected {\n border-color: #7c3aed;\n background: #f5f3ff;\n color: #7c3aed;\n font-weight: 500;\n }\n\n .cb-textarea {\n width: 100%;\n min-height: 80px;\n padding: 12px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n font-size: 14px;\n font-family: inherit;\n resize: vertical;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .cb-textarea:focus {\n border-color: #7c3aed;\n }\n\n .cb-btn {\n display: block;\n width: 100%;\n padding: 12px 20px;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n border: none;\n transition: all 0.15s ease;\n margin-bottom: 8px;\n }\n\n .cb-btn-primary {\n background: #7c3aed;\n color: white;\n }\n\n .cb-btn-primary:hover {\n background: #6d28d9;\n }\n\n .cb-btn-danger {\n background: #ef4444;\n color: white;\n }\n\n .cb-btn-danger:hover {\n background: #dc2626;\n }\n\n .cb-btn-secondary {\n background: transparent;\n color: #7c3aed;\n border: 2px solid #7c3aed;\n }\n\n .cb-btn-secondary:hover {\n background: #f5f3ff;\n }\n\n .cb-btn-ghost {\n background: transparent;\n color: #888;\n }\n\n .cb-btn-ghost:hover {\n color: #555;\n }\n\n .cb-step-dots {\n display: flex;\n justify-content: center;\n gap: 6px;\n margin-top: 16px;\n }\n\n .cb-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #e0e0e0;\n transition: background 0.2s ease;\n }\n\n .cb-dot.active {\n background: #7c3aed;\n }\n\n .cb-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n font-size: 24px;\n color: #888;\n cursor: pointer;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n }\n\n .cb-close:hover {\n background: #f0f0f0;\n color: #333;\n }\n\n .cb-modal-inner {\n position: relative;\n }\n`;\n","import type { FlowStep } from \"../core/api\";\n\nexport interface StepState {\n currentIndex: number;\n selectedReason: string | null;\n comment: string;\n totalSteps: number;\n}\n\nexport function renderStep(\n step: FlowStep,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n): HTMLElement {\n const container = document.createElement(\"div\");\n\n switch (step.type) {\n case \"reason_survey\":\n renderReasonSurvey(container, step.config, state, callbacks);\n break;\n case \"offer\":\n renderOffer(container, step.config, callbacks);\n break;\n case \"custom_message\":\n renderCustomMessage(container, step.config, {\n onNext: callbacks.onNext,\n onClose: callbacks.onGoBack,\n });\n break;\n case \"confirmation\":\n renderConfirmation(container, step.config, callbacks);\n break;\n }\n\n // Step dots\n if (state.totalSteps > 1) {\n const dots = document.createElement(\"div\");\n dots.className = \"cb-step-dots\";\n for (let i = 0; i < state.totalSteps; i++) {\n const dot = document.createElement(\"div\");\n dot.className = `cb-dot${i === state.currentIndex ? \" active\" : \"\"}`;\n dots.appendChild(dot);\n }\n container.appendChild(dots);\n }\n\n return container;\n}\n\nfunction renderReasonSurvey(\n container: HTMLElement,\n config: Record<string, unknown>,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Why are you cancelling?\";\n container.appendChild(headline);\n\n const reasons = (config.reasons as Array<{ id: string; label: string }>) || [];\n const reasonsDiv = document.createElement(\"div\");\n reasonsDiv.className = \"cb-reasons\";\n\n reasons.forEach((r) => {\n const btn = document.createElement(\"button\");\n btn.className = `cb-reason${state.selectedReason === r.id ? \" selected\" : \"\"}`;\n btn.textContent = r.label;\n btn.onclick = () => {\n callbacks.onSelectReason(r.id);\n // Update selection visually\n reasonsDiv.querySelectorAll(\".cb-reason\").forEach((el) => el.classList.remove(\"selected\"));\n btn.classList.add(\"selected\");\n };\n reasonsDiv.appendChild(btn);\n });\n container.appendChild(reasonsDiv);\n\n if (config.allow_comment) {\n const textarea = document.createElement(\"textarea\");\n textarea.className = \"cb-textarea\";\n textarea.placeholder = (config.comment_placeholder as string) || \"Tell us more...\";\n textarea.value = state.comment;\n textarea.oninput = () => callbacks.onComment(textarea.value);\n container.appendChild(textarea);\n }\n\n const nextBtn = document.createElement(\"button\");\n nextBtn.className = \"cb-btn cb-btn-primary\";\n nextBtn.textContent = \"Continue\";\n nextBtn.onclick = callbacks.onNext;\n container.appendChild(nextBtn);\n}\n\nfunction renderOffer(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Special offer for you\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const acceptBtn = document.createElement(\"button\");\n acceptBtn.className = \"cb-btn cb-btn-primary\";\n acceptBtn.textContent = (config.cta_accept as string) || \"Accept Offer\";\n acceptBtn.onclick = () => callbacks.onAcceptOffer(config);\n container.appendChild(acceptBtn);\n\n const declineBtn = document.createElement(\"button\");\n declineBtn.className = \"cb-btn cb-btn-ghost\";\n declineBtn.textContent = (config.cta_decline as string) || \"No thanks\";\n declineBtn.onclick = callbacks.onDeclineOffer;\n container.appendChild(declineBtn);\n}\n\nfunction renderCustomMessage(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: { onNext: () => void; onClose: () => void }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n if (config.cta_text) {\n const btn = document.createElement(\"button\");\n btn.className = \"cb-btn cb-btn-primary\";\n btn.textContent = config.cta_text as string;\n btn.onclick = config.cta_action === \"close\" ? callbacks.onClose : callbacks.onNext;\n container.appendChild(btn);\n }\n}\n\nfunction renderConfirmation(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Are you sure?\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const goBackBtn = document.createElement(\"button\");\n goBackBtn.className = \"cb-btn cb-btn-primary\";\n goBackBtn.textContent = (config.go_back_text as string) || \"Keep my subscription\";\n goBackBtn.onclick = callbacks.onGoBack;\n container.appendChild(goBackBtn);\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.className = \"cb-btn cb-btn-danger\";\n cancelBtn.textContent = (config.confirm_cancel_text as string) || \"Yes, cancel\";\n cancelBtn.onclick = callbacks.onConfirmCancel;\n container.appendChild(cancelBtn);\n}\n","import type { ApiClient, FlowStep, FlowResponse } from \"../core/api\";\nimport { MODAL_STYLES } from \"./styles\";\nimport { renderStep, type StepState } from \"./steps\";\n\nexport interface CancelFlowOptions {\n stripeCustomerId: string;\n stripeSubscriptionId: string;\n stripePriceId?: string;\n onComplete?: (result: CancelFlowResult) => void;\n onDismiss?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface CancelFlowResult {\n action: \"saved\" | \"cancelled\" | \"dismissed\";\n reason?: string;\n comment?: string;\n offerAccepted?: Record<string, unknown>;\n}\n\nexport class CancelFlowModal {\n private shadowHost: HTMLElement | null = null;\n private shadowRoot: ShadowRoot | null = null;\n private state: StepState;\n private steps: FlowStep[] = [];\n private sessionId = \"\";\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: CancelFlowOptions\n ) {\n this.state = {\n currentIndex: 0,\n selectedReason: null,\n comment: \"\",\n totalSteps: 0,\n };\n }\n\n async open(): Promise<void> {\n // Fetch flow config\n let flow: FlowResponse;\n try {\n flow = await this.api.fetchCancelFlow(\n this.options.stripeCustomerId,\n this.options.stripeSubscriptionId,\n this.options.stripePriceId\n );\n } catch (err) {\n console.error(\"[ChurnBack] Failed to load cancel flow:\", err);\n const error = err instanceof Error ? err : new Error(String(err));\n this.options.onError?.(error);\n throw err;\n }\n\n if (this.destroyed) return;\n\n this.sessionId = flow.sessionId;\n this.steps = flow.steps;\n this.state.totalSteps = flow.steps.length;\n\n // Create Shadow DOM host\n this.shadowHost = document.createElement(\"div\");\n this.shadowHost.id = \"churnback-modal\";\n document.body.appendChild(this.shadowHost);\n this.shadowRoot = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n // Inject styles\n const style = document.createElement(\"style\");\n style.textContent = MODAL_STYLES;\n this.shadowRoot.appendChild(style);\n\n this.renderCurrentStep();\n }\n\n private renderCurrentStep(): void {\n if (!this.shadowRoot || this.destroyed) return;\n\n // Remove old content (keep style)\n const existing = this.shadowRoot.querySelector(\".cb-overlay\");\n if (existing) existing.remove();\n\n const step = this.steps[this.state.currentIndex];\n if (!step) return;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"cb-overlay\";\n overlay.onclick = (e) => {\n if (e.target === overlay) this.dismiss();\n };\n\n const modal = document.createElement(\"div\");\n modal.className = \"cb-modal\";\n\n const inner = document.createElement(\"div\");\n inner.className = \"cb-modal-inner\";\n\n // Close button\n const closeBtn = document.createElement(\"button\");\n closeBtn.className = \"cb-close\";\n closeBtn.innerHTML = \"×\";\n closeBtn.onclick = () => this.dismiss();\n inner.appendChild(closeBtn);\n\n const stepContent = renderStep(step, this.state, {\n onNext: () => this.nextStep(),\n onSelectReason: (id) => {\n this.state.selectedReason = id;\n },\n onComment: (text) => {\n this.state.comment = text;\n },\n onAcceptOffer: (offer) => this.acceptOffer(offer),\n onDeclineOffer: () => { console.log(\"[ChurnBack] Decline offer clicked\"); this.nextStep(); },\n onConfirmCancel: () => this.confirmCancel(),\n onGoBack: () => this.dismiss(),\n });\n\n inner.appendChild(stepContent);\n modal.appendChild(inner);\n overlay.appendChild(modal);\n this.shadowRoot.appendChild(overlay);\n }\n\n private nextStep(): void {\n console.log(`[ChurnBack] nextStep: index=${this.state.currentIndex}, total=${this.steps.length}`);\n if (this.state.currentIndex < this.steps.length - 1) {\n this.state.currentIndex++;\n console.log(\"[ChurnBack] Advancing to step\", this.state.currentIndex);\n this.renderCurrentStep();\n } else {\n console.log(\"[ChurnBack] Last step — calling confirmCancel\");\n this.confirmCancel();\n }\n }\n\n private acceptOffer(offer: Record<string, unknown>): void {\n this.close();\n this.options.onComplete?.({\n action: \"saved\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n offerAccepted: offer,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"offer_accepted\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n acceptedOffer: offer,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit offer acceptance:\", err));\n }\n\n private confirmCancel(): void {\n console.log(\"[ChurnBack] confirmCancel called\");\n this.close();\n console.log(\"[ChurnBack] modal closed\");\n this.options.onComplete?.({\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit cancellation:\", err));\n }\n\n private dismiss(): void {\n this.close();\n this.options.onDismiss?.();\n this.options.onComplete?.({ action: \"dismissed\" });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"abandoned\",\n }).catch(() => { /* Silent fail on dismiss */ });\n }\n\n private close(): void {\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n this.shadowRoot = null;\n }\n\n destroy(): void {\n this.destroyed = true;\n this.close();\n }\n}\n","export const BANNER_STYLES = `\n .cb-dunning-banner {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 10px;\n padding: 16px 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n }\n\n .cb-dunning-content {\n flex: 1;\n min-width: 200px;\n }\n\n .cb-dunning-headline {\n font-size: 15px;\n font-weight: 600;\n color: #991b1b;\n margin-bottom: 4px;\n }\n\n .cb-dunning-body {\n font-size: 13px;\n color: #b91c1c;\n line-height: 1.4;\n }\n\n .cb-dunning-cta {\n padding: 8px 16px;\n background: #ef4444;\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.15s ease;\n text-decoration: none;\n display: inline-block;\n }\n\n .cb-dunning-cta:hover {\n background: #dc2626;\n }\n\n .cb-dunning-hidden {\n display: none;\n }\n`;\n","import type { ApiClient, DunningResponse } from \"../core/api\";\nimport { BANNER_STYLES } from \"./styles\";\n\nexport interface DunningBannerOptions {\n stripeCustomerId: string;\n targetElement: string | HTMLElement;\n}\n\nexport class DunningBanner {\n private shadowHost: HTMLElement | null = null;\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: DunningBannerOptions\n ) {}\n\n async mount(): Promise<void> {\n let dunning: DunningResponse;\n try {\n dunning = await this.api.checkDunning(this.options.stripeCustomerId);\n } catch {\n return; // Silent fail\n }\n\n if (this.destroyed || !dunning.hasFailedPayment) return;\n\n const target =\n typeof this.options.targetElement === \"string\"\n ? document.querySelector(this.options.targetElement)\n : this.options.targetElement;\n\n if (!target) {\n console.warn(\"[ChurnBack] Dunning banner target element not found\");\n return;\n }\n\n this.shadowHost = document.createElement(\"div\");\n const shadow = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n const style = document.createElement(\"style\");\n style.textContent = BANNER_STYLES;\n shadow.appendChild(style);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cb-dunning-banner\";\n\n const content = document.createElement(\"div\");\n content.className = \"cb-dunning-content\";\n\n const headline = document.createElement(\"div\");\n headline.className = \"cb-dunning-headline\";\n headline.textContent = dunning.bannerConfig?.headline || \"Payment failed\";\n\n const body = document.createElement(\"div\");\n body.className = \"cb-dunning-body\";\n body.textContent =\n dunning.bannerConfig?.body ||\n `We were unable to process your payment${dunning.amountDueCents ? ` of $${(dunning.amountDueCents / 100).toFixed(2)}` : \"\"}. Please update your payment method to avoid service interruption.`;\n\n content.appendChild(headline);\n content.appendChild(body);\n banner.appendChild(content);\n\n if (dunning.updatePaymentUrl) {\n const cta = document.createElement(\"a\");\n cta.className = \"cb-dunning-cta\";\n cta.href = dunning.updatePaymentUrl;\n cta.target = \"_blank\";\n cta.rel = \"noopener noreferrer\";\n cta.textContent = dunning.bannerConfig?.ctaText || \"Update Payment\";\n banner.appendChild(cta);\n }\n\n shadow.appendChild(banner);\n target.appendChild(this.shadowHost);\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n }\n}\n"],"mappings":"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","names":["index_exports","__export","ChurnBackClient","index_default","__toCommonJS","resolveConfig","config","ApiClient","config","customerId","subscriptionId","priceId","params","res","body","payload","MODAL_STYLES","renderStep","step","state","callbacks","container","renderReasonSurvey","renderOffer","renderCustomMessage","renderConfirmation","dots","dot","config","headline","reasons","reasonsDiv","btn","el","textarea","nextBtn","body","acceptBtn","declineBtn","goBackBtn","cancelBtn","CancelFlowModal","api","options","flow","err","error","style","MODAL_STYLES","existing","step","overlay","e","modal","inner","closeBtn","stepContent","renderStep","id","text","offer","BANNER_STYLES","DunningBanner","api","options","dunning","target","shadow","style","BANNER_STYLES","banner","content","headline","body","cta","ChurnBackClient","config","resolved","resolveConfig","ApiClient","options","modal","CancelFlowModal","err","banner","DunningBanner","b","index_default"]}
|
package/dist/index.mjs
CHANGED
|
@@ -193,7 +193,7 @@ function u(s){return{apiKey:s.apiKey,apiBaseUrl:(s.apiBaseUrl||"https://churnbac
|
|
|
193
193
|
.cb-modal-inner {
|
|
194
194
|
position: relative;
|
|
195
195
|
}
|
|
196
|
-
`;function b(s,e,n){let t=document.createElement("div");switch(s.type){case"reason_survey":
|
|
196
|
+
`;function b(s,e,n){let t=document.createElement("div");switch(s.type){case"reason_survey":C(t,s.config,e,n);break;case"offer":y(t,s.config,n);break;case"custom_message":w(t,s.config,{onNext:n.onNext,onClose:n.onGoBack});break;case"confirmation":v(t,s.config,n);break}if(e.totalSteps>1){let i=document.createElement("div");i.className="cb-step-dots";for(let o=0;o<e.totalSteps;o++){let a=document.createElement("div");a.className=`cb-dot${o===e.currentIndex?" active":""}`,i.appendChild(a)}t.appendChild(i)}return t}function C(s,e,n,t){let i=document.createElement("h2");i.className="cb-headline",i.textContent=e.headline||"Why are you cancelling?",s.appendChild(i);let o=e.reasons||[],a=document.createElement("div");if(a.className="cb-reasons",o.forEach(r=>{let c=document.createElement("button");c.className=`cb-reason${n.selectedReason===r.id?" selected":""}`,c.textContent=r.label,c.onclick=()=>{t.onSelectReason(r.id),a.querySelectorAll(".cb-reason").forEach(x=>x.classList.remove("selected")),c.classList.add("selected")},a.appendChild(c)}),s.appendChild(a),e.allow_comment){let r=document.createElement("textarea");r.className="cb-textarea",r.placeholder=e.comment_placeholder||"Tell us more...",r.value=n.comment,r.oninput=()=>t.onComment(r.value),s.appendChild(r)}let d=document.createElement("button");d.className="cb-btn cb-btn-primary",d.textContent="Continue",d.onclick=t.onNext,s.appendChild(d)}function y(s,e,n){let t=document.createElement("h2");t.className="cb-headline",t.textContent=e.headline||"Special offer for you",s.appendChild(t);let i=document.createElement("p");i.className="cb-body",i.textContent=e.body||"",s.appendChild(i);let o=document.createElement("button");o.className="cb-btn cb-btn-primary",o.textContent=e.cta_accept||"Accept Offer",o.onclick=()=>n.onAcceptOffer(e),s.appendChild(o);let a=document.createElement("button");a.className="cb-btn cb-btn-ghost",a.textContent=e.cta_decline||"No thanks",a.onclick=n.onDeclineOffer,s.appendChild(a)}function w(s,e,n){let t=document.createElement("h2");t.className="cb-headline",t.textContent=e.headline||"",s.appendChild(t);let i=document.createElement("p");if(i.className="cb-body",i.textContent=e.body||"",s.appendChild(i),e.cta_text){let o=document.createElement("button");o.className="cb-btn cb-btn-primary",o.textContent=e.cta_text,o.onclick=e.cta_action==="close"?n.onClose:n.onNext,s.appendChild(o)}}function v(s,e,n){let t=document.createElement("h2");t.className="cb-headline",t.textContent=e.headline||"Are you sure?",s.appendChild(t);let i=document.createElement("p");i.className="cb-body",i.textContent=e.body||"",s.appendChild(i);let o=document.createElement("button");o.className="cb-btn cb-btn-primary",o.textContent=e.go_back_text||"Keep my subscription",o.onclick=n.onGoBack,s.appendChild(o);let a=document.createElement("button");a.className="cb-btn cb-btn-danger",a.textContent=e.confirm_cancel_text||"Yes, cancel",a.onclick=n.onConfirmCancel,s.appendChild(a)}var p=class{constructor(e,n){this.api=e;this.options=n;this.shadowHost=null;this.shadowRoot=null;this.steps=[];this.sessionId="";this.destroyed=!1;this.state={currentIndex:0,selectedReason:null,comment:"",totalSteps:0}}async open(){let e;try{e=await this.api.fetchCancelFlow(this.options.stripeCustomerId,this.options.stripeSubscriptionId,this.options.stripePriceId)}catch(t){console.error("[ChurnBack] Failed to load cancel flow:",t);let i=t instanceof Error?t:new Error(String(t));throw this.options.onError?.(i),t}if(this.destroyed)return;this.sessionId=e.sessionId,this.steps=e.steps,this.state.totalSteps=e.steps.length,this.shadowHost=document.createElement("div"),this.shadowHost.id="churnback-modal",document.body.appendChild(this.shadowHost),this.shadowRoot=this.shadowHost.attachShadow({mode:"closed"});let n=document.createElement("style");n.textContent=f,this.shadowRoot.appendChild(n),this.renderCurrentStep()}renderCurrentStep(){if(!this.shadowRoot||this.destroyed)return;let e=this.shadowRoot.querySelector(".cb-overlay");e&&e.remove();let n=this.steps[this.state.currentIndex];if(!n)return;let t=document.createElement("div");t.className="cb-overlay",t.onclick=r=>{r.target===t&&this.dismiss()};let i=document.createElement("div");i.className="cb-modal";let o=document.createElement("div");o.className="cb-modal-inner";let a=document.createElement("button");a.className="cb-close",a.innerHTML="×",a.onclick=()=>this.dismiss(),o.appendChild(a);let d=b(n,this.state,{onNext:()=>this.nextStep(),onSelectReason:r=>{this.state.selectedReason=r},onComment:r=>{this.state.comment=r},onAcceptOffer:r=>this.acceptOffer(r),onDeclineOffer:()=>{console.log("[ChurnBack] Decline offer clicked"),this.nextStep()},onConfirmCancel:()=>this.confirmCancel(),onGoBack:()=>this.dismiss()});o.appendChild(d),i.appendChild(o),t.appendChild(i),this.shadowRoot.appendChild(t)}nextStep(){console.log(`[ChurnBack] nextStep: index=${this.state.currentIndex}, total=${this.steps.length}`),this.state.currentIndex<this.steps.length-1?(this.state.currentIndex++,console.log("[ChurnBack] Advancing to step",this.state.currentIndex),this.renderCurrentStep()):(console.log("[ChurnBack] Last step \u2014 calling confirmCancel"),this.confirmCancel())}acceptOffer(e){this.close(),this.options.onComplete?.({action:"saved",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0,offerAccepted:e}),this.api.submitResult({sessionId:this.sessionId,action:"offer_accepted",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0,acceptedOffer:e}).catch(n=>console.error("[ChurnBack] Failed to submit offer acceptance:",n))}confirmCancel(){console.log("[ChurnBack] confirmCancel called"),this.close(),console.log("[ChurnBack] modal closed"),this.options.onComplete?.({action:"cancelled",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0}),this.api.submitResult({sessionId:this.sessionId,action:"cancelled",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0}).catch(e=>console.error("[ChurnBack] Failed to submit cancellation:",e))}dismiss(){this.close(),this.options.onDismiss?.(),this.options.onComplete?.({action:"dismissed"}),this.api.submitResult({sessionId:this.sessionId,action:"abandoned"}).catch(()=>{})}close(){this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null,this.shadowRoot=null}destroy(){this.destroyed=!0,this.close()}};var g=`
|
|
197
197
|
.cb-dunning-banner {
|
|
198
198
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
199
199
|
background: #fef2f2;
|
|
@@ -247,6 +247,6 @@ function u(s){return{apiKey:s.apiKey,apiBaseUrl:(s.apiBaseUrl||"https://churnbac
|
|
|
247
247
|
.cb-dunning-hidden {
|
|
248
248
|
display: none;
|
|
249
249
|
}
|
|
250
|
-
`;var m=class{constructor(e,n){this.api=e;this.options=n;this.shadowHost=null;this.destroyed=!1}async mount(){let e;try{e=await this.api.checkDunning(this.options.stripeCustomerId)}catch{return}if(this.destroyed||!e.hasFailedPayment)return;let n=typeof this.options.targetElement=="string"?document.querySelector(this.options.targetElement):this.options.targetElement;if(!n){console.warn("[ChurnBack] Dunning banner target element not found");return}this.shadowHost=document.createElement("div");let t=this.shadowHost.attachShadow({mode:"closed"}),i=document.createElement("style");i.textContent=g,t.appendChild(i);let o=document.createElement("div");o.className="cb-dunning-banner";let a=document.createElement("div");a.className="cb-dunning-content";let d=document.createElement("div");d.className="cb-dunning-headline",d.textContent=e.bannerConfig?.headline||"Payment failed";let r=document.createElement("div");if(r.className="cb-dunning-body",r.textContent=e.bannerConfig?.body||`We were unable to process your payment${e.amountDueCents?` of $${(e.amountDueCents/100).toFixed(2)}`:""}. Please update your payment method to avoid service interruption.`,a.appendChild(d),a.appendChild(r),o.appendChild(a),e.updatePaymentUrl){let c=document.createElement("a");c.className="cb-dunning-cta",c.href=e.updatePaymentUrl,c.target="_blank",c.rel="noopener noreferrer",c.textContent=e.bannerConfig?.ctaText||"Update Payment",o.appendChild(c)}t.appendChild(o),n.appendChild(this.shadowHost)}destroy(){this.destroyed=!0,this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null}};var h=class{constructor(e){this.activeModal=null;this.activeBanners=[];if(!e.apiKey)throw new Error("[ChurnBack] apiKey is required");let n=u(e);this.api=new l(n)}async triggerCancelFlow(e){this.activeModal?.destroy();let n=new p(this.api,e);this.activeModal=n;try{await n.open()}catch(t){if(e.onError)return;throw t}}async checkDunning(e){return this.api.checkDunning(e.stripeCustomerId)}mountDunningBanner(e){let n=new m(this.api,e);this.activeBanners.push(n),n.mount()}destroy(){this.activeModal?.destroy(),this.activeModal=null,this.activeBanners.forEach(e=>e.destroy()),this.activeBanners=[]}},
|
|
250
|
+
`;var m=class{constructor(e,n){this.api=e;this.options=n;this.shadowHost=null;this.destroyed=!1}async mount(){let e;try{e=await this.api.checkDunning(this.options.stripeCustomerId)}catch{return}if(this.destroyed||!e.hasFailedPayment)return;let n=typeof this.options.targetElement=="string"?document.querySelector(this.options.targetElement):this.options.targetElement;if(!n){console.warn("[ChurnBack] Dunning banner target element not found");return}this.shadowHost=document.createElement("div");let t=this.shadowHost.attachShadow({mode:"closed"}),i=document.createElement("style");i.textContent=g,t.appendChild(i);let o=document.createElement("div");o.className="cb-dunning-banner";let a=document.createElement("div");a.className="cb-dunning-content";let d=document.createElement("div");d.className="cb-dunning-headline",d.textContent=e.bannerConfig?.headline||"Payment failed";let r=document.createElement("div");if(r.className="cb-dunning-body",r.textContent=e.bannerConfig?.body||`We were unable to process your payment${e.amountDueCents?` of $${(e.amountDueCents/100).toFixed(2)}`:""}. Please update your payment method to avoid service interruption.`,a.appendChild(d),a.appendChild(r),o.appendChild(a),e.updatePaymentUrl){let c=document.createElement("a");c.className="cb-dunning-cta",c.href=e.updatePaymentUrl,c.target="_blank",c.rel="noopener noreferrer",c.textContent=e.bannerConfig?.ctaText||"Update Payment",o.appendChild(c)}t.appendChild(o),n.appendChild(this.shadowHost)}destroy(){this.destroyed=!0,this.shadowHost&&this.shadowHost.parentNode&&this.shadowHost.parentNode.removeChild(this.shadowHost),this.shadowHost=null}};var h=class{constructor(e){this.activeModal=null;this.activeBanners=[];if(!e.apiKey)throw new Error("[ChurnBack] apiKey is required");let n=u(e);this.api=new l(n)}async triggerCancelFlow(e){this.activeModal?.destroy();let n=new p(this.api,e);this.activeModal=n;try{await n.open()}catch(t){if(e.onError)return;throw t}}async checkDunning(e){return this.api.checkDunning(e.stripeCustomerId)}mountDunningBanner(e){let n=new m(this.api,e);this.activeBanners.push(n),n.mount()}destroy(){this.activeModal?.destroy(),this.activeModal=null,this.activeBanners.forEach(e=>e.destroy()),this.activeBanners=[]}},L=h;export{h as ChurnBackClient,L as default};
|
|
251
251
|
if(typeof ChurnBack!=='undefined'&&ChurnBack.default){ChurnBack=ChurnBack.default;}
|
|
252
252
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/config.ts","../src/core/api.ts","../src/cancel-flow/styles.ts","../src/cancel-flow/steps.ts","../src/cancel-flow/modal.ts","../src/dunning/styles.ts","../src/dunning/banner.ts","../src/index.ts"],"sourcesContent":["export interface ChurnBackConfig {\n apiKey: string;\n apiBaseUrl?: string;\n}\n\nexport interface ResolvedConfig {\n apiKey: string;\n apiBaseUrl: string;\n}\n\nexport function resolveConfig(config: ChurnBackConfig): ResolvedConfig {\n return {\n apiKey: config.apiKey,\n apiBaseUrl: (config.apiBaseUrl || \"https://churnback.ai/api/sdk\").replace(/\\/$/, \"\"),\n };\n}\n","import type { ResolvedConfig } from \"./config\";\n\nexport interface FlowResponse {\n sessionId: string;\n steps: FlowStep[];\n style: Record<string, string>;\n}\n\nexport interface FlowStep {\n id: string;\n type: \"reason_survey\" | \"offer\" | \"custom_message\" | \"confirmation\";\n config: Record<string, unknown>;\n}\n\nexport interface SubmitPayload {\n sessionId: string;\n action: \"offer_accepted\" | \"cancelled\" | \"abandoned\";\n reason?: string;\n comment?: string;\n acceptedOffer?: Record<string, unknown>;\n}\n\nexport interface DunningResponse {\n hasFailedPayment: boolean;\n amountDueCents?: number;\n updatePaymentUrl?: string;\n bannerConfig?: {\n headline: string;\n body: string;\n ctaText: string;\n };\n}\n\nexport class ApiClient {\n constructor(private config: ResolvedConfig) {}\n\n async fetchCancelFlow(\n customerId: string,\n subscriptionId: string,\n priceId?: string\n ): Promise<FlowResponse> {\n const params = new URLSearchParams({\n customer_id: customerId,\n subscription_id: subscriptionId,\n });\n if (priceId) params.set(\"price_id\", priceId);\n\n const res = await fetch(`${this.config.apiBaseUrl}/cancel-flow?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to fetch cancel flow (${res.status})`);\n }\n\n return res.json();\n }\n\n async submitResult(payload: SubmitPayload): Promise<void> {\n const res = await fetch(`${this.config.apiBaseUrl}/submit`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-churnback-key\": this.config.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to submit result (${res.status})`);\n }\n }\n\n async checkDunning(customerId: string): Promise<DunningResponse> {\n const params = new URLSearchParams({ customer_id: customerId });\n\n const res = await fetch(`${this.config.apiBaseUrl}/dunning?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n return { hasFailedPayment: false };\n }\n\n return res.json();\n }\n}\n","export const MODAL_STYLES = `\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n .cb-overlay {\n position: fixed;\n inset: 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 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n animation: cb-fade-in 0.2s ease;\n }\n\n @keyframes cb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes cb-slide-up {\n from { transform: translateY(20px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n }\n\n .cb-modal {\n background: white;\n border-radius: 16px;\n padding: 32px;\n max-width: 480px;\n width: 90%;\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n animation: cb-slide-up 0.3s ease;\n }\n\n .cb-headline {\n font-size: 22px;\n font-weight: 700;\n color: #1a1a1a;\n margin-bottom: 12px;\n line-height: 1.3;\n }\n\n .cb-body {\n font-size: 15px;\n color: #555;\n line-height: 1.6;\n margin-bottom: 20px;\n }\n\n .cb-reasons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 16px;\n }\n\n .cb-reason {\n padding: 12px 16px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n transition: all 0.15s ease;\n background: white;\n text-align: left;\n }\n\n .cb-reason:hover {\n border-color: #7c3aed;\n background: #f5f3ff;\n }\n\n .cb-reason.selected {\n border-color: #7c3aed;\n background: #f5f3ff;\n color: #7c3aed;\n font-weight: 500;\n }\n\n .cb-textarea {\n width: 100%;\n min-height: 80px;\n padding: 12px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n font-size: 14px;\n font-family: inherit;\n resize: vertical;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .cb-textarea:focus {\n border-color: #7c3aed;\n }\n\n .cb-btn {\n display: block;\n width: 100%;\n padding: 12px 20px;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n border: none;\n transition: all 0.15s ease;\n margin-bottom: 8px;\n }\n\n .cb-btn-primary {\n background: #7c3aed;\n color: white;\n }\n\n .cb-btn-primary:hover {\n background: #6d28d9;\n }\n\n .cb-btn-danger {\n background: #ef4444;\n color: white;\n }\n\n .cb-btn-danger:hover {\n background: #dc2626;\n }\n\n .cb-btn-secondary {\n background: transparent;\n color: #7c3aed;\n border: 2px solid #7c3aed;\n }\n\n .cb-btn-secondary:hover {\n background: #f5f3ff;\n }\n\n .cb-btn-ghost {\n background: transparent;\n color: #888;\n }\n\n .cb-btn-ghost:hover {\n color: #555;\n }\n\n .cb-step-dots {\n display: flex;\n justify-content: center;\n gap: 6px;\n margin-top: 16px;\n }\n\n .cb-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #e0e0e0;\n transition: background 0.2s ease;\n }\n\n .cb-dot.active {\n background: #7c3aed;\n }\n\n .cb-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n font-size: 24px;\n color: #888;\n cursor: pointer;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n }\n\n .cb-close:hover {\n background: #f0f0f0;\n color: #333;\n }\n\n .cb-modal-inner {\n position: relative;\n }\n`;\n","import type { FlowStep } from \"../core/api\";\n\nexport interface StepState {\n currentIndex: number;\n selectedReason: string | null;\n comment: string;\n totalSteps: number;\n}\n\nexport function renderStep(\n step: FlowStep,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n): HTMLElement {\n const container = document.createElement(\"div\");\n\n switch (step.type) {\n case \"reason_survey\":\n renderReasonSurvey(container, step.config, state, callbacks);\n break;\n case \"offer\":\n renderOffer(container, step.config, callbacks);\n break;\n case \"custom_message\":\n renderCustomMessage(container, step.config, {\n onNext: callbacks.onNext,\n onClose: callbacks.onGoBack,\n });\n break;\n case \"confirmation\":\n renderConfirmation(container, step.config, callbacks);\n break;\n }\n\n // Step dots\n if (state.totalSteps > 1) {\n const dots = document.createElement(\"div\");\n dots.className = \"cb-step-dots\";\n for (let i = 0; i < state.totalSteps; i++) {\n const dot = document.createElement(\"div\");\n dot.className = `cb-dot${i === state.currentIndex ? \" active\" : \"\"}`;\n dots.appendChild(dot);\n }\n container.appendChild(dots);\n }\n\n return container;\n}\n\nfunction renderReasonSurvey(\n container: HTMLElement,\n config: Record<string, unknown>,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Why are you cancelling?\";\n container.appendChild(headline);\n\n const reasons = (config.reasons as Array<{ id: string; label: string }>) || [];\n const reasonsDiv = document.createElement(\"div\");\n reasonsDiv.className = \"cb-reasons\";\n\n reasons.forEach((r) => {\n const btn = document.createElement(\"button\");\n btn.className = `cb-reason${state.selectedReason === r.id ? \" selected\" : \"\"}`;\n btn.textContent = r.label;\n btn.onclick = () => {\n callbacks.onSelectReason(r.id);\n // Update selection visually\n reasonsDiv.querySelectorAll(\".cb-reason\").forEach((el) => el.classList.remove(\"selected\"));\n btn.classList.add(\"selected\");\n };\n reasonsDiv.appendChild(btn);\n });\n container.appendChild(reasonsDiv);\n\n if (config.allow_comment) {\n const textarea = document.createElement(\"textarea\");\n textarea.className = \"cb-textarea\";\n textarea.placeholder = (config.comment_placeholder as string) || \"Tell us more...\";\n textarea.value = state.comment;\n textarea.oninput = () => callbacks.onComment(textarea.value);\n container.appendChild(textarea);\n }\n\n const nextBtn = document.createElement(\"button\");\n nextBtn.className = \"cb-btn cb-btn-primary\";\n nextBtn.textContent = \"Continue\";\n nextBtn.onclick = callbacks.onNext;\n container.appendChild(nextBtn);\n}\n\nfunction renderOffer(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Special offer for you\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const acceptBtn = document.createElement(\"button\");\n acceptBtn.className = \"cb-btn cb-btn-primary\";\n acceptBtn.textContent = (config.cta_accept as string) || \"Accept Offer\";\n acceptBtn.onclick = () => callbacks.onAcceptOffer(config);\n container.appendChild(acceptBtn);\n\n const declineBtn = document.createElement(\"button\");\n declineBtn.className = \"cb-btn cb-btn-ghost\";\n declineBtn.textContent = (config.cta_decline as string) || \"No thanks\";\n declineBtn.onclick = callbacks.onDeclineOffer;\n container.appendChild(declineBtn);\n}\n\nfunction renderCustomMessage(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: { onNext: () => void; onClose: () => void }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n if (config.cta_text) {\n const btn = document.createElement(\"button\");\n btn.className = \"cb-btn cb-btn-primary\";\n btn.textContent = config.cta_text as string;\n btn.onclick = config.cta_action === \"close\" ? callbacks.onClose : callbacks.onNext;\n container.appendChild(btn);\n }\n}\n\nfunction renderConfirmation(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Are you sure?\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const goBackBtn = document.createElement(\"button\");\n goBackBtn.className = \"cb-btn cb-btn-primary\";\n goBackBtn.textContent = (config.go_back_text as string) || \"Keep my subscription\";\n goBackBtn.onclick = callbacks.onGoBack;\n container.appendChild(goBackBtn);\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.className = \"cb-btn cb-btn-danger\";\n cancelBtn.textContent = (config.confirm_cancel_text as string) || \"Yes, cancel\";\n cancelBtn.onclick = callbacks.onConfirmCancel;\n container.appendChild(cancelBtn);\n}\n","import type { ApiClient, FlowStep, FlowResponse } from \"../core/api\";\nimport { MODAL_STYLES } from \"./styles\";\nimport { renderStep, type StepState } from \"./steps\";\n\nexport interface CancelFlowOptions {\n stripeCustomerId: string;\n stripeSubscriptionId: string;\n stripePriceId?: string;\n onComplete?: (result: CancelFlowResult) => void;\n onDismiss?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface CancelFlowResult {\n action: \"saved\" | \"cancelled\" | \"dismissed\";\n reason?: string;\n comment?: string;\n offerAccepted?: Record<string, unknown>;\n}\n\nexport class CancelFlowModal {\n private shadowHost: HTMLElement | null = null;\n private shadowRoot: ShadowRoot | null = null;\n private state: StepState;\n private steps: FlowStep[] = [];\n private sessionId = \"\";\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: CancelFlowOptions\n ) {\n this.state = {\n currentIndex: 0,\n selectedReason: null,\n comment: \"\",\n totalSteps: 0,\n };\n }\n\n async open(): Promise<void> {\n // Fetch flow config\n let flow: FlowResponse;\n try {\n flow = await this.api.fetchCancelFlow(\n this.options.stripeCustomerId,\n this.options.stripeSubscriptionId,\n this.options.stripePriceId\n );\n } catch (err) {\n console.error(\"[ChurnBack] Failed to load cancel flow:\", err);\n const error = err instanceof Error ? err : new Error(String(err));\n this.options.onError?.(error);\n throw err;\n }\n\n if (this.destroyed) return;\n\n this.sessionId = flow.sessionId;\n this.steps = flow.steps;\n this.state.totalSteps = flow.steps.length;\n\n // Create Shadow DOM host\n this.shadowHost = document.createElement(\"div\");\n this.shadowHost.id = \"churnback-modal\";\n document.body.appendChild(this.shadowHost);\n this.shadowRoot = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n // Inject styles\n const style = document.createElement(\"style\");\n style.textContent = MODAL_STYLES;\n this.shadowRoot.appendChild(style);\n\n this.renderCurrentStep();\n }\n\n private renderCurrentStep(): void {\n if (!this.shadowRoot || this.destroyed) return;\n\n // Remove old content (keep style)\n const existing = this.shadowRoot.querySelector(\".cb-overlay\");\n if (existing) existing.remove();\n\n const step = this.steps[this.state.currentIndex];\n if (!step) return;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"cb-overlay\";\n overlay.onclick = (e) => {\n if (e.target === overlay) this.dismiss();\n };\n\n const modal = document.createElement(\"div\");\n modal.className = \"cb-modal\";\n\n const inner = document.createElement(\"div\");\n inner.className = \"cb-modal-inner\";\n\n // Close button\n const closeBtn = document.createElement(\"button\");\n closeBtn.className = \"cb-close\";\n closeBtn.innerHTML = \"×\";\n closeBtn.onclick = () => this.dismiss();\n inner.appendChild(closeBtn);\n\n const stepContent = renderStep(step, this.state, {\n onNext: () => this.nextStep(),\n onSelectReason: (id) => {\n this.state.selectedReason = id;\n },\n onComment: (text) => {\n this.state.comment = text;\n },\n onAcceptOffer: (offer) => this.acceptOffer(offer),\n onDeclineOffer: () => this.nextStep(),\n onConfirmCancel: () => this.confirmCancel(),\n onGoBack: () => this.dismiss(),\n });\n\n inner.appendChild(stepContent);\n modal.appendChild(inner);\n overlay.appendChild(modal);\n this.shadowRoot.appendChild(overlay);\n }\n\n private nextStep(): void {\n if (this.state.currentIndex < this.steps.length - 1) {\n this.state.currentIndex++;\n this.renderCurrentStep();\n } else {\n this.confirmCancel();\n }\n }\n\n private acceptOffer(offer: Record<string, unknown>): void {\n this.close();\n this.options.onComplete?.({\n action: \"saved\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n offerAccepted: offer,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"offer_accepted\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n acceptedOffer: offer,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit offer acceptance:\", err));\n }\n\n private confirmCancel(): void {\n this.close();\n this.options.onComplete?.({\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit cancellation:\", err));\n }\n\n private dismiss(): void {\n this.close();\n this.options.onDismiss?.();\n this.options.onComplete?.({ action: \"dismissed\" });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"abandoned\",\n }).catch(() => { /* Silent fail on dismiss */ });\n }\n\n private close(): void {\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n this.shadowRoot = null;\n }\n\n destroy(): void {\n this.destroyed = true;\n this.close();\n }\n}\n","export const BANNER_STYLES = `\n .cb-dunning-banner {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 10px;\n padding: 16px 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n }\n\n .cb-dunning-content {\n flex: 1;\n min-width: 200px;\n }\n\n .cb-dunning-headline {\n font-size: 15px;\n font-weight: 600;\n color: #991b1b;\n margin-bottom: 4px;\n }\n\n .cb-dunning-body {\n font-size: 13px;\n color: #b91c1c;\n line-height: 1.4;\n }\n\n .cb-dunning-cta {\n padding: 8px 16px;\n background: #ef4444;\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.15s ease;\n text-decoration: none;\n display: inline-block;\n }\n\n .cb-dunning-cta:hover {\n background: #dc2626;\n }\n\n .cb-dunning-hidden {\n display: none;\n }\n`;\n","import type { ApiClient, DunningResponse } from \"../core/api\";\nimport { BANNER_STYLES } from \"./styles\";\n\nexport interface DunningBannerOptions {\n stripeCustomerId: string;\n targetElement: string | HTMLElement;\n}\n\nexport class DunningBanner {\n private shadowHost: HTMLElement | null = null;\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: DunningBannerOptions\n ) {}\n\n async mount(): Promise<void> {\n let dunning: DunningResponse;\n try {\n dunning = await this.api.checkDunning(this.options.stripeCustomerId);\n } catch {\n return; // Silent fail\n }\n\n if (this.destroyed || !dunning.hasFailedPayment) return;\n\n const target =\n typeof this.options.targetElement === \"string\"\n ? document.querySelector(this.options.targetElement)\n : this.options.targetElement;\n\n if (!target) {\n console.warn(\"[ChurnBack] Dunning banner target element not found\");\n return;\n }\n\n this.shadowHost = document.createElement(\"div\");\n const shadow = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n const style = document.createElement(\"style\");\n style.textContent = BANNER_STYLES;\n shadow.appendChild(style);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cb-dunning-banner\";\n\n const content = document.createElement(\"div\");\n content.className = \"cb-dunning-content\";\n\n const headline = document.createElement(\"div\");\n headline.className = \"cb-dunning-headline\";\n headline.textContent = dunning.bannerConfig?.headline || \"Payment failed\";\n\n const body = document.createElement(\"div\");\n body.className = \"cb-dunning-body\";\n body.textContent =\n dunning.bannerConfig?.body ||\n `We were unable to process your payment${dunning.amountDueCents ? ` of $${(dunning.amountDueCents / 100).toFixed(2)}` : \"\"}. Please update your payment method to avoid service interruption.`;\n\n content.appendChild(headline);\n content.appendChild(body);\n banner.appendChild(content);\n\n if (dunning.updatePaymentUrl) {\n const cta = document.createElement(\"a\");\n cta.className = \"cb-dunning-cta\";\n cta.href = dunning.updatePaymentUrl;\n cta.target = \"_blank\";\n cta.rel = \"noopener noreferrer\";\n cta.textContent = dunning.bannerConfig?.ctaText || \"Update Payment\";\n banner.appendChild(cta);\n }\n\n shadow.appendChild(banner);\n target.appendChild(this.shadowHost);\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n }\n}\n","import { resolveConfig, type ChurnBackConfig } from \"./core/config\";\nimport { ApiClient, type DunningResponse } from \"./core/api\";\nimport { CancelFlowModal, type CancelFlowOptions, type CancelFlowResult } from \"./cancel-flow/modal\";\nimport { DunningBanner, type DunningBannerOptions } from \"./dunning/banner\";\n\nexport type { ChurnBackConfig, CancelFlowOptions, CancelFlowResult, DunningResponse };\n\nexport class ChurnBackClient {\n private api: ApiClient;\n private activeModal: CancelFlowModal | null = null;\n private activeBanners: DunningBanner[] = [];\n\n constructor(config: ChurnBackConfig) {\n if (!config.apiKey) {\n throw new Error(\"[ChurnBack] apiKey is required\");\n }\n const resolved = resolveConfig(config);\n this.api = new ApiClient(resolved);\n }\n\n async triggerCancelFlow(options: CancelFlowOptions): Promise<void> {\n this.activeModal?.destroy();\n\n const modal = new CancelFlowModal(this.api, options);\n this.activeModal = modal;\n\n try {\n await modal.open();\n } catch (err) {\n // If onError is provided, the consumer handles it — don't re-throw\n if (options.onError) return;\n throw err;\n }\n }\n\n async checkDunning(options: { stripeCustomerId: string }): Promise<DunningResponse> {\n return this.api.checkDunning(options.stripeCustomerId);\n }\n\n mountDunningBanner(options: DunningBannerOptions): void {\n const banner = new DunningBanner(this.api, options);\n this.activeBanners.push(banner);\n banner.mount();\n }\n\n destroy(): void {\n this.activeModal?.destroy();\n this.activeModal = null;\n this.activeBanners.forEach((b) => b.destroy());\n this.activeBanners = [];\n }\n}\n\nexport default ChurnBackClient;\n"],"mappings":"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","names":["resolveConfig","config","ApiClient","config","customerId","subscriptionId","priceId","params","res","body","payload","MODAL_STYLES","renderStep","step","state","callbacks","container","renderReasonSurvey","renderOffer","renderCustomMessage","renderConfirmation","dots","i","dot","config","headline","reasons","reasonsDiv","btn","el","textarea","nextBtn","body","acceptBtn","declineBtn","goBackBtn","cancelBtn","CancelFlowModal","api","options","flow","err","error","style","MODAL_STYLES","existing","step","overlay","e","modal","inner","closeBtn","stepContent","renderStep","id","text","offer","BANNER_STYLES","DunningBanner","api","options","dunning","target","shadow","style","BANNER_STYLES","banner","content","headline","body","cta","ChurnBackClient","config","resolved","resolveConfig","ApiClient","options","modal","CancelFlowModal","err","banner","DunningBanner","b","index_default"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/core/api.ts","../src/cancel-flow/styles.ts","../src/cancel-flow/steps.ts","../src/cancel-flow/modal.ts","../src/dunning/styles.ts","../src/dunning/banner.ts","../src/index.ts"],"sourcesContent":["export interface ChurnBackConfig {\n apiKey: string;\n apiBaseUrl?: string;\n}\n\nexport interface ResolvedConfig {\n apiKey: string;\n apiBaseUrl: string;\n}\n\nexport function resolveConfig(config: ChurnBackConfig): ResolvedConfig {\n return {\n apiKey: config.apiKey,\n apiBaseUrl: (config.apiBaseUrl || \"https://churnback.ai/api/sdk\").replace(/\\/$/, \"\"),\n };\n}\n","import type { ResolvedConfig } from \"./config\";\n\nexport interface FlowResponse {\n sessionId: string;\n steps: FlowStep[];\n style: Record<string, string>;\n}\n\nexport interface FlowStep {\n id: string;\n type: \"reason_survey\" | \"offer\" | \"custom_message\" | \"confirmation\";\n config: Record<string, unknown>;\n}\n\nexport interface SubmitPayload {\n sessionId: string;\n action: \"offer_accepted\" | \"cancelled\" | \"abandoned\";\n reason?: string;\n comment?: string;\n acceptedOffer?: Record<string, unknown>;\n}\n\nexport interface DunningResponse {\n hasFailedPayment: boolean;\n amountDueCents?: number;\n updatePaymentUrl?: string;\n bannerConfig?: {\n headline: string;\n body: string;\n ctaText: string;\n };\n}\n\nexport class ApiClient {\n constructor(private config: ResolvedConfig) {}\n\n async fetchCancelFlow(\n customerId: string,\n subscriptionId: string,\n priceId?: string\n ): Promise<FlowResponse> {\n const params = new URLSearchParams({\n customer_id: customerId,\n subscription_id: subscriptionId,\n });\n if (priceId) params.set(\"price_id\", priceId);\n\n const res = await fetch(`${this.config.apiBaseUrl}/cancel-flow?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to fetch cancel flow (${res.status})`);\n }\n\n return res.json();\n }\n\n async submitResult(payload: SubmitPayload): Promise<void> {\n const res = await fetch(`${this.config.apiBaseUrl}/submit`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-churnback-key\": this.config.apiKey,\n },\n body: JSON.stringify(payload),\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({}));\n throw new Error(body.error || `Failed to submit result (${res.status})`);\n }\n }\n\n async checkDunning(customerId: string): Promise<DunningResponse> {\n const params = new URLSearchParams({ customer_id: customerId });\n\n const res = await fetch(`${this.config.apiBaseUrl}/dunning?${params}`, {\n headers: { \"x-churnback-key\": this.config.apiKey },\n });\n\n if (!res.ok) {\n return { hasFailedPayment: false };\n }\n\n return res.json();\n }\n}\n","export const MODAL_STYLES = `\n * { box-sizing: border-box; margin: 0; padding: 0; }\n\n .cb-overlay {\n position: fixed;\n inset: 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 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n animation: cb-fade-in 0.2s ease;\n }\n\n @keyframes cb-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n @keyframes cb-slide-up {\n from { transform: translateY(20px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n }\n\n .cb-modal {\n background: white;\n border-radius: 16px;\n padding: 32px;\n max-width: 480px;\n width: 90%;\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n animation: cb-slide-up 0.3s ease;\n }\n\n .cb-headline {\n font-size: 22px;\n font-weight: 700;\n color: #1a1a1a;\n margin-bottom: 12px;\n line-height: 1.3;\n }\n\n .cb-body {\n font-size: 15px;\n color: #555;\n line-height: 1.6;\n margin-bottom: 20px;\n }\n\n .cb-reasons {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 16px;\n }\n\n .cb-reason {\n padding: 12px 16px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n cursor: pointer;\n font-size: 14px;\n color: #333;\n transition: all 0.15s ease;\n background: white;\n text-align: left;\n }\n\n .cb-reason:hover {\n border-color: #7c3aed;\n background: #f5f3ff;\n }\n\n .cb-reason.selected {\n border-color: #7c3aed;\n background: #f5f3ff;\n color: #7c3aed;\n font-weight: 500;\n }\n\n .cb-textarea {\n width: 100%;\n min-height: 80px;\n padding: 12px;\n border: 2px solid #e0e0e0;\n border-radius: 10px;\n font-size: 14px;\n font-family: inherit;\n resize: vertical;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.15s ease;\n }\n\n .cb-textarea:focus {\n border-color: #7c3aed;\n }\n\n .cb-btn {\n display: block;\n width: 100%;\n padding: 12px 20px;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n border: none;\n transition: all 0.15s ease;\n margin-bottom: 8px;\n }\n\n .cb-btn-primary {\n background: #7c3aed;\n color: white;\n }\n\n .cb-btn-primary:hover {\n background: #6d28d9;\n }\n\n .cb-btn-danger {\n background: #ef4444;\n color: white;\n }\n\n .cb-btn-danger:hover {\n background: #dc2626;\n }\n\n .cb-btn-secondary {\n background: transparent;\n color: #7c3aed;\n border: 2px solid #7c3aed;\n }\n\n .cb-btn-secondary:hover {\n background: #f5f3ff;\n }\n\n .cb-btn-ghost {\n background: transparent;\n color: #888;\n }\n\n .cb-btn-ghost:hover {\n color: #555;\n }\n\n .cb-step-dots {\n display: flex;\n justify-content: center;\n gap: 6px;\n margin-top: 16px;\n }\n\n .cb-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #e0e0e0;\n transition: background 0.2s ease;\n }\n\n .cb-dot.active {\n background: #7c3aed;\n }\n\n .cb-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n font-size: 24px;\n color: #888;\n cursor: pointer;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n }\n\n .cb-close:hover {\n background: #f0f0f0;\n color: #333;\n }\n\n .cb-modal-inner {\n position: relative;\n }\n`;\n","import type { FlowStep } from \"../core/api\";\n\nexport interface StepState {\n currentIndex: number;\n selectedReason: string | null;\n comment: string;\n totalSteps: number;\n}\n\nexport function renderStep(\n step: FlowStep,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n): HTMLElement {\n const container = document.createElement(\"div\");\n\n switch (step.type) {\n case \"reason_survey\":\n renderReasonSurvey(container, step.config, state, callbacks);\n break;\n case \"offer\":\n renderOffer(container, step.config, callbacks);\n break;\n case \"custom_message\":\n renderCustomMessage(container, step.config, {\n onNext: callbacks.onNext,\n onClose: callbacks.onGoBack,\n });\n break;\n case \"confirmation\":\n renderConfirmation(container, step.config, callbacks);\n break;\n }\n\n // Step dots\n if (state.totalSteps > 1) {\n const dots = document.createElement(\"div\");\n dots.className = \"cb-step-dots\";\n for (let i = 0; i < state.totalSteps; i++) {\n const dot = document.createElement(\"div\");\n dot.className = `cb-dot${i === state.currentIndex ? \" active\" : \"\"}`;\n dots.appendChild(dot);\n }\n container.appendChild(dots);\n }\n\n return container;\n}\n\nfunction renderReasonSurvey(\n container: HTMLElement,\n config: Record<string, unknown>,\n state: StepState,\n callbacks: {\n onNext: () => void;\n onSelectReason: (reasonId: string) => void;\n onComment: (text: string) => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Why are you cancelling?\";\n container.appendChild(headline);\n\n const reasons = (config.reasons as Array<{ id: string; label: string }>) || [];\n const reasonsDiv = document.createElement(\"div\");\n reasonsDiv.className = \"cb-reasons\";\n\n reasons.forEach((r) => {\n const btn = document.createElement(\"button\");\n btn.className = `cb-reason${state.selectedReason === r.id ? \" selected\" : \"\"}`;\n btn.textContent = r.label;\n btn.onclick = () => {\n callbacks.onSelectReason(r.id);\n // Update selection visually\n reasonsDiv.querySelectorAll(\".cb-reason\").forEach((el) => el.classList.remove(\"selected\"));\n btn.classList.add(\"selected\");\n };\n reasonsDiv.appendChild(btn);\n });\n container.appendChild(reasonsDiv);\n\n if (config.allow_comment) {\n const textarea = document.createElement(\"textarea\");\n textarea.className = \"cb-textarea\";\n textarea.placeholder = (config.comment_placeholder as string) || \"Tell us more...\";\n textarea.value = state.comment;\n textarea.oninput = () => callbacks.onComment(textarea.value);\n container.appendChild(textarea);\n }\n\n const nextBtn = document.createElement(\"button\");\n nextBtn.className = \"cb-btn cb-btn-primary\";\n nextBtn.textContent = \"Continue\";\n nextBtn.onclick = callbacks.onNext;\n container.appendChild(nextBtn);\n}\n\nfunction renderOffer(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onAcceptOffer: (offer: Record<string, unknown>) => void;\n onDeclineOffer: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Special offer for you\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const acceptBtn = document.createElement(\"button\");\n acceptBtn.className = \"cb-btn cb-btn-primary\";\n acceptBtn.textContent = (config.cta_accept as string) || \"Accept Offer\";\n acceptBtn.onclick = () => callbacks.onAcceptOffer(config);\n container.appendChild(acceptBtn);\n\n const declineBtn = document.createElement(\"button\");\n declineBtn.className = \"cb-btn cb-btn-ghost\";\n declineBtn.textContent = (config.cta_decline as string) || \"No thanks\";\n declineBtn.onclick = callbacks.onDeclineOffer;\n container.appendChild(declineBtn);\n}\n\nfunction renderCustomMessage(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: { onNext: () => void; onClose: () => void }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n if (config.cta_text) {\n const btn = document.createElement(\"button\");\n btn.className = \"cb-btn cb-btn-primary\";\n btn.textContent = config.cta_text as string;\n btn.onclick = config.cta_action === \"close\" ? callbacks.onClose : callbacks.onNext;\n container.appendChild(btn);\n }\n}\n\nfunction renderConfirmation(\n container: HTMLElement,\n config: Record<string, unknown>,\n callbacks: {\n onConfirmCancel: () => void;\n onGoBack: () => void;\n }\n) {\n const headline = document.createElement(\"h2\");\n headline.className = \"cb-headline\";\n headline.textContent = (config.headline as string) || \"Are you sure?\";\n container.appendChild(headline);\n\n const body = document.createElement(\"p\");\n body.className = \"cb-body\";\n body.textContent = (config.body as string) || \"\";\n container.appendChild(body);\n\n const goBackBtn = document.createElement(\"button\");\n goBackBtn.className = \"cb-btn cb-btn-primary\";\n goBackBtn.textContent = (config.go_back_text as string) || \"Keep my subscription\";\n goBackBtn.onclick = callbacks.onGoBack;\n container.appendChild(goBackBtn);\n\n const cancelBtn = document.createElement(\"button\");\n cancelBtn.className = \"cb-btn cb-btn-danger\";\n cancelBtn.textContent = (config.confirm_cancel_text as string) || \"Yes, cancel\";\n cancelBtn.onclick = callbacks.onConfirmCancel;\n container.appendChild(cancelBtn);\n}\n","import type { ApiClient, FlowStep, FlowResponse } from \"../core/api\";\nimport { MODAL_STYLES } from \"./styles\";\nimport { renderStep, type StepState } from \"./steps\";\n\nexport interface CancelFlowOptions {\n stripeCustomerId: string;\n stripeSubscriptionId: string;\n stripePriceId?: string;\n onComplete?: (result: CancelFlowResult) => void;\n onDismiss?: () => void;\n onError?: (error: Error) => void;\n}\n\nexport interface CancelFlowResult {\n action: \"saved\" | \"cancelled\" | \"dismissed\";\n reason?: string;\n comment?: string;\n offerAccepted?: Record<string, unknown>;\n}\n\nexport class CancelFlowModal {\n private shadowHost: HTMLElement | null = null;\n private shadowRoot: ShadowRoot | null = null;\n private state: StepState;\n private steps: FlowStep[] = [];\n private sessionId = \"\";\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: CancelFlowOptions\n ) {\n this.state = {\n currentIndex: 0,\n selectedReason: null,\n comment: \"\",\n totalSteps: 0,\n };\n }\n\n async open(): Promise<void> {\n // Fetch flow config\n let flow: FlowResponse;\n try {\n flow = await this.api.fetchCancelFlow(\n this.options.stripeCustomerId,\n this.options.stripeSubscriptionId,\n this.options.stripePriceId\n );\n } catch (err) {\n console.error(\"[ChurnBack] Failed to load cancel flow:\", err);\n const error = err instanceof Error ? err : new Error(String(err));\n this.options.onError?.(error);\n throw err;\n }\n\n if (this.destroyed) return;\n\n this.sessionId = flow.sessionId;\n this.steps = flow.steps;\n this.state.totalSteps = flow.steps.length;\n\n // Create Shadow DOM host\n this.shadowHost = document.createElement(\"div\");\n this.shadowHost.id = \"churnback-modal\";\n document.body.appendChild(this.shadowHost);\n this.shadowRoot = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n // Inject styles\n const style = document.createElement(\"style\");\n style.textContent = MODAL_STYLES;\n this.shadowRoot.appendChild(style);\n\n this.renderCurrentStep();\n }\n\n private renderCurrentStep(): void {\n if (!this.shadowRoot || this.destroyed) return;\n\n // Remove old content (keep style)\n const existing = this.shadowRoot.querySelector(\".cb-overlay\");\n if (existing) existing.remove();\n\n const step = this.steps[this.state.currentIndex];\n if (!step) return;\n\n const overlay = document.createElement(\"div\");\n overlay.className = \"cb-overlay\";\n overlay.onclick = (e) => {\n if (e.target === overlay) this.dismiss();\n };\n\n const modal = document.createElement(\"div\");\n modal.className = \"cb-modal\";\n\n const inner = document.createElement(\"div\");\n inner.className = \"cb-modal-inner\";\n\n // Close button\n const closeBtn = document.createElement(\"button\");\n closeBtn.className = \"cb-close\";\n closeBtn.innerHTML = \"×\";\n closeBtn.onclick = () => this.dismiss();\n inner.appendChild(closeBtn);\n\n const stepContent = renderStep(step, this.state, {\n onNext: () => this.nextStep(),\n onSelectReason: (id) => {\n this.state.selectedReason = id;\n },\n onComment: (text) => {\n this.state.comment = text;\n },\n onAcceptOffer: (offer) => this.acceptOffer(offer),\n onDeclineOffer: () => { console.log(\"[ChurnBack] Decline offer clicked\"); this.nextStep(); },\n onConfirmCancel: () => this.confirmCancel(),\n onGoBack: () => this.dismiss(),\n });\n\n inner.appendChild(stepContent);\n modal.appendChild(inner);\n overlay.appendChild(modal);\n this.shadowRoot.appendChild(overlay);\n }\n\n private nextStep(): void {\n console.log(`[ChurnBack] nextStep: index=${this.state.currentIndex}, total=${this.steps.length}`);\n if (this.state.currentIndex < this.steps.length - 1) {\n this.state.currentIndex++;\n console.log(\"[ChurnBack] Advancing to step\", this.state.currentIndex);\n this.renderCurrentStep();\n } else {\n console.log(\"[ChurnBack] Last step — calling confirmCancel\");\n this.confirmCancel();\n }\n }\n\n private acceptOffer(offer: Record<string, unknown>): void {\n this.close();\n this.options.onComplete?.({\n action: \"saved\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n offerAccepted: offer,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"offer_accepted\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n acceptedOffer: offer,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit offer acceptance:\", err));\n }\n\n private confirmCancel(): void {\n console.log(\"[ChurnBack] confirmCancel called\");\n this.close();\n console.log(\"[ChurnBack] modal closed\");\n this.options.onComplete?.({\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n }).catch((err) => console.error(\"[ChurnBack] Failed to submit cancellation:\", err));\n }\n\n private dismiss(): void {\n this.close();\n this.options.onDismiss?.();\n this.options.onComplete?.({ action: \"dismissed\" });\n this.api.submitResult({\n sessionId: this.sessionId,\n action: \"abandoned\",\n }).catch(() => { /* Silent fail on dismiss */ });\n }\n\n private close(): void {\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n this.shadowRoot = null;\n }\n\n destroy(): void {\n this.destroyed = true;\n this.close();\n }\n}\n","export const BANNER_STYLES = `\n .cb-dunning-banner {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n background: #fef2f2;\n border: 1px solid #fecaca;\n border-radius: 10px;\n padding: 16px 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n flex-wrap: wrap;\n }\n\n .cb-dunning-content {\n flex: 1;\n min-width: 200px;\n }\n\n .cb-dunning-headline {\n font-size: 15px;\n font-weight: 600;\n color: #991b1b;\n margin-bottom: 4px;\n }\n\n .cb-dunning-body {\n font-size: 13px;\n color: #b91c1c;\n line-height: 1.4;\n }\n\n .cb-dunning-cta {\n padding: 8px 16px;\n background: #ef4444;\n color: white;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n white-space: nowrap;\n transition: background 0.15s ease;\n text-decoration: none;\n display: inline-block;\n }\n\n .cb-dunning-cta:hover {\n background: #dc2626;\n }\n\n .cb-dunning-hidden {\n display: none;\n }\n`;\n","import type { ApiClient, DunningResponse } from \"../core/api\";\nimport { BANNER_STYLES } from \"./styles\";\n\nexport interface DunningBannerOptions {\n stripeCustomerId: string;\n targetElement: string | HTMLElement;\n}\n\nexport class DunningBanner {\n private shadowHost: HTMLElement | null = null;\n private destroyed = false;\n\n constructor(\n private api: ApiClient,\n private options: DunningBannerOptions\n ) {}\n\n async mount(): Promise<void> {\n let dunning: DunningResponse;\n try {\n dunning = await this.api.checkDunning(this.options.stripeCustomerId);\n } catch {\n return; // Silent fail\n }\n\n if (this.destroyed || !dunning.hasFailedPayment) return;\n\n const target =\n typeof this.options.targetElement === \"string\"\n ? document.querySelector(this.options.targetElement)\n : this.options.targetElement;\n\n if (!target) {\n console.warn(\"[ChurnBack] Dunning banner target element not found\");\n return;\n }\n\n this.shadowHost = document.createElement(\"div\");\n const shadow = this.shadowHost.attachShadow({ mode: \"closed\" });\n\n const style = document.createElement(\"style\");\n style.textContent = BANNER_STYLES;\n shadow.appendChild(style);\n\n const banner = document.createElement(\"div\");\n banner.className = \"cb-dunning-banner\";\n\n const content = document.createElement(\"div\");\n content.className = \"cb-dunning-content\";\n\n const headline = document.createElement(\"div\");\n headline.className = \"cb-dunning-headline\";\n headline.textContent = dunning.bannerConfig?.headline || \"Payment failed\";\n\n const body = document.createElement(\"div\");\n body.className = \"cb-dunning-body\";\n body.textContent =\n dunning.bannerConfig?.body ||\n `We were unable to process your payment${dunning.amountDueCents ? ` of $${(dunning.amountDueCents / 100).toFixed(2)}` : \"\"}. Please update your payment method to avoid service interruption.`;\n\n content.appendChild(headline);\n content.appendChild(body);\n banner.appendChild(content);\n\n if (dunning.updatePaymentUrl) {\n const cta = document.createElement(\"a\");\n cta.className = \"cb-dunning-cta\";\n cta.href = dunning.updatePaymentUrl;\n cta.target = \"_blank\";\n cta.rel = \"noopener noreferrer\";\n cta.textContent = dunning.bannerConfig?.ctaText || \"Update Payment\";\n banner.appendChild(cta);\n }\n\n shadow.appendChild(banner);\n target.appendChild(this.shadowHost);\n }\n\n destroy(): void {\n this.destroyed = true;\n if (this.shadowHost && this.shadowHost.parentNode) {\n this.shadowHost.parentNode.removeChild(this.shadowHost);\n }\n this.shadowHost = null;\n }\n}\n","import { resolveConfig, type ChurnBackConfig } from \"./core/config\";\nimport { ApiClient, type DunningResponse } from \"./core/api\";\nimport { CancelFlowModal, type CancelFlowOptions, type CancelFlowResult } from \"./cancel-flow/modal\";\nimport { DunningBanner, type DunningBannerOptions } from \"./dunning/banner\";\n\nexport type { ChurnBackConfig, CancelFlowOptions, CancelFlowResult, DunningResponse };\n\nexport class ChurnBackClient {\n private api: ApiClient;\n private activeModal: CancelFlowModal | null = null;\n private activeBanners: DunningBanner[] = [];\n\n constructor(config: ChurnBackConfig) {\n if (!config.apiKey) {\n throw new Error(\"[ChurnBack] apiKey is required\");\n }\n const resolved = resolveConfig(config);\n this.api = new ApiClient(resolved);\n }\n\n async triggerCancelFlow(options: CancelFlowOptions): Promise<void> {\n this.activeModal?.destroy();\n\n const modal = new CancelFlowModal(this.api, options);\n this.activeModal = modal;\n\n try {\n await modal.open();\n } catch (err) {\n // If onError is provided, the consumer handles it — don't re-throw\n if (options.onError) return;\n throw err;\n }\n }\n\n async checkDunning(options: { stripeCustomerId: string }): Promise<DunningResponse> {\n return this.api.checkDunning(options.stripeCustomerId);\n }\n\n mountDunningBanner(options: DunningBannerOptions): void {\n const banner = new DunningBanner(this.api, options);\n this.activeBanners.push(banner);\n banner.mount();\n }\n\n destroy(): void {\n this.activeModal?.destroy();\n this.activeModal = null;\n this.activeBanners.forEach((b) => b.destroy());\n this.activeBanners = [];\n }\n}\n\nexport default ChurnBackClient;\n"],"mappings":"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","names":["resolveConfig","config","ApiClient","config","customerId","subscriptionId","priceId","params","res","body","payload","MODAL_STYLES","renderStep","step","state","callbacks","container","renderReasonSurvey","renderOffer","renderCustomMessage","renderConfirmation","dots","i","dot","config","headline","reasons","reasonsDiv","btn","el","textarea","nextBtn","body","acceptBtn","declineBtn","goBackBtn","cancelBtn","CancelFlowModal","api","options","flow","err","error","style","MODAL_STYLES","existing","step","overlay","e","modal","inner","closeBtn","stepContent","renderStep","id","text","offer","BANNER_STYLES","DunningBanner","api","options","dunning","target","shadow","style","BANNER_STYLES","banner","content","headline","body","cta","ChurnBackClient","config","resolved","resolveConfig","ApiClient","options","modal","CancelFlowModal","err","banner","DunningBanner","b","index_default"]}
|