@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/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/index.d.mts +49 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.global.js +252 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +252 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +252 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ChurnBack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# @churnback/sdk
|
|
2
|
+
|
|
3
|
+
Reduce churn with intelligent cancel flows and dunning banners. Drop-in JavaScript SDK for any web app.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @churnback/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import ChurnBackClient from "@churnback/sdk";
|
|
15
|
+
|
|
16
|
+
const churnback = new ChurnBackClient({
|
|
17
|
+
apiKey: "your-api-key",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Show a cancel flow when a user clicks "Cancel Subscription"
|
|
21
|
+
document.getElementById("cancel-btn").addEventListener("click", () => {
|
|
22
|
+
churnback.triggerCancelFlow({
|
|
23
|
+
stripeCustomerId: "cus_xxx",
|
|
24
|
+
stripeSubscriptionId: "sub_xxx",
|
|
25
|
+
onComplete: (result) => {
|
|
26
|
+
if (result.action === "saved") {
|
|
27
|
+
console.log("Customer saved!");
|
|
28
|
+
} else if (result.action === "cancelled") {
|
|
29
|
+
console.log("Customer cancelled:", result.reason);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### `new ChurnBackClient(config)`
|
|
39
|
+
|
|
40
|
+
| Option | Type | Required | Description |
|
|
41
|
+
|--------|------|----------|-------------|
|
|
42
|
+
| `apiKey` | `string` | Yes | Your ChurnBack API key |
|
|
43
|
+
| `apiBaseUrl` | `string` | No | Custom API base URL (defaults to `https://churnback.ai/api/sdk`) |
|
|
44
|
+
|
|
45
|
+
### `triggerCancelFlow(options)`
|
|
46
|
+
|
|
47
|
+
Opens an interactive cancel flow modal with surveys, offers, and confirmation steps.
|
|
48
|
+
|
|
49
|
+
| Option | Type | Required | Description |
|
|
50
|
+
|--------|------|----------|-------------|
|
|
51
|
+
| `stripeCustomerId` | `string` | Yes | Stripe customer ID |
|
|
52
|
+
| `stripeSubscriptionId` | `string` | Yes | Stripe subscription ID |
|
|
53
|
+
| `stripePriceId` | `string` | No | Stripe price ID for targeted offers |
|
|
54
|
+
| `onComplete` | `function` | No | Callback with `{ action, reason, comment, offerAccepted }` |
|
|
55
|
+
| `onDismiss` | `function` | No | Called when the user closes the modal |
|
|
56
|
+
|
|
57
|
+
The `action` in the result is one of: `"saved"`, `"cancelled"`, or `"dismissed"`.
|
|
58
|
+
|
|
59
|
+
### `checkDunning(options)`
|
|
60
|
+
|
|
61
|
+
Checks if a customer has a failed payment.
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
const result = await churnback.checkDunning({
|
|
65
|
+
stripeCustomerId: "cus_xxx",
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `mountDunningBanner(options)`
|
|
70
|
+
|
|
71
|
+
Renders a payment failure banner inside a target element.
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
churnback.mountDunningBanner({
|
|
75
|
+
stripeCustomerId: "cus_xxx",
|
|
76
|
+
targetElement: "#billing-section", // CSS selector or HTMLElement
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `destroy()`
|
|
81
|
+
|
|
82
|
+
Cleans up all modals and banners.
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
churnback.destroy();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Script Tag Usage
|
|
89
|
+
|
|
90
|
+
For apps that don't use a bundler:
|
|
91
|
+
|
|
92
|
+
```html
|
|
93
|
+
<script src="https://unpkg.com/@churnback/sdk/dist/index.global.js"></script>
|
|
94
|
+
<script>
|
|
95
|
+
const churnback = new ChurnBack({ apiKey: "your-api-key" });
|
|
96
|
+
</script>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
interface ChurnBackConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
apiBaseUrl?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface DunningResponse {
|
|
7
|
+
hasFailedPayment: boolean;
|
|
8
|
+
amountDueCents?: number;
|
|
9
|
+
updatePaymentUrl?: string;
|
|
10
|
+
bannerConfig?: {
|
|
11
|
+
headline: string;
|
|
12
|
+
body: string;
|
|
13
|
+
ctaText: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface CancelFlowOptions {
|
|
18
|
+
stripeCustomerId: string;
|
|
19
|
+
stripeSubscriptionId: string;
|
|
20
|
+
stripePriceId?: string;
|
|
21
|
+
onComplete?: (result: CancelFlowResult) => void;
|
|
22
|
+
onDismiss?: () => void;
|
|
23
|
+
}
|
|
24
|
+
interface CancelFlowResult {
|
|
25
|
+
action: "saved" | "cancelled" | "dismissed";
|
|
26
|
+
reason?: string;
|
|
27
|
+
comment?: string;
|
|
28
|
+
offerAccepted?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface DunningBannerOptions {
|
|
32
|
+
stripeCustomerId: string;
|
|
33
|
+
targetElement: string | HTMLElement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare class ChurnBackClient {
|
|
37
|
+
private api;
|
|
38
|
+
private activeModal;
|
|
39
|
+
private activeBanners;
|
|
40
|
+
constructor(config: ChurnBackConfig);
|
|
41
|
+
triggerCancelFlow(options: CancelFlowOptions): Promise<void>;
|
|
42
|
+
checkDunning(options: {
|
|
43
|
+
stripeCustomerId: string;
|
|
44
|
+
}): Promise<DunningResponse>;
|
|
45
|
+
mountDunningBanner(options: DunningBannerOptions): void;
|
|
46
|
+
destroy(): void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { type CancelFlowOptions, type CancelFlowResult, ChurnBackClient, type ChurnBackConfig, type DunningResponse, ChurnBackClient as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
interface ChurnBackConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
apiBaseUrl?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface DunningResponse {
|
|
7
|
+
hasFailedPayment: boolean;
|
|
8
|
+
amountDueCents?: number;
|
|
9
|
+
updatePaymentUrl?: string;
|
|
10
|
+
bannerConfig?: {
|
|
11
|
+
headline: string;
|
|
12
|
+
body: string;
|
|
13
|
+
ctaText: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface CancelFlowOptions {
|
|
18
|
+
stripeCustomerId: string;
|
|
19
|
+
stripeSubscriptionId: string;
|
|
20
|
+
stripePriceId?: string;
|
|
21
|
+
onComplete?: (result: CancelFlowResult) => void;
|
|
22
|
+
onDismiss?: () => void;
|
|
23
|
+
}
|
|
24
|
+
interface CancelFlowResult {
|
|
25
|
+
action: "saved" | "cancelled" | "dismissed";
|
|
26
|
+
reason?: string;
|
|
27
|
+
comment?: string;
|
|
28
|
+
offerAccepted?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface DunningBannerOptions {
|
|
32
|
+
stripeCustomerId: string;
|
|
33
|
+
targetElement: string | HTMLElement;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare class ChurnBackClient {
|
|
37
|
+
private api;
|
|
38
|
+
private activeModal;
|
|
39
|
+
private activeBanners;
|
|
40
|
+
constructor(config: ChurnBackConfig);
|
|
41
|
+
triggerCancelFlow(options: CancelFlowOptions): Promise<void>;
|
|
42
|
+
checkDunning(options: {
|
|
43
|
+
stripeCustomerId: string;
|
|
44
|
+
}): Promise<DunningResponse>;
|
|
45
|
+
mountDunningBanner(options: DunningBannerOptions): void;
|
|
46
|
+
destroy(): void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { type CancelFlowOptions, type CancelFlowResult, ChurnBackClient, type ChurnBackConfig, type DunningResponse, ChurnBackClient as default };
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";var ChurnBack=(()=>{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});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="×",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;return S(I);})();
|
|
251
|
+
if(typeof ChurnBack!=='undefined'&&ChurnBack.default){ChurnBack=ChurnBack.default;}
|
|
252
|
+
//# sourceMappingURL=index.global.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 = \"×\";\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","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"]}
|