@churnback/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,252 @@
1
+ "use strict";var u=Object.defineProperty;var C=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})},R=(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=C(e,s))||n.enumerable});return o};var S=o=>R(u({},"__esModule",{value:!0}),o);var I={};k(I,{ChurnBackClient:()=>h,default:()=>D});module.exports=S(I);function b(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 f=`
2
+ * { box-sizing: border-box; margin: 0; padding: 0; }
3
+
4
+ .cb-overlay {
5
+ position: fixed;
6
+ inset: 0;
7
+ background: rgba(0, 0, 0, 0.5);
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ z-index: 999999;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
13
+ animation: cb-fade-in 0.2s ease;
14
+ }
15
+
16
+ @keyframes cb-fade-in {
17
+ from { opacity: 0; }
18
+ to { opacity: 1; }
19
+ }
20
+
21
+ @keyframes cb-slide-up {
22
+ from { transform: translateY(20px); opacity: 0; }
23
+ to { transform: translateY(0); opacity: 1; }
24
+ }
25
+
26
+ .cb-modal {
27
+ background: white;
28
+ border-radius: 16px;
29
+ padding: 32px;
30
+ max-width: 480px;
31
+ width: 90%;
32
+ max-height: 90vh;
33
+ overflow-y: auto;
34
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
35
+ animation: cb-slide-up 0.3s ease;
36
+ }
37
+
38
+ .cb-headline {
39
+ font-size: 22px;
40
+ font-weight: 700;
41
+ color: #1a1a1a;
42
+ margin-bottom: 12px;
43
+ line-height: 1.3;
44
+ }
45
+
46
+ .cb-body {
47
+ font-size: 15px;
48
+ color: #555;
49
+ line-height: 1.6;
50
+ margin-bottom: 20px;
51
+ }
52
+
53
+ .cb-reasons {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 8px;
57
+ margin-bottom: 16px;
58
+ }
59
+
60
+ .cb-reason {
61
+ padding: 12px 16px;
62
+ border: 2px solid #e0e0e0;
63
+ border-radius: 10px;
64
+ cursor: pointer;
65
+ font-size: 14px;
66
+ color: #333;
67
+ transition: all 0.15s ease;
68
+ background: white;
69
+ text-align: left;
70
+ }
71
+
72
+ .cb-reason:hover {
73
+ border-color: #7c3aed;
74
+ background: #f5f3ff;
75
+ }
76
+
77
+ .cb-reason.selected {
78
+ border-color: #7c3aed;
79
+ background: #f5f3ff;
80
+ color: #7c3aed;
81
+ font-weight: 500;
82
+ }
83
+
84
+ .cb-textarea {
85
+ width: 100%;
86
+ min-height: 80px;
87
+ padding: 12px;
88
+ border: 2px solid #e0e0e0;
89
+ border-radius: 10px;
90
+ font-size: 14px;
91
+ font-family: inherit;
92
+ resize: vertical;
93
+ margin-bottom: 16px;
94
+ outline: none;
95
+ transition: border-color 0.15s ease;
96
+ }
97
+
98
+ .cb-textarea:focus {
99
+ border-color: #7c3aed;
100
+ }
101
+
102
+ .cb-btn {
103
+ display: block;
104
+ width: 100%;
105
+ padding: 12px 20px;
106
+ border-radius: 10px;
107
+ font-size: 15px;
108
+ font-weight: 600;
109
+ cursor: pointer;
110
+ border: none;
111
+ transition: all 0.15s ease;
112
+ margin-bottom: 8px;
113
+ }
114
+
115
+ .cb-btn-primary {
116
+ background: #7c3aed;
117
+ color: white;
118
+ }
119
+
120
+ .cb-btn-primary:hover {
121
+ background: #6d28d9;
122
+ }
123
+
124
+ .cb-btn-danger {
125
+ background: #ef4444;
126
+ color: white;
127
+ }
128
+
129
+ .cb-btn-danger:hover {
130
+ background: #dc2626;
131
+ }
132
+
133
+ .cb-btn-secondary {
134
+ background: transparent;
135
+ color: #7c3aed;
136
+ border: 2px solid #7c3aed;
137
+ }
138
+
139
+ .cb-btn-secondary:hover {
140
+ background: #f5f3ff;
141
+ }
142
+
143
+ .cb-btn-ghost {
144
+ background: transparent;
145
+ color: #888;
146
+ }
147
+
148
+ .cb-btn-ghost:hover {
149
+ color: #555;
150
+ }
151
+
152
+ .cb-step-dots {
153
+ display: flex;
154
+ justify-content: center;
155
+ gap: 6px;
156
+ margin-top: 16px;
157
+ }
158
+
159
+ .cb-dot {
160
+ width: 8px;
161
+ height: 8px;
162
+ border-radius: 50%;
163
+ background: #e0e0e0;
164
+ transition: background 0.2s ease;
165
+ }
166
+
167
+ .cb-dot.active {
168
+ background: #7c3aed;
169
+ }
170
+
171
+ .cb-close {
172
+ position: absolute;
173
+ top: 12px;
174
+ right: 12px;
175
+ background: none;
176
+ border: none;
177
+ font-size: 24px;
178
+ color: #888;
179
+ cursor: pointer;
180
+ width: 32px;
181
+ height: 32px;
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ border-radius: 50%;
186
+ }
187
+
188
+ .cb-close:hover {
189
+ background: #f0f0f0;
190
+ color: #333;
191
+ }
192
+
193
+ .cb-modal-inner {
194
+ position: relative;
195
+ }
196
+ `;function g(o,e,t){let n=document.createElement("div");switch(o.type){case"reason_survey":E(n,o.config,e,t);break;case"offer":N(n,o.config,t);break;case"custom_message":B(n,o.config,t);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 E(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(x=>x.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 N(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 B(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=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){throw console.error("[ChurnBack] Failed to load cancel flow:",n),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=f,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="&times;",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:()=>this.nextStep(),onConfirmCancel:()=>this.confirmCancel(),onGoBack:()=>this.dismiss()});i.appendChild(d),s.appendChild(i),n.appendChild(s),this.shadowRoot.appendChild(n)}nextStep(){this.state.currentIndex<this.steps.length-1&&(this.state.currentIndex++,this.renderCurrentStep())}async acceptOffer(e){try{await 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)}this.close(),this.options.onComplete?.({action:"saved",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0,offerAccepted:e})}async confirmCancel(){try{await 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)}this.close(),this.options.onComplete?.({action:"cancelled",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0})}async dismiss(){try{await this.api.submitResult({sessionId:this.sessionId,action:"abandoned"})}catch{}this.close(),this.options.onDismiss?.(),this.options.onComplete?.({action:"dismissed"})}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 y=`
197
+ .cb-dunning-banner {
198
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
199
+ background: #fef2f2;
200
+ border: 1px solid #fecaca;
201
+ border-radius: 10px;
202
+ padding: 16px 20px;
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: space-between;
206
+ gap: 16px;
207
+ flex-wrap: wrap;
208
+ }
209
+
210
+ .cb-dunning-content {
211
+ flex: 1;
212
+ min-width: 200px;
213
+ }
214
+
215
+ .cb-dunning-headline {
216
+ font-size: 15px;
217
+ font-weight: 600;
218
+ color: #991b1b;
219
+ margin-bottom: 4px;
220
+ }
221
+
222
+ .cb-dunning-body {
223
+ font-size: 13px;
224
+ color: #b91c1c;
225
+ line-height: 1.4;
226
+ }
227
+
228
+ .cb-dunning-cta {
229
+ padding: 8px 16px;
230
+ background: #ef4444;
231
+ color: white;
232
+ border: none;
233
+ border-radius: 8px;
234
+ font-size: 14px;
235
+ font-weight: 600;
236
+ cursor: pointer;
237
+ white-space: nowrap;
238
+ transition: background 0.15s ease;
239
+ text-decoration: none;
240
+ display: inline-block;
241
+ }
242
+
243
+ .cb-dunning-cta:hover {
244
+ background: #dc2626;
245
+ }
246
+
247
+ .cb-dunning-hidden {
248
+ display: none;
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=y,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=b(e);this.api=new l(t)}async triggerCancelFlow(e){this.activeModal?.destroy();let t=new p(this.api,e);this.activeModal=t,await t.open()}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=[]}},D=h;0&&(module.exports={ChurnBackClient});
251
+ if(typeof ChurnBack!=='undefined'&&ChurnBack.default){ChurnBack=ChurnBack.default;}
252
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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 await modal.open();\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, callbacks);\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 }\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 = 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}\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 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 = \"&times;\";\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 }\n }\n\n private async acceptOffer(offer: Record<string, unknown>): Promise<void> {\n try {\n await 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 });\n } catch (err) {\n console.error(\"[ChurnBack] Failed to submit offer acceptance:\", err);\n }\n\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 }\n\n private async confirmCancel(): Promise<void> {\n try {\n await this.api.submitResult({\n sessionId: this.sessionId,\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n });\n } catch (err) {\n console.error(\"[ChurnBack] Failed to submit cancellation:\", err);\n }\n\n this.close();\n this.options.onComplete?.({\n action: \"cancelled\",\n reason: this.state.selectedReason || undefined,\n comment: this.state.comment || undefined,\n });\n }\n\n private async dismiss(): Promise<void> {\n try {\n await this.api.submitResult({\n sessionId: this.sessionId,\n action: \"abandoned\",\n });\n } catch {\n // Silent fail on dismiss\n }\n\n this.close();\n this.options.onDismiss?.();\n this.options.onComplete?.({ action: \"dismissed\" });\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","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","banner","DunningBanner","b","index_default"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,252 @@
1
+ function u(s){return{apiKey:s.apiKey,apiBaseUrl:(s.apiBaseUrl||"https://churnback.ai/api/sdk").replace(/\/$/,"")}}var l=class{constructor(e){this.config=e}async fetchCancelFlow(e,t,n){let i=new URLSearchParams({customer_id:e,subscription_id:t});n&&i.set("price_id",n);let o=await fetch(`${this.config.apiBaseUrl}/cancel-flow?${i}`,{headers:{"x-churnback-key":this.config.apiKey}});if(!o.ok){let a=await o.json().catch(()=>({}));throw new Error(a.error||`Failed to fetch cancel flow (${o.status})`)}return o.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
+ * { box-sizing: border-box; margin: 0; padding: 0; }
3
+
4
+ .cb-overlay {
5
+ position: fixed;
6
+ inset: 0;
7
+ background: rgba(0, 0, 0, 0.5);
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ z-index: 999999;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
13
+ animation: cb-fade-in 0.2s ease;
14
+ }
15
+
16
+ @keyframes cb-fade-in {
17
+ from { opacity: 0; }
18
+ to { opacity: 1; }
19
+ }
20
+
21
+ @keyframes cb-slide-up {
22
+ from { transform: translateY(20px); opacity: 0; }
23
+ to { transform: translateY(0); opacity: 1; }
24
+ }
25
+
26
+ .cb-modal {
27
+ background: white;
28
+ border-radius: 16px;
29
+ padding: 32px;
30
+ max-width: 480px;
31
+ width: 90%;
32
+ max-height: 90vh;
33
+ overflow-y: auto;
34
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
35
+ animation: cb-slide-up 0.3s ease;
36
+ }
37
+
38
+ .cb-headline {
39
+ font-size: 22px;
40
+ font-weight: 700;
41
+ color: #1a1a1a;
42
+ margin-bottom: 12px;
43
+ line-height: 1.3;
44
+ }
45
+
46
+ .cb-body {
47
+ font-size: 15px;
48
+ color: #555;
49
+ line-height: 1.6;
50
+ margin-bottom: 20px;
51
+ }
52
+
53
+ .cb-reasons {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 8px;
57
+ margin-bottom: 16px;
58
+ }
59
+
60
+ .cb-reason {
61
+ padding: 12px 16px;
62
+ border: 2px solid #e0e0e0;
63
+ border-radius: 10px;
64
+ cursor: pointer;
65
+ font-size: 14px;
66
+ color: #333;
67
+ transition: all 0.15s ease;
68
+ background: white;
69
+ text-align: left;
70
+ }
71
+
72
+ .cb-reason:hover {
73
+ border-color: #7c3aed;
74
+ background: #f5f3ff;
75
+ }
76
+
77
+ .cb-reason.selected {
78
+ border-color: #7c3aed;
79
+ background: #f5f3ff;
80
+ color: #7c3aed;
81
+ font-weight: 500;
82
+ }
83
+
84
+ .cb-textarea {
85
+ width: 100%;
86
+ min-height: 80px;
87
+ padding: 12px;
88
+ border: 2px solid #e0e0e0;
89
+ border-radius: 10px;
90
+ font-size: 14px;
91
+ font-family: inherit;
92
+ resize: vertical;
93
+ margin-bottom: 16px;
94
+ outline: none;
95
+ transition: border-color 0.15s ease;
96
+ }
97
+
98
+ .cb-textarea:focus {
99
+ border-color: #7c3aed;
100
+ }
101
+
102
+ .cb-btn {
103
+ display: block;
104
+ width: 100%;
105
+ padding: 12px 20px;
106
+ border-radius: 10px;
107
+ font-size: 15px;
108
+ font-weight: 600;
109
+ cursor: pointer;
110
+ border: none;
111
+ transition: all 0.15s ease;
112
+ margin-bottom: 8px;
113
+ }
114
+
115
+ .cb-btn-primary {
116
+ background: #7c3aed;
117
+ color: white;
118
+ }
119
+
120
+ .cb-btn-primary:hover {
121
+ background: #6d28d9;
122
+ }
123
+
124
+ .cb-btn-danger {
125
+ background: #ef4444;
126
+ color: white;
127
+ }
128
+
129
+ .cb-btn-danger:hover {
130
+ background: #dc2626;
131
+ }
132
+
133
+ .cb-btn-secondary {
134
+ background: transparent;
135
+ color: #7c3aed;
136
+ border: 2px solid #7c3aed;
137
+ }
138
+
139
+ .cb-btn-secondary:hover {
140
+ background: #f5f3ff;
141
+ }
142
+
143
+ .cb-btn-ghost {
144
+ background: transparent;
145
+ color: #888;
146
+ }
147
+
148
+ .cb-btn-ghost:hover {
149
+ color: #555;
150
+ }
151
+
152
+ .cb-step-dots {
153
+ display: flex;
154
+ justify-content: center;
155
+ gap: 6px;
156
+ margin-top: 16px;
157
+ }
158
+
159
+ .cb-dot {
160
+ width: 8px;
161
+ height: 8px;
162
+ border-radius: 50%;
163
+ background: #e0e0e0;
164
+ transition: background 0.2s ease;
165
+ }
166
+
167
+ .cb-dot.active {
168
+ background: #7c3aed;
169
+ }
170
+
171
+ .cb-close {
172
+ position: absolute;
173
+ top: 12px;
174
+ right: 12px;
175
+ background: none;
176
+ border: none;
177
+ font-size: 24px;
178
+ color: #888;
179
+ cursor: pointer;
180
+ width: 32px;
181
+ height: 32px;
182
+ display: flex;
183
+ align-items: center;
184
+ justify-content: center;
185
+ border-radius: 50%;
186
+ }
187
+
188
+ .cb-close:hover {
189
+ background: #f0f0f0;
190
+ color: #333;
191
+ }
192
+
193
+ .cb-modal-inner {
194
+ position: relative;
195
+ }
196
+ `;function f(s,e,t){let n=document.createElement("div");switch(s.type){case"reason_survey":x(n,s.config,e,t);break;case"offer":C(n,s.config,t);break;case"custom_message":w(n,s.config,t);break;case"confirmation":v(n,s.config,t);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)}n.appendChild(i)}return n}function x(s,e,t,n){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${t.selectedReason===r.id?" selected":""}`,c.textContent=r.label,c.onclick=()=>{n.onSelectReason(r.id),a.querySelectorAll(".cb-reason").forEach(y=>y.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=t.comment,r.oninput=()=>n.onComment(r.value),s.appendChild(r)}let d=document.createElement("button");d.className="cb-btn cb-btn-primary",d.textContent="Continue",d.onclick=n.onNext,s.appendChild(d)}function C(s,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"Special offer for you",s.appendChild(n);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=()=>t.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=t.onDeclineOffer,s.appendChild(a)}function w(s,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"",s.appendChild(n);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=t.onNext,s.appendChild(o)}}function v(s,e,t){let n=document.createElement("h2");n.className="cb-headline",n.textContent=e.headline||"Are you sure?",s.appendChild(n);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=t.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=t.onConfirmCancel,s.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){throw console.error("[ChurnBack] Failed to load cancel flow:",n),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 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="&times;",a.onclick=()=>this.dismiss(),o.appendChild(a);let d=f(t,this.state,{onNext:()=>this.nextStep(),onSelectReason:r=>{this.state.selectedReason=r},onComment:r=>{this.state.comment=r},onAcceptOffer:r=>this.acceptOffer(r),onDeclineOffer:()=>this.nextStep(),onConfirmCancel:()=>this.confirmCancel(),onGoBack:()=>this.dismiss()});o.appendChild(d),i.appendChild(o),n.appendChild(i),this.shadowRoot.appendChild(n)}nextStep(){this.state.currentIndex<this.steps.length-1&&(this.state.currentIndex++,this.renderCurrentStep())}async acceptOffer(e){try{await 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)}this.close(),this.options.onComplete?.({action:"saved",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0,offerAccepted:e})}async confirmCancel(){try{await 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)}this.close(),this.options.onComplete?.({action:"cancelled",reason:this.state.selectedReason||void 0,comment:this.state.comment||void 0})}async dismiss(){try{await this.api.submitResult({sessionId:this.sessionId,action:"abandoned"})}catch{}this.close(),this.options.onDismiss?.(),this.options.onComplete?.({action:"dismissed"})}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
+ .cb-dunning-banner {
198
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
199
+ background: #fef2f2;
200
+ border: 1px solid #fecaca;
201
+ border-radius: 10px;
202
+ padding: 16px 20px;
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: space-between;
206
+ gap: 16px;
207
+ flex-wrap: wrap;
208
+ }
209
+
210
+ .cb-dunning-content {
211
+ flex: 1;
212
+ min-width: 200px;
213
+ }
214
+
215
+ .cb-dunning-headline {
216
+ font-size: 15px;
217
+ font-weight: 600;
218
+ color: #991b1b;
219
+ margin-bottom: 4px;
220
+ }
221
+
222
+ .cb-dunning-body {
223
+ font-size: 13px;
224
+ color: #b91c1c;
225
+ line-height: 1.4;
226
+ }
227
+
228
+ .cb-dunning-cta {
229
+ padding: 8px 16px;
230
+ background: #ef4444;
231
+ color: white;
232
+ border: none;
233
+ border-radius: 8px;
234
+ font-size: 14px;
235
+ font-weight: 600;
236
+ cursor: pointer;
237
+ white-space: nowrap;
238
+ transition: background 0.15s ease;
239
+ text-decoration: none;
240
+ display: inline-block;
241
+ }
242
+
243
+ .cb-dunning-cta:hover {
244
+ background: #dc2626;
245
+ }
246
+
247
+ .cb-dunning-hidden {
248
+ display: none;
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"}),i=document.createElement("style");i.textContent=g,n.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)}n.appendChild(o),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=u(e);this.api=new l(t)}async triggerCancelFlow(e){this.activeModal?.destroy();let t=new p(this.api,e);this.activeModal=t,await t.open()}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=[]}},A=h;export{h as ChurnBackClient,A as default};
251
+ if(typeof ChurnBack!=='undefined'&&ChurnBack.default){ChurnBack=ChurnBack.default;}
252
+ //# sourceMappingURL=index.mjs.map