@funnelfox/payments-sdk 0.1.2-beta.1 → 0.1.2-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +167 -23
- package/dist/index.d.cts +66 -37
- package/dist/index.d.ts +66 -37
- package/dist/index.js +167 -23
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -33,53 +33,187 @@ var CheckoutError = class extends Error {
|
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
// src/providers/
|
|
37
|
-
var
|
|
38
|
-
|
|
36
|
+
// src/providers/stripe/index.ts
|
|
37
|
+
var import_stripe_js = require("@stripe/stripe-js");
|
|
38
|
+
|
|
39
|
+
// src/http.ts
|
|
40
|
+
var HttpService = class {
|
|
41
|
+
constructor(baseUrl, headers = {}) {
|
|
42
|
+
this.baseUrl = baseUrl;
|
|
43
|
+
this.headers = headers;
|
|
44
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
45
|
+
}
|
|
46
|
+
baseUrl;
|
|
47
|
+
headers;
|
|
48
|
+
async post(path, body) {
|
|
49
|
+
return this.request("POST", path, body);
|
|
50
|
+
}
|
|
51
|
+
async get(path) {
|
|
52
|
+
return this.request("GET", path);
|
|
53
|
+
}
|
|
54
|
+
async request(method, path, body) {
|
|
55
|
+
const url = `${this.baseUrl}${path}`;
|
|
56
|
+
const headers = new Headers(this.headers);
|
|
57
|
+
headers.set("Content-Type", "application/json");
|
|
58
|
+
let response;
|
|
59
|
+
try {
|
|
60
|
+
response = await fetch(url, {
|
|
61
|
+
method,
|
|
62
|
+
headers,
|
|
63
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
throw new CheckoutError(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
67
|
+
}
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const text = await response.text().catch(() => response.statusText);
|
|
70
|
+
throw new CheckoutError(`Backend error ${response.status}: ${text}`);
|
|
71
|
+
}
|
|
72
|
+
return response.json();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/providers/stripe/service.ts
|
|
77
|
+
var StripeCheckoutService = class extends HttpService {
|
|
78
|
+
constructor(baseUrl, headers) {
|
|
79
|
+
super(baseUrl, headers);
|
|
80
|
+
}
|
|
81
|
+
async getSession(sessionId, account) {
|
|
82
|
+
const query = account ? `?account=${encodeURIComponent(account)}` : "";
|
|
83
|
+
return this.get(`/stripe/session/${sessionId}${query}`);
|
|
84
|
+
}
|
|
85
|
+
async createCheckoutSession(params) {
|
|
86
|
+
const query = `?account=${encodeURIComponent(params.accountId)}`;
|
|
87
|
+
const data = await this.post(`/stripe/purchase${query}`, {
|
|
88
|
+
price_id: params.priceId,
|
|
89
|
+
managed: params.managed,
|
|
90
|
+
customer_id: params.customerId,
|
|
91
|
+
customer: params.customer,
|
|
92
|
+
return_url: params.returnUrl
|
|
93
|
+
});
|
|
94
|
+
if (!data.client_secret) {
|
|
95
|
+
throw new CheckoutError("Backend returned no client_secret");
|
|
96
|
+
}
|
|
97
|
+
return data.client_secret;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/providers/stripe/index.ts
|
|
102
|
+
var StripeProvider = class {
|
|
103
|
+
constructor(config, callbacks, baseUrl, headers) {
|
|
39
104
|
this.config = config;
|
|
40
105
|
this.callbacks = callbacks;
|
|
106
|
+
this.service = new StripeCheckoutService(baseUrl, headers);
|
|
41
107
|
}
|
|
42
108
|
config;
|
|
43
109
|
callbacks;
|
|
110
|
+
stripe = null;
|
|
111
|
+
embeddedCheckout = null;
|
|
112
|
+
service;
|
|
113
|
+
clientSecret = null;
|
|
44
114
|
async init() {
|
|
115
|
+
const stripe = await (0, import_stripe_js.loadStripe)(this.config.publishableKey);
|
|
116
|
+
if (!stripe) {
|
|
117
|
+
throw new CheckoutError("Failed to load Stripe.js \u2014 verify your publishableKey");
|
|
118
|
+
}
|
|
119
|
+
this.stripe = stripe;
|
|
45
120
|
}
|
|
46
|
-
render(container, options) {
|
|
47
|
-
|
|
121
|
+
async render(container, options) {
|
|
122
|
+
if (!this.stripe) {
|
|
123
|
+
throw new CheckoutError("Provider not initialized \u2014 call init() before render()");
|
|
124
|
+
}
|
|
125
|
+
if (this.embeddedCheckout) {
|
|
126
|
+
throw new CheckoutError("Already rendered \u2014 call destroy() before rendering again");
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
this.embeddedCheckout = await this.stripe.createEmbeddedCheckoutPage({
|
|
130
|
+
fetchClientSecret: () => this.fetchClientSecret(),
|
|
131
|
+
onComplete: () => this.handleComplete(),
|
|
132
|
+
onAnalyticsEvent: (event) => console.log("[stripe analytics]", event.eventType, event)
|
|
133
|
+
});
|
|
134
|
+
} catch (err) {
|
|
135
|
+
this.callbacks.onError?.({ provider: "stripe", error: err });
|
|
136
|
+
if (err instanceof CheckoutError) throw err;
|
|
137
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
138
|
+
throw new CheckoutError(`Stripe checkout failed: ${detail}`);
|
|
139
|
+
}
|
|
140
|
+
this.embeddedCheckout.mount(container);
|
|
48
141
|
}
|
|
49
|
-
reset() {
|
|
50
|
-
|
|
142
|
+
async reset() {
|
|
143
|
+
this.destroy();
|
|
51
144
|
}
|
|
52
145
|
destroy() {
|
|
53
|
-
this.
|
|
146
|
+
this.embeddedCheckout?.destroy();
|
|
147
|
+
this.embeddedCheckout = null;
|
|
148
|
+
}
|
|
149
|
+
async fetchClientSecret() {
|
|
150
|
+
const secret = await this.service.createCheckoutSession({
|
|
151
|
+
...this.config,
|
|
152
|
+
returnUrl: this.config.returnUrl ?? window.location.href
|
|
153
|
+
});
|
|
154
|
+
this.clientSecret = secret;
|
|
155
|
+
return secret;
|
|
156
|
+
}
|
|
157
|
+
// Format: cs_test_{sessionId}_secret_{random}
|
|
158
|
+
parseSessionId(clientSecret) {
|
|
159
|
+
if (!clientSecret) return null;
|
|
160
|
+
const idx = clientSecret.indexOf("_secret_");
|
|
161
|
+
return idx !== -1 ? clientSecret.slice(0, idx) : null;
|
|
162
|
+
}
|
|
163
|
+
async handleComplete() {
|
|
164
|
+
const sessionId = this.parseSessionId(this.clientSecret);
|
|
165
|
+
if (!sessionId) {
|
|
166
|
+
this.callbacks.onComplete?.({ provider: "stripe" });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const session = await this.service.getSession(sessionId, this.config.accountId);
|
|
171
|
+
this.callbacks.onComplete?.({
|
|
172
|
+
provider: "stripe",
|
|
173
|
+
customerId: session.customer_id ?? void 0,
|
|
174
|
+
subscriptionId: session.subscription_id ?? void 0,
|
|
175
|
+
invoiceId: session.invoice_id ?? void 0,
|
|
176
|
+
amountPaid: session.amount_total ?? void 0,
|
|
177
|
+
currency: session.currency ?? void 0
|
|
178
|
+
});
|
|
179
|
+
} catch {
|
|
180
|
+
this.callbacks.onComplete?.({ provider: "stripe" });
|
|
181
|
+
}
|
|
54
182
|
}
|
|
55
183
|
};
|
|
56
184
|
|
|
57
|
-
// src/providers/
|
|
58
|
-
var
|
|
59
|
-
constructor(config, callbacks) {
|
|
185
|
+
// src/providers/fastspring/index.ts
|
|
186
|
+
var FastSpringProvider = class {
|
|
187
|
+
constructor(config, callbacks, baseUrl, headers = {}) {
|
|
60
188
|
this.config = config;
|
|
61
189
|
this.callbacks = callbacks;
|
|
190
|
+
this.baseUrl = baseUrl;
|
|
191
|
+
this.headers = headers;
|
|
62
192
|
}
|
|
63
193
|
config;
|
|
64
194
|
callbacks;
|
|
195
|
+
baseUrl;
|
|
196
|
+
headers;
|
|
65
197
|
async init() {
|
|
66
198
|
}
|
|
67
|
-
render(container, options) {
|
|
68
|
-
container.innerHTML = `<div>Hello World from
|
|
199
|
+
async render(container, options) {
|
|
200
|
+
container.innerHTML = `<div>Hello World from FastSpring [${options?.paymentMethodTypes?.join(", ")}]</div>`;
|
|
69
201
|
}
|
|
70
|
-
reset() {
|
|
202
|
+
async reset() {
|
|
203
|
+
window.fastspring?.builder?.reset();
|
|
71
204
|
}
|
|
72
205
|
destroy() {
|
|
206
|
+
this.reset();
|
|
73
207
|
}
|
|
74
208
|
};
|
|
75
209
|
|
|
76
210
|
// src/providers/registry.ts
|
|
77
|
-
function createProvider(provider, config, callbacks) {
|
|
211
|
+
function createProvider(provider, config, callbacks, baseUrl, headers) {
|
|
78
212
|
switch (provider) {
|
|
79
213
|
case "stripe":
|
|
80
|
-
return new StripeProvider(config, callbacks);
|
|
214
|
+
return new StripeProvider(config, callbacks, baseUrl, headers);
|
|
81
215
|
case "fastspring":
|
|
82
|
-
return new FastSpringProvider(config, callbacks);
|
|
216
|
+
return new FastSpringProvider(config, callbacks, baseUrl, headers);
|
|
83
217
|
default:
|
|
84
218
|
throw new CheckoutError(`Unknown provider: ${provider}`);
|
|
85
219
|
}
|
|
@@ -91,25 +225,35 @@ var CheckoutInstance = class {
|
|
|
91
225
|
this.provider = provider;
|
|
92
226
|
}
|
|
93
227
|
provider;
|
|
94
|
-
|
|
228
|
+
container = null;
|
|
229
|
+
options;
|
|
230
|
+
async render(container, options) {
|
|
95
231
|
const el = typeof container === "string" ? document.querySelector(container) : container;
|
|
96
232
|
if (!el) {
|
|
97
233
|
throw new CheckoutError(`Container not found: ${container}`);
|
|
98
234
|
}
|
|
99
|
-
this.
|
|
235
|
+
this.container = el;
|
|
236
|
+
this.options = options;
|
|
237
|
+
await this.provider.render(el, options);
|
|
100
238
|
}
|
|
101
|
-
reset() {
|
|
102
|
-
this.
|
|
239
|
+
async reset() {
|
|
240
|
+
if (!this.container) {
|
|
241
|
+
throw new CheckoutError("Cannot reset: render() has not been called");
|
|
242
|
+
}
|
|
243
|
+
await this.provider.reset();
|
|
244
|
+
await this.provider.render(this.container, this.options);
|
|
103
245
|
}
|
|
104
246
|
destroy() {
|
|
105
247
|
this.provider.destroy();
|
|
248
|
+
this.container = null;
|
|
249
|
+
this.options = void 0;
|
|
106
250
|
}
|
|
107
251
|
};
|
|
108
252
|
|
|
109
253
|
// src/checkout.ts
|
|
110
254
|
async function initCheckout(options) {
|
|
111
|
-
const { provider: providerName, config, ...callbacks } = options;
|
|
112
|
-
const provider = createProvider(providerName, config, callbacks);
|
|
255
|
+
const { provider: providerName, config, baseUrl, headers = {}, ...callbacks } = options;
|
|
256
|
+
const provider = createProvider(providerName, config, callbacks, baseUrl, headers);
|
|
113
257
|
await provider.init();
|
|
114
258
|
return new CheckoutInstance(provider);
|
|
115
259
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,39 +1,28 @@
|
|
|
1
1
|
type PaymentMethodType = 'card' | 'google_pay' | 'apple_pay';
|
|
2
2
|
|
|
3
|
-
interface
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
interface CheckoutCancelEvent {
|
|
12
|
-
paymentMethodType: PaymentMethodType;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface CheckoutCallbacks {
|
|
16
|
-
onComplete?: (event: CheckoutCompleteEvent) => void;
|
|
17
|
-
onError?: (event: CheckoutErrorEvent) => void;
|
|
18
|
-
onCancel?: (event: CheckoutCancelEvent) => void;
|
|
3
|
+
interface StripeCustomer {
|
|
4
|
+
email?: string;
|
|
5
|
+
phone?: string;
|
|
6
|
+
address?: {
|
|
7
|
+
country?: string;
|
|
8
|
+
postal_code?: string;
|
|
9
|
+
};
|
|
19
10
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
reset(): void;
|
|
36
|
-
destroy(): void;
|
|
11
|
+
interface StripeConfig {
|
|
12
|
+
/** Stripe publishable key (pk_test_... or pk_live_...) */
|
|
13
|
+
publishableKey: string;
|
|
14
|
+
/** Stripe Price ID for the product being sold */
|
|
15
|
+
priceId: string;
|
|
16
|
+
/** Whether to use Stripe Managed Payments (embedded checkout, no upfront payment method needed) */
|
|
17
|
+
managed: boolean;
|
|
18
|
+
/** Stripe Connect account ID (acct_...) — required when price belongs to a connected account */
|
|
19
|
+
accountId: string;
|
|
20
|
+
/** Existing Stripe customer ID — if provided, a new customer will not be created */
|
|
21
|
+
customerId?: string;
|
|
22
|
+
/** New customer details — required when customerId is not provided */
|
|
23
|
+
customer?: StripeCustomer;
|
|
24
|
+
/** URL to redirect to after payment completes. Defaults to window.location.href for embedded checkout. */
|
|
25
|
+
returnUrl?: string;
|
|
37
26
|
}
|
|
38
27
|
|
|
39
28
|
interface FastSpringConfig {
|
|
@@ -55,19 +44,59 @@ declare global {
|
|
|
55
44
|
}
|
|
56
45
|
}
|
|
57
46
|
|
|
58
|
-
interface StripeConfig {
|
|
59
|
-
publishableKey: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
47
|
type ProviderName = 'stripe' | 'fastspring';
|
|
63
48
|
type ProviderConfigMap = {
|
|
64
49
|
stripe: StripeConfig;
|
|
65
50
|
fastspring: FastSpringConfig;
|
|
66
51
|
};
|
|
67
52
|
|
|
53
|
+
interface CheckoutCompleteEvent {
|
|
54
|
+
provider: ProviderName;
|
|
55
|
+
paymentMethodType?: PaymentMethodType;
|
|
56
|
+
orderId?: string;
|
|
57
|
+
customerId?: string;
|
|
58
|
+
subscriptionId?: string;
|
|
59
|
+
invoiceId?: string;
|
|
60
|
+
amountPaid?: number;
|
|
61
|
+
currency?: string;
|
|
62
|
+
}
|
|
63
|
+
interface CheckoutErrorEvent {
|
|
64
|
+
provider: ProviderName;
|
|
65
|
+
paymentMethodType?: PaymentMethodType;
|
|
66
|
+
error: unknown;
|
|
67
|
+
}
|
|
68
|
+
interface CheckoutCancelEvent {
|
|
69
|
+
provider: ProviderName;
|
|
70
|
+
paymentMethodType?: PaymentMethodType;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface CheckoutCallbacks {
|
|
74
|
+
onComplete?: (event: CheckoutCompleteEvent) => void;
|
|
75
|
+
onError?: (event: CheckoutErrorEvent) => void;
|
|
76
|
+
onCancel?: (event: CheckoutCancelEvent) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface RenderOptions {
|
|
80
|
+
paymentMethodTypes?: PaymentMethodType[];
|
|
81
|
+
}
|
|
82
|
+
interface CheckoutInstance {
|
|
83
|
+
render(container: string | HTMLElement, options?: RenderOptions): Promise<void>;
|
|
84
|
+
reset(): Promise<void>;
|
|
85
|
+
destroy(): void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface PaymentProvider {
|
|
89
|
+
init(): Promise<void>;
|
|
90
|
+
render(container: HTMLElement, options?: RenderOptions): Promise<void>;
|
|
91
|
+
reset(): Promise<void>;
|
|
92
|
+
destroy(): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
68
95
|
type InitCheckoutConfig<T extends ProviderName> = {
|
|
69
96
|
provider: T;
|
|
70
97
|
config: ProviderConfigMap[T];
|
|
98
|
+
baseUrl: string;
|
|
99
|
+
headers?: HeadersInit;
|
|
71
100
|
} & CheckoutCallbacks;
|
|
72
101
|
declare function initCheckout<T extends ProviderName>(options: InitCheckoutConfig<T>): Promise<CheckoutInstance>;
|
|
73
102
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,39 +1,28 @@
|
|
|
1
1
|
type PaymentMethodType = 'card' | 'google_pay' | 'apple_pay';
|
|
2
2
|
|
|
3
|
-
interface
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
interface CheckoutCancelEvent {
|
|
12
|
-
paymentMethodType: PaymentMethodType;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface CheckoutCallbacks {
|
|
16
|
-
onComplete?: (event: CheckoutCompleteEvent) => void;
|
|
17
|
-
onError?: (event: CheckoutErrorEvent) => void;
|
|
18
|
-
onCancel?: (event: CheckoutCancelEvent) => void;
|
|
3
|
+
interface StripeCustomer {
|
|
4
|
+
email?: string;
|
|
5
|
+
phone?: string;
|
|
6
|
+
address?: {
|
|
7
|
+
country?: string;
|
|
8
|
+
postal_code?: string;
|
|
9
|
+
};
|
|
19
10
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
reset(): void;
|
|
36
|
-
destroy(): void;
|
|
11
|
+
interface StripeConfig {
|
|
12
|
+
/** Stripe publishable key (pk_test_... or pk_live_...) */
|
|
13
|
+
publishableKey: string;
|
|
14
|
+
/** Stripe Price ID for the product being sold */
|
|
15
|
+
priceId: string;
|
|
16
|
+
/** Whether to use Stripe Managed Payments (embedded checkout, no upfront payment method needed) */
|
|
17
|
+
managed: boolean;
|
|
18
|
+
/** Stripe Connect account ID (acct_...) — required when price belongs to a connected account */
|
|
19
|
+
accountId: string;
|
|
20
|
+
/** Existing Stripe customer ID — if provided, a new customer will not be created */
|
|
21
|
+
customerId?: string;
|
|
22
|
+
/** New customer details — required when customerId is not provided */
|
|
23
|
+
customer?: StripeCustomer;
|
|
24
|
+
/** URL to redirect to after payment completes. Defaults to window.location.href for embedded checkout. */
|
|
25
|
+
returnUrl?: string;
|
|
37
26
|
}
|
|
38
27
|
|
|
39
28
|
interface FastSpringConfig {
|
|
@@ -55,19 +44,59 @@ declare global {
|
|
|
55
44
|
}
|
|
56
45
|
}
|
|
57
46
|
|
|
58
|
-
interface StripeConfig {
|
|
59
|
-
publishableKey: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
47
|
type ProviderName = 'stripe' | 'fastspring';
|
|
63
48
|
type ProviderConfigMap = {
|
|
64
49
|
stripe: StripeConfig;
|
|
65
50
|
fastspring: FastSpringConfig;
|
|
66
51
|
};
|
|
67
52
|
|
|
53
|
+
interface CheckoutCompleteEvent {
|
|
54
|
+
provider: ProviderName;
|
|
55
|
+
paymentMethodType?: PaymentMethodType;
|
|
56
|
+
orderId?: string;
|
|
57
|
+
customerId?: string;
|
|
58
|
+
subscriptionId?: string;
|
|
59
|
+
invoiceId?: string;
|
|
60
|
+
amountPaid?: number;
|
|
61
|
+
currency?: string;
|
|
62
|
+
}
|
|
63
|
+
interface CheckoutErrorEvent {
|
|
64
|
+
provider: ProviderName;
|
|
65
|
+
paymentMethodType?: PaymentMethodType;
|
|
66
|
+
error: unknown;
|
|
67
|
+
}
|
|
68
|
+
interface CheckoutCancelEvent {
|
|
69
|
+
provider: ProviderName;
|
|
70
|
+
paymentMethodType?: PaymentMethodType;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface CheckoutCallbacks {
|
|
74
|
+
onComplete?: (event: CheckoutCompleteEvent) => void;
|
|
75
|
+
onError?: (event: CheckoutErrorEvent) => void;
|
|
76
|
+
onCancel?: (event: CheckoutCancelEvent) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface RenderOptions {
|
|
80
|
+
paymentMethodTypes?: PaymentMethodType[];
|
|
81
|
+
}
|
|
82
|
+
interface CheckoutInstance {
|
|
83
|
+
render(container: string | HTMLElement, options?: RenderOptions): Promise<void>;
|
|
84
|
+
reset(): Promise<void>;
|
|
85
|
+
destroy(): void;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface PaymentProvider {
|
|
89
|
+
init(): Promise<void>;
|
|
90
|
+
render(container: HTMLElement, options?: RenderOptions): Promise<void>;
|
|
91
|
+
reset(): Promise<void>;
|
|
92
|
+
destroy(): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
68
95
|
type InitCheckoutConfig<T extends ProviderName> = {
|
|
69
96
|
provider: T;
|
|
70
97
|
config: ProviderConfigMap[T];
|
|
98
|
+
baseUrl: string;
|
|
99
|
+
headers?: HeadersInit;
|
|
71
100
|
} & CheckoutCallbacks;
|
|
72
101
|
declare function initCheckout<T extends ProviderName>(options: InitCheckoutConfig<T>): Promise<CheckoutInstance>;
|
|
73
102
|
|
package/dist/index.js
CHANGED
|
@@ -6,53 +6,187 @@ var CheckoutError = class extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
// src/providers/
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
// src/providers/stripe/index.ts
|
|
10
|
+
import { loadStripe } from "@stripe/stripe-js";
|
|
11
|
+
|
|
12
|
+
// src/http.ts
|
|
13
|
+
var HttpService = class {
|
|
14
|
+
constructor(baseUrl, headers = {}) {
|
|
15
|
+
this.baseUrl = baseUrl;
|
|
16
|
+
this.headers = headers;
|
|
17
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
18
|
+
}
|
|
19
|
+
baseUrl;
|
|
20
|
+
headers;
|
|
21
|
+
async post(path, body) {
|
|
22
|
+
return this.request("POST", path, body);
|
|
23
|
+
}
|
|
24
|
+
async get(path) {
|
|
25
|
+
return this.request("GET", path);
|
|
26
|
+
}
|
|
27
|
+
async request(method, path, body) {
|
|
28
|
+
const url = `${this.baseUrl}${path}`;
|
|
29
|
+
const headers = new Headers(this.headers);
|
|
30
|
+
headers.set("Content-Type", "application/json");
|
|
31
|
+
let response;
|
|
32
|
+
try {
|
|
33
|
+
response = await fetch(url, {
|
|
34
|
+
method,
|
|
35
|
+
headers,
|
|
36
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
37
|
+
});
|
|
38
|
+
} catch (err) {
|
|
39
|
+
throw new CheckoutError(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
40
|
+
}
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
const text = await response.text().catch(() => response.statusText);
|
|
43
|
+
throw new CheckoutError(`Backend error ${response.status}: ${text}`);
|
|
44
|
+
}
|
|
45
|
+
return response.json();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// src/providers/stripe/service.ts
|
|
50
|
+
var StripeCheckoutService = class extends HttpService {
|
|
51
|
+
constructor(baseUrl, headers) {
|
|
52
|
+
super(baseUrl, headers);
|
|
53
|
+
}
|
|
54
|
+
async getSession(sessionId, account) {
|
|
55
|
+
const query = account ? `?account=${encodeURIComponent(account)}` : "";
|
|
56
|
+
return this.get(`/stripe/session/${sessionId}${query}`);
|
|
57
|
+
}
|
|
58
|
+
async createCheckoutSession(params) {
|
|
59
|
+
const query = `?account=${encodeURIComponent(params.accountId)}`;
|
|
60
|
+
const data = await this.post(`/stripe/purchase${query}`, {
|
|
61
|
+
price_id: params.priceId,
|
|
62
|
+
managed: params.managed,
|
|
63
|
+
customer_id: params.customerId,
|
|
64
|
+
customer: params.customer,
|
|
65
|
+
return_url: params.returnUrl
|
|
66
|
+
});
|
|
67
|
+
if (!data.client_secret) {
|
|
68
|
+
throw new CheckoutError("Backend returned no client_secret");
|
|
69
|
+
}
|
|
70
|
+
return data.client_secret;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/providers/stripe/index.ts
|
|
75
|
+
var StripeProvider = class {
|
|
76
|
+
constructor(config, callbacks, baseUrl, headers) {
|
|
12
77
|
this.config = config;
|
|
13
78
|
this.callbacks = callbacks;
|
|
79
|
+
this.service = new StripeCheckoutService(baseUrl, headers);
|
|
14
80
|
}
|
|
15
81
|
config;
|
|
16
82
|
callbacks;
|
|
83
|
+
stripe = null;
|
|
84
|
+
embeddedCheckout = null;
|
|
85
|
+
service;
|
|
86
|
+
clientSecret = null;
|
|
17
87
|
async init() {
|
|
88
|
+
const stripe = await loadStripe(this.config.publishableKey);
|
|
89
|
+
if (!stripe) {
|
|
90
|
+
throw new CheckoutError("Failed to load Stripe.js \u2014 verify your publishableKey");
|
|
91
|
+
}
|
|
92
|
+
this.stripe = stripe;
|
|
18
93
|
}
|
|
19
|
-
render(container, options) {
|
|
20
|
-
|
|
94
|
+
async render(container, options) {
|
|
95
|
+
if (!this.stripe) {
|
|
96
|
+
throw new CheckoutError("Provider not initialized \u2014 call init() before render()");
|
|
97
|
+
}
|
|
98
|
+
if (this.embeddedCheckout) {
|
|
99
|
+
throw new CheckoutError("Already rendered \u2014 call destroy() before rendering again");
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
this.embeddedCheckout = await this.stripe.createEmbeddedCheckoutPage({
|
|
103
|
+
fetchClientSecret: () => this.fetchClientSecret(),
|
|
104
|
+
onComplete: () => this.handleComplete(),
|
|
105
|
+
onAnalyticsEvent: (event) => console.log("[stripe analytics]", event.eventType, event)
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
this.callbacks.onError?.({ provider: "stripe", error: err });
|
|
109
|
+
if (err instanceof CheckoutError) throw err;
|
|
110
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
111
|
+
throw new CheckoutError(`Stripe checkout failed: ${detail}`);
|
|
112
|
+
}
|
|
113
|
+
this.embeddedCheckout.mount(container);
|
|
21
114
|
}
|
|
22
|
-
reset() {
|
|
23
|
-
|
|
115
|
+
async reset() {
|
|
116
|
+
this.destroy();
|
|
24
117
|
}
|
|
25
118
|
destroy() {
|
|
26
|
-
this.
|
|
119
|
+
this.embeddedCheckout?.destroy();
|
|
120
|
+
this.embeddedCheckout = null;
|
|
121
|
+
}
|
|
122
|
+
async fetchClientSecret() {
|
|
123
|
+
const secret = await this.service.createCheckoutSession({
|
|
124
|
+
...this.config,
|
|
125
|
+
returnUrl: this.config.returnUrl ?? window.location.href
|
|
126
|
+
});
|
|
127
|
+
this.clientSecret = secret;
|
|
128
|
+
return secret;
|
|
129
|
+
}
|
|
130
|
+
// Format: cs_test_{sessionId}_secret_{random}
|
|
131
|
+
parseSessionId(clientSecret) {
|
|
132
|
+
if (!clientSecret) return null;
|
|
133
|
+
const idx = clientSecret.indexOf("_secret_");
|
|
134
|
+
return idx !== -1 ? clientSecret.slice(0, idx) : null;
|
|
135
|
+
}
|
|
136
|
+
async handleComplete() {
|
|
137
|
+
const sessionId = this.parseSessionId(this.clientSecret);
|
|
138
|
+
if (!sessionId) {
|
|
139
|
+
this.callbacks.onComplete?.({ provider: "stripe" });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const session = await this.service.getSession(sessionId, this.config.accountId);
|
|
144
|
+
this.callbacks.onComplete?.({
|
|
145
|
+
provider: "stripe",
|
|
146
|
+
customerId: session.customer_id ?? void 0,
|
|
147
|
+
subscriptionId: session.subscription_id ?? void 0,
|
|
148
|
+
invoiceId: session.invoice_id ?? void 0,
|
|
149
|
+
amountPaid: session.amount_total ?? void 0,
|
|
150
|
+
currency: session.currency ?? void 0
|
|
151
|
+
});
|
|
152
|
+
} catch {
|
|
153
|
+
this.callbacks.onComplete?.({ provider: "stripe" });
|
|
154
|
+
}
|
|
27
155
|
}
|
|
28
156
|
};
|
|
29
157
|
|
|
30
|
-
// src/providers/
|
|
31
|
-
var
|
|
32
|
-
constructor(config, callbacks) {
|
|
158
|
+
// src/providers/fastspring/index.ts
|
|
159
|
+
var FastSpringProvider = class {
|
|
160
|
+
constructor(config, callbacks, baseUrl, headers = {}) {
|
|
33
161
|
this.config = config;
|
|
34
162
|
this.callbacks = callbacks;
|
|
163
|
+
this.baseUrl = baseUrl;
|
|
164
|
+
this.headers = headers;
|
|
35
165
|
}
|
|
36
166
|
config;
|
|
37
167
|
callbacks;
|
|
168
|
+
baseUrl;
|
|
169
|
+
headers;
|
|
38
170
|
async init() {
|
|
39
171
|
}
|
|
40
|
-
render(container, options) {
|
|
41
|
-
container.innerHTML = `<div>Hello World from
|
|
172
|
+
async render(container, options) {
|
|
173
|
+
container.innerHTML = `<div>Hello World from FastSpring [${options?.paymentMethodTypes?.join(", ")}]</div>`;
|
|
42
174
|
}
|
|
43
|
-
reset() {
|
|
175
|
+
async reset() {
|
|
176
|
+
window.fastspring?.builder?.reset();
|
|
44
177
|
}
|
|
45
178
|
destroy() {
|
|
179
|
+
this.reset();
|
|
46
180
|
}
|
|
47
181
|
};
|
|
48
182
|
|
|
49
183
|
// src/providers/registry.ts
|
|
50
|
-
function createProvider(provider, config, callbacks) {
|
|
184
|
+
function createProvider(provider, config, callbacks, baseUrl, headers) {
|
|
51
185
|
switch (provider) {
|
|
52
186
|
case "stripe":
|
|
53
|
-
return new StripeProvider(config, callbacks);
|
|
187
|
+
return new StripeProvider(config, callbacks, baseUrl, headers);
|
|
54
188
|
case "fastspring":
|
|
55
|
-
return new FastSpringProvider(config, callbacks);
|
|
189
|
+
return new FastSpringProvider(config, callbacks, baseUrl, headers);
|
|
56
190
|
default:
|
|
57
191
|
throw new CheckoutError(`Unknown provider: ${provider}`);
|
|
58
192
|
}
|
|
@@ -64,25 +198,35 @@ var CheckoutInstance = class {
|
|
|
64
198
|
this.provider = provider;
|
|
65
199
|
}
|
|
66
200
|
provider;
|
|
67
|
-
|
|
201
|
+
container = null;
|
|
202
|
+
options;
|
|
203
|
+
async render(container, options) {
|
|
68
204
|
const el = typeof container === "string" ? document.querySelector(container) : container;
|
|
69
205
|
if (!el) {
|
|
70
206
|
throw new CheckoutError(`Container not found: ${container}`);
|
|
71
207
|
}
|
|
72
|
-
this.
|
|
208
|
+
this.container = el;
|
|
209
|
+
this.options = options;
|
|
210
|
+
await this.provider.render(el, options);
|
|
73
211
|
}
|
|
74
|
-
reset() {
|
|
75
|
-
this.
|
|
212
|
+
async reset() {
|
|
213
|
+
if (!this.container) {
|
|
214
|
+
throw new CheckoutError("Cannot reset: render() has not been called");
|
|
215
|
+
}
|
|
216
|
+
await this.provider.reset();
|
|
217
|
+
await this.provider.render(this.container, this.options);
|
|
76
218
|
}
|
|
77
219
|
destroy() {
|
|
78
220
|
this.provider.destroy();
|
|
221
|
+
this.container = null;
|
|
222
|
+
this.options = void 0;
|
|
79
223
|
}
|
|
80
224
|
};
|
|
81
225
|
|
|
82
226
|
// src/checkout.ts
|
|
83
227
|
async function initCheckout(options) {
|
|
84
|
-
const { provider: providerName, config, ...callbacks } = options;
|
|
85
|
-
const provider = createProvider(providerName, config, callbacks);
|
|
228
|
+
const { provider: providerName, config, baseUrl, headers = {}, ...callbacks } = options;
|
|
229
|
+
const provider = createProvider(providerName, config, callbacks, baseUrl, headers);
|
|
86
230
|
await provider.init();
|
|
87
231
|
return new CheckoutInstance(provider);
|
|
88
232
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funnelfox/payments-sdk",
|
|
3
|
-
"version": "0.1.2-beta.
|
|
3
|
+
"version": "0.1.2-beta.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"build": "tsup",
|
|
21
21
|
"dev": "tsup --watch"
|
|
22
22
|
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@stripe/stripe-js": "^9.1.0"
|
|
25
|
+
},
|
|
23
26
|
"devDependencies": {
|
|
24
27
|
"tsup": "^8.4.0",
|
|
25
28
|
"typescript": "^5.8.0"
|