@flopay/node 1.4.3 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,269 +1 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- FloPay: () => FloPay,
34
- STRIPE_API_VERSION: () => STRIPE_API_VERSION
35
- });
36
- module.exports = __toCommonJS(index_exports);
37
-
38
- // src/flopay-node.ts
39
- var import_stripe = __toESM(require("stripe"), 1);
40
- var import_shared = require("@flopay/shared");
41
- var STRIPE_API_VERSION = "2026-04-22.dahlia";
42
- var FloPay = class {
43
- constructor(secretKey, options) {
44
- if (!secretKey) {
45
- throw new import_shared.FloPayError(
46
- "A secret key is required to initialize the FloPay server SDK.",
47
- "authentication_error"
48
- );
49
- }
50
- this.stripe = new import_stripe.default(options?.stripeSecretKey ?? secretKey, {
51
- apiVersion: options?.apiVersion ?? STRIPE_API_VERSION,
52
- appInfo: {
53
- name: "flopay-node",
54
- version: import_shared.SDK_VERSION
55
- }
56
- });
57
- this.checkout = {
58
- sessions: {
59
- create: this.createSession.bind(this),
60
- retrieve: this.retrieveSession.bind(this),
61
- expire: this.expireSession.bind(this),
62
- listLineItems: this.listLineItems.bind(this)
63
- }
64
- };
65
- this.webhooks = {
66
- constructEvent: this.constructWebhookEvent.bind(this)
67
- };
68
- this.customers = {
69
- create: this.createCustomer.bind(this),
70
- retrieve: this.retrieveCustomer.bind(this),
71
- update: this.updateCustomer.bind(this)
72
- };
73
- }
74
- /**
75
- * Create a checkout session via the billing API.
76
- *
77
- * Mirrors `createCheckoutSession` from `clicktech-core-ui/modules`.
78
- * Posts to `{billingApiUrl}/v1/checkouts/sessions` and returns
79
- * the session UUID for redirect.
80
- */
81
- async createSession(params) {
82
- const {
83
- billingApiUrl,
84
- checkoutBaseUrl,
85
- items = [],
86
- subscriptions = [],
87
- products,
88
- account,
89
- successUrl,
90
- cancelUrl,
91
- checkoutMode = "confirm",
92
- couponCodes = [],
93
- tagsData,
94
- redirectParams = {},
95
- timeoutMs = 12e3,
96
- clientId,
97
- currency,
98
- utmMetadata
99
- } = params;
100
- const wireProducts = products ?? (0, import_shared.foldIntoProducts)(items, subscriptions);
101
- const sessionCurrency = (0, import_shared.resolveSessionCurrency)(currency, items, subscriptions, wireProducts);
102
- if (!sessionCurrency) {
103
- throw new import_shared.FloPayError(
104
- "currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.",
105
- "validation_error",
106
- { code: "CurrencyRequired", param: "currency" }
107
- );
108
- }
109
- const payload = {
110
- clientId,
111
- checkoutVersion: import_shared.SDK_VERSION,
112
- successUrl,
113
- cancelUrl,
114
- currency: sessionCurrency,
115
- checkoutMode,
116
- products: wireProducts.map((product) => (0, import_shared.buildProductPayload)(product, sessionCurrency)),
117
- accountData: {
118
- userId: account.userId,
119
- firstName: account.firstName ?? null,
120
- lastName: account.lastName ?? null,
121
- email: account.email,
122
- country: account.country ?? null,
123
- gender: account.gender ?? null,
124
- city: account.city ?? null,
125
- state: account.state ?? null,
126
- zip: account.zip ?? null
127
- },
128
- couponCodes
129
- };
130
- if (tagsData) {
131
- payload["tagsData"] = tagsData;
132
- }
133
- if (utmMetadata?.length) {
134
- payload["utmMetadata"] = utmMetadata;
135
- }
136
- const url = `${billingApiUrl.replace(/\/+$/, "")}/v1/checkouts/sessions`;
137
- const controller = new AbortController();
138
- const timer = setTimeout(() => controller.abort(), timeoutMs);
139
- let status;
140
- let body;
141
- try {
142
- const response = await fetch(url, {
143
- method: "POST",
144
- headers: { "Content-Type": "application/json" },
145
- body: JSON.stringify(payload),
146
- signal: controller.signal
147
- });
148
- status = response.status;
149
- try {
150
- body = await response.json();
151
- } catch {
152
- }
153
- } finally {
154
- clearTimeout(timer);
155
- }
156
- if (status === 201) {
157
- const uuid = body?.data?.uuid;
158
- const nonce = body?.data?.nonce;
159
- if (!uuid) {
160
- throw new Error("Checkout session created but no UUID was returned by the billing API");
161
- }
162
- if (!nonce) {
163
- throw new import_shared.FloPayError(
164
- "Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.",
165
- "api_error",
166
- { code: "MissingCheckoutSessionToken" }
167
- );
168
- }
169
- const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
170
- redirectUrl.searchParams.set("id", uuid);
171
- for (const [key, value] of Object.entries(redirectParams)) {
172
- redirectUrl.searchParams.set(key, value);
173
- }
174
- return { status: 201, redirectUrl: redirectUrl.toString(), nonce };
175
- }
176
- if (status === 204) {
177
- return { status: 204 };
178
- }
179
- return { status };
180
- }
181
- normalizeStripeSession(session) {
182
- const statusMap = {
183
- open: "open",
184
- complete: "complete",
185
- expired: "expired"
186
- };
187
- return {
188
- id: session.id,
189
- clientSecret: session.client_secret ?? "",
190
- mode: session.mode,
191
- status: statusMap[session.status ?? "open"] ?? "open",
192
- amount: session.amount_total ?? 0,
193
- currency: session.currency ?? "usd",
194
- metadata: session.metadata ?? {}
195
- };
196
- }
197
- async retrieveSession(id) {
198
- const session = await this.stripe.checkout.sessions.retrieve(id);
199
- return this.normalizeStripeSession(session);
200
- }
201
- async expireSession(id) {
202
- const session = await this.stripe.checkout.sessions.expire(id);
203
- return this.normalizeStripeSession(session);
204
- }
205
- async listLineItems(id) {
206
- const items = await this.stripe.checkout.sessions.listLineItems(id);
207
- return items.data.map((item) => ({
208
- price: item.price?.id,
209
- quantity: item.quantity ?? 1
210
- }));
211
- }
212
- constructWebhookEvent(payload, signature, secret) {
213
- const event = this.stripe.webhooks.constructEvent(payload, signature, secret);
214
- return {
215
- id: event.id,
216
- type: event.type,
217
- data: event.data,
218
- created: event.created
219
- };
220
- }
221
- async createCustomer(params) {
222
- const customer = await this.stripe.customers.create({
223
- email: params.email,
224
- name: params.name,
225
- metadata: params.metadata
226
- });
227
- return {
228
- id: customer.id,
229
- email: customer.email ?? params.email,
230
- firstName: params.name?.split(" ")[0],
231
- lastName: params.name?.split(" ").slice(1).join(" ")
232
- };
233
- }
234
- async retrieveCustomer(id) {
235
- const customer = await this.stripe.customers.retrieve(id);
236
- if (customer.deleted) {
237
- throw new import_shared.FloPayError(
238
- `Customer ${id} has been deleted`,
239
- "api_error",
240
- { code: "resource_missing" }
241
- );
242
- }
243
- return {
244
- id: customer.id,
245
- email: customer.email ?? "",
246
- firstName: customer.name?.split(" ")[0],
247
- lastName: customer.name?.split(" ").slice(1).join(" ")
248
- };
249
- }
250
- async updateCustomer(id, params) {
251
- const customer = await this.stripe.customers.update(id, {
252
- email: params.email,
253
- name: params.name,
254
- metadata: params.metadata
255
- });
256
- return {
257
- id: customer.id,
258
- email: customer.email ?? "",
259
- firstName: customer.name?.split(" ")[0],
260
- lastName: customer.name?.split(" ").slice(1).join(" ")
261
- };
262
- }
263
- };
264
- // Annotate the CommonJS export names for ESM import in node:
265
- 0 && (module.exports = {
266
- FloPay,
267
- STRIPE_API_VERSION
268
- });
269
- //# sourceMappingURL=index.cjs.map
1
+ "use strict";var A=Object.create;var u=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var F=Object.getPrototypeOf,M=Object.prototype.hasOwnProperty;var q=(s,e)=>{for(var t in e)u(s,t,{get:e[t],enumerable:!0})},b=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of D(e))!M.call(s,o)&&o!==t&&u(s,o,{get:()=>e[o],enumerable:!(r=z(e,o))||r.enumerable});return s};var W=(s,e,t)=>(t=s!=null?A(F(s)):{},b(e||!s||!s.__esModule?u(t,"default",{value:s,enumerable:!0}):t,s)),$=s=>b(u({},"__esModule",{value:!0}),s);var B={};q(B,{FloPay:()=>m,STRIPE_API_VERSION:()=>y});module.exports=$(B);var v=W(require("stripe"),1),i=require("@flopay/shared"),y="2026-04-22.dahlia",m=class{constructor(e,t){if(!e)throw new i.FloPayError("A secret key is required to initialize the FloPay server SDK.","authentication_error");this.stripe=new v.default(t?.stripeSecretKey??e,{apiVersion:t?.apiVersion??y,appInfo:{name:"flopay-node",version:i.SDK_VERSION}}),this.checkout={sessions:{create:this.createSession.bind(this),retrieve:this.retrieveSession.bind(this),expire:this.expireSession.bind(this),listLineItems:this.listLineItems.bind(this)}},this.webhooks={constructEvent:this.constructWebhookEvent.bind(this)},this.customers={create:this.createCustomer.bind(this),retrieve:this.retrieveCustomer.bind(this),update:this.updateCustomer.bind(this)}}async createSession(e){let{billingApiUrl:t,checkoutBaseUrl:r,items:o=[],subscriptions:C=[],products:w,account:n,successUrl:I,cancelUrl:N,checkoutMode:E="confirm",couponCodes:R=[],tagsData:S,redirectParams:U={},timeoutMs:x=12e3,clientId:_,currency:L,utmMetadata:g}=e,P=w??(0,i.foldIntoProducts)(o,C),l=(0,i.resolveSessionCurrency)(L,o,C,P);if(!l)throw new i.FloPayError("currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.","validation_error",{code:"CurrencyRequired",param:"currency"});let d={clientId:_,checkoutVersion:i.SDK_VERSION,successUrl:I,cancelUrl:N,currency:l,checkoutMode:E,products:P.map(a=>(0,i.buildProductPayload)(a,l)),accountData:{userId:n.userId,firstName:n.firstName??null,lastName:n.lastName??null,email:n.email,country:n.country??null,gender:n.gender??null,city:n.city??null,state:n.state??null,zip:n.zip??null},couponCodes:R};S&&(d.tagsData=S),g?.length&&(d.utmMetadata=g);let O=`${t.replace(/\/+$/,"")}/v1/checkouts/sessions`,f=new AbortController,T=setTimeout(()=>f.abort(),x),c,p;try{let a=await fetch(O,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d),signal:f.signal});c=a.status;try{p=await a.json()}catch{}}finally{clearTimeout(T)}if(c===201){let a=p?.data?.uuid,k=p?.data?.nonce;if(!a)throw new Error("Checkout session created but no UUID was returned by the billing API");if(!k)throw new i.FloPayError("Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.","api_error",{code:"MissingCheckoutSessionToken"});let h=new URL(`${r.replace(/\/+$/,"")}/secure`);h.searchParams.set("id",a);for(let[V,j]of Object.entries(U))h.searchParams.set(V,j);return{status:201,redirectUrl:h.toString(),nonce:k}}return c===204?{status:204}:{status:c}}normalizeStripeSession(e){let t={open:"open",complete:"complete",expired:"expired"};return{id:e.id,clientSecret:e.client_secret??"",mode:e.mode,status:t[e.status??"open"]??"open",amount:e.amount_total??0,currency:e.currency??"usd",metadata:e.metadata??{}}}async retrieveSession(e){let t=await this.stripe.checkout.sessions.retrieve(e);return this.normalizeStripeSession(t)}async expireSession(e){let t=await this.stripe.checkout.sessions.expire(e);return this.normalizeStripeSession(t)}async listLineItems(e){return(await this.stripe.checkout.sessions.listLineItems(e)).data.map(r=>({price:r.price?.id,quantity:r.quantity??1}))}constructWebhookEvent(e,t,r){let o=this.stripe.webhooks.constructEvent(e,t,r);return{id:o.id,type:o.type,data:o.data,created:o.created}}async createCustomer(e){let t=await this.stripe.customers.create({email:e.email,name:e.name,metadata:e.metadata});return{id:t.id,email:t.email??e.email,firstName:e.name?.split(" ")[0],lastName:e.name?.split(" ").slice(1).join(" ")}}async retrieveCustomer(e){let t=await this.stripe.customers.retrieve(e);if(t.deleted)throw new i.FloPayError(`Customer ${e} has been deleted`,"api_error",{code:"resource_missing"});return{id:t.id,email:t.email??"",firstName:t.name?.split(" ")[0],lastName:t.name?.split(" ").slice(1).join(" ")}}async updateCustomer(e,t){let r=await this.stripe.customers.update(e,{email:t.email,name:t.name,metadata:t.metadata});return{id:r.id,email:r.email??"",firstName:r.name?.split(" ")[0],lastName:r.name?.split(" ").slice(1).join(" ")}}};0&&(module.exports={FloPay,STRIPE_API_VERSION});
package/dist/index.mjs CHANGED
@@ -1,237 +1 @@
1
- // src/flopay-node.ts
2
- import Stripe from "stripe";
3
- import {
4
- FloPayError,
5
- SDK_VERSION,
6
- buildProductPayload,
7
- foldIntoProducts,
8
- resolveSessionCurrency
9
- } from "@flopay/shared";
10
- var STRIPE_API_VERSION = "2026-04-22.dahlia";
11
- var FloPay = class {
12
- constructor(secretKey, options) {
13
- if (!secretKey) {
14
- throw new FloPayError(
15
- "A secret key is required to initialize the FloPay server SDK.",
16
- "authentication_error"
17
- );
18
- }
19
- this.stripe = new Stripe(options?.stripeSecretKey ?? secretKey, {
20
- apiVersion: options?.apiVersion ?? STRIPE_API_VERSION,
21
- appInfo: {
22
- name: "flopay-node",
23
- version: SDK_VERSION
24
- }
25
- });
26
- this.checkout = {
27
- sessions: {
28
- create: this.createSession.bind(this),
29
- retrieve: this.retrieveSession.bind(this),
30
- expire: this.expireSession.bind(this),
31
- listLineItems: this.listLineItems.bind(this)
32
- }
33
- };
34
- this.webhooks = {
35
- constructEvent: this.constructWebhookEvent.bind(this)
36
- };
37
- this.customers = {
38
- create: this.createCustomer.bind(this),
39
- retrieve: this.retrieveCustomer.bind(this),
40
- update: this.updateCustomer.bind(this)
41
- };
42
- }
43
- /**
44
- * Create a checkout session via the billing API.
45
- *
46
- * Mirrors `createCheckoutSession` from `clicktech-core-ui/modules`.
47
- * Posts to `{billingApiUrl}/v1/checkouts/sessions` and returns
48
- * the session UUID for redirect.
49
- */
50
- async createSession(params) {
51
- const {
52
- billingApiUrl,
53
- checkoutBaseUrl,
54
- items = [],
55
- subscriptions = [],
56
- products,
57
- account,
58
- successUrl,
59
- cancelUrl,
60
- checkoutMode = "confirm",
61
- couponCodes = [],
62
- tagsData,
63
- redirectParams = {},
64
- timeoutMs = 12e3,
65
- clientId,
66
- currency,
67
- utmMetadata
68
- } = params;
69
- const wireProducts = products ?? foldIntoProducts(items, subscriptions);
70
- const sessionCurrency = resolveSessionCurrency(currency, items, subscriptions, wireProducts);
71
- if (!sessionCurrency) {
72
- throw new FloPayError(
73
- "currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.",
74
- "validation_error",
75
- { code: "CurrencyRequired", param: "currency" }
76
- );
77
- }
78
- const payload = {
79
- clientId,
80
- checkoutVersion: SDK_VERSION,
81
- successUrl,
82
- cancelUrl,
83
- currency: sessionCurrency,
84
- checkoutMode,
85
- products: wireProducts.map((product) => buildProductPayload(product, sessionCurrency)),
86
- accountData: {
87
- userId: account.userId,
88
- firstName: account.firstName ?? null,
89
- lastName: account.lastName ?? null,
90
- email: account.email,
91
- country: account.country ?? null,
92
- gender: account.gender ?? null,
93
- city: account.city ?? null,
94
- state: account.state ?? null,
95
- zip: account.zip ?? null
96
- },
97
- couponCodes
98
- };
99
- if (tagsData) {
100
- payload["tagsData"] = tagsData;
101
- }
102
- if (utmMetadata?.length) {
103
- payload["utmMetadata"] = utmMetadata;
104
- }
105
- const url = `${billingApiUrl.replace(/\/+$/, "")}/v1/checkouts/sessions`;
106
- const controller = new AbortController();
107
- const timer = setTimeout(() => controller.abort(), timeoutMs);
108
- let status;
109
- let body;
110
- try {
111
- const response = await fetch(url, {
112
- method: "POST",
113
- headers: { "Content-Type": "application/json" },
114
- body: JSON.stringify(payload),
115
- signal: controller.signal
116
- });
117
- status = response.status;
118
- try {
119
- body = await response.json();
120
- } catch {
121
- }
122
- } finally {
123
- clearTimeout(timer);
124
- }
125
- if (status === 201) {
126
- const uuid = body?.data?.uuid;
127
- const nonce = body?.data?.nonce;
128
- if (!uuid) {
129
- throw new Error("Checkout session created but no UUID was returned by the billing API");
130
- }
131
- if (!nonce) {
132
- throw new FloPayError(
133
- "Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.",
134
- "api_error",
135
- { code: "MissingCheckoutSessionToken" }
136
- );
137
- }
138
- const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
139
- redirectUrl.searchParams.set("id", uuid);
140
- for (const [key, value] of Object.entries(redirectParams)) {
141
- redirectUrl.searchParams.set(key, value);
142
- }
143
- return { status: 201, redirectUrl: redirectUrl.toString(), nonce };
144
- }
145
- if (status === 204) {
146
- return { status: 204 };
147
- }
148
- return { status };
149
- }
150
- normalizeStripeSession(session) {
151
- const statusMap = {
152
- open: "open",
153
- complete: "complete",
154
- expired: "expired"
155
- };
156
- return {
157
- id: session.id,
158
- clientSecret: session.client_secret ?? "",
159
- mode: session.mode,
160
- status: statusMap[session.status ?? "open"] ?? "open",
161
- amount: session.amount_total ?? 0,
162
- currency: session.currency ?? "usd",
163
- metadata: session.metadata ?? {}
164
- };
165
- }
166
- async retrieveSession(id) {
167
- const session = await this.stripe.checkout.sessions.retrieve(id);
168
- return this.normalizeStripeSession(session);
169
- }
170
- async expireSession(id) {
171
- const session = await this.stripe.checkout.sessions.expire(id);
172
- return this.normalizeStripeSession(session);
173
- }
174
- async listLineItems(id) {
175
- const items = await this.stripe.checkout.sessions.listLineItems(id);
176
- return items.data.map((item) => ({
177
- price: item.price?.id,
178
- quantity: item.quantity ?? 1
179
- }));
180
- }
181
- constructWebhookEvent(payload, signature, secret) {
182
- const event = this.stripe.webhooks.constructEvent(payload, signature, secret);
183
- return {
184
- id: event.id,
185
- type: event.type,
186
- data: event.data,
187
- created: event.created
188
- };
189
- }
190
- async createCustomer(params) {
191
- const customer = await this.stripe.customers.create({
192
- email: params.email,
193
- name: params.name,
194
- metadata: params.metadata
195
- });
196
- return {
197
- id: customer.id,
198
- email: customer.email ?? params.email,
199
- firstName: params.name?.split(" ")[0],
200
- lastName: params.name?.split(" ").slice(1).join(" ")
201
- };
202
- }
203
- async retrieveCustomer(id) {
204
- const customer = await this.stripe.customers.retrieve(id);
205
- if (customer.deleted) {
206
- throw new FloPayError(
207
- `Customer ${id} has been deleted`,
208
- "api_error",
209
- { code: "resource_missing" }
210
- );
211
- }
212
- return {
213
- id: customer.id,
214
- email: customer.email ?? "",
215
- firstName: customer.name?.split(" ")[0],
216
- lastName: customer.name?.split(" ").slice(1).join(" ")
217
- };
218
- }
219
- async updateCustomer(id, params) {
220
- const customer = await this.stripe.customers.update(id, {
221
- email: params.email,
222
- name: params.name,
223
- metadata: params.metadata
224
- });
225
- return {
226
- id: customer.id,
227
- email: customer.email ?? "",
228
- firstName: customer.name?.split(" ")[0],
229
- lastName: customer.name?.split(" ").slice(1).join(" ")
230
- };
231
- }
232
- };
233
- export {
234
- FloPay,
235
- STRIPE_API_VERSION
236
- };
237
- //# sourceMappingURL=index.mjs.map
1
+ import T from"stripe";import{FloPayError as a,SDK_VERSION as P,buildProductPayload as V,foldIntoProducts as j,resolveSessionCurrency as A}from"@flopay/shared";var f="2026-04-22.dahlia",d=class{constructor(e,t){if(!e)throw new a("A secret key is required to initialize the FloPay server SDK.","authentication_error");this.stripe=new T(t?.stripeSecretKey??e,{apiVersion:t?.apiVersion??f,appInfo:{name:"flopay-node",version:P}}),this.checkout={sessions:{create:this.createSession.bind(this),retrieve:this.retrieveSession.bind(this),expire:this.expireSession.bind(this),listLineItems:this.listLineItems.bind(this)}},this.webhooks={constructEvent:this.constructWebhookEvent.bind(this)},this.customers={create:this.createCustomer.bind(this),retrieve:this.retrieveCustomer.bind(this),update:this.updateCustomer.bind(this)}}async createSession(e){let{billingApiUrl:t,checkoutBaseUrl:s,items:i=[],subscriptions:p=[],products:k,account:r,successUrl:b,cancelUrl:v,checkoutMode:w="confirm",couponCodes:I=[],tagsData:h,redirectParams:N={},timeoutMs:E=12e3,clientId:R,currency:U,utmMetadata:y}=e,C=k??j(i,p),c=A(U,i,p,C);if(!c)throw new a("currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.","validation_error",{code:"CurrencyRequired",param:"currency"});let u={clientId:R,checkoutVersion:P,successUrl:b,cancelUrl:v,currency:c,checkoutMode:w,products:C.map(o=>V(o,c)),accountData:{userId:r.userId,firstName:r.firstName??null,lastName:r.lastName??null,email:r.email,country:r.country??null,gender:r.gender??null,city:r.city??null,state:r.state??null,zip:r.zip??null},couponCodes:I};h&&(u.tagsData=h),y?.length&&(u.utmMetadata=y);let x=`${t.replace(/\/+$/,"")}/v1/checkouts/sessions`,S=new AbortController,_=setTimeout(()=>S.abort(),E),n,m;try{let o=await fetch(x,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(u),signal:S.signal});n=o.status;try{m=await o.json()}catch{}}finally{clearTimeout(_)}if(n===201){let o=m?.data?.uuid,g=m?.data?.nonce;if(!o)throw new Error("Checkout session created but no UUID was returned by the billing API");if(!g)throw new a("Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.","api_error",{code:"MissingCheckoutSessionToken"});let l=new URL(`${s.replace(/\/+$/,"")}/secure`);l.searchParams.set("id",o);for(let[L,O]of Object.entries(N))l.searchParams.set(L,O);return{status:201,redirectUrl:l.toString(),nonce:g}}return n===204?{status:204}:{status:n}}normalizeStripeSession(e){let t={open:"open",complete:"complete",expired:"expired"};return{id:e.id,clientSecret:e.client_secret??"",mode:e.mode,status:t[e.status??"open"]??"open",amount:e.amount_total??0,currency:e.currency??"usd",metadata:e.metadata??{}}}async retrieveSession(e){let t=await this.stripe.checkout.sessions.retrieve(e);return this.normalizeStripeSession(t)}async expireSession(e){let t=await this.stripe.checkout.sessions.expire(e);return this.normalizeStripeSession(t)}async listLineItems(e){return(await this.stripe.checkout.sessions.listLineItems(e)).data.map(s=>({price:s.price?.id,quantity:s.quantity??1}))}constructWebhookEvent(e,t,s){let i=this.stripe.webhooks.constructEvent(e,t,s);return{id:i.id,type:i.type,data:i.data,created:i.created}}async createCustomer(e){let t=await this.stripe.customers.create({email:e.email,name:e.name,metadata:e.metadata});return{id:t.id,email:t.email??e.email,firstName:e.name?.split(" ")[0],lastName:e.name?.split(" ").slice(1).join(" ")}}async retrieveCustomer(e){let t=await this.stripe.customers.retrieve(e);if(t.deleted)throw new a(`Customer ${e} has been deleted`,"api_error",{code:"resource_missing"});return{id:t.id,email:t.email??"",firstName:t.name?.split(" ")[0],lastName:t.name?.split(" ").slice(1).join(" ")}}async updateCustomer(e,t){let s=await this.stripe.customers.update(e,{email:t.email,name:t.name,metadata:t.metadata});return{id:s.id,email:s.email??"",firstName:s.name?.split(" ")[0],lastName:s.name?.split(" ").slice(1).join(" ")}}};export{d as FloPay,f as STRIPE_API_VERSION};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flopay/node",
3
- "version": "1.4.3",
3
+ "version": "1.4.4",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "publishConfig": {
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "stripe": "^22.2.1",
30
- "@flopay/shared": "1.4.3"
30
+ "@flopay/shared": "1.4.4"
31
31
  },
32
32
  "devDependencies": {
33
33
  "tsup": "^8.3.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts","../src/flopay-node.ts"],"sourcesContent":["export { FloPay, STRIPE_API_VERSION } from './flopay-node.js';\nexport type { FloPayNodeOptions } from './flopay-node.js';\n","import Stripe from 'stripe';\nimport type {\n CheckoutSession,\n LineItem,\n CreateSessionParams,\n CheckoutSessionResult,\n CreateCustomerParams,\n UpdateCustomerParams,\n WebhookEvent,\n Customer,\n} from '@flopay/shared';\nimport {\n FloPayError,\n SDK_VERSION,\n buildProductPayload,\n foldIntoProducts,\n resolveSessionCurrency,\n} from '@flopay/shared';\n\nexport const STRIPE_API_VERSION = '2026-04-22.dahlia';\n\ntype StripeConfig = NonNullable<ConstructorParameters<typeof Stripe>[1]>;\n\n/** Options for the FloPay Node SDK constructor. */\nexport interface FloPayNodeOptions {\n apiVersion?: string;\n /** Stripe secret key, required when using Stripe-specific operations. */\n stripeSecretKey?: string;\n}\n\n/**\n * Server-side FloPay SDK.\n *\n * Supports two modes of operation:\n *\n * 1. **Billing API mode** — Creates checkout sessions via the FloPay/ClickTech\n * billing API (`POST /v1/checkouts/sessions`). This mirrors the\n * `createCheckoutSession` function from `clicktech-core-ui`.\n *\n * 2. **Stripe direct mode** — When a `stripeSecretKey` is provided, also\n * exposes direct Stripe operations (customers, webhooks).\n *\n * @example\n * ```ts\n * import { FloPay } from '@flopay/node';\n *\n * const flopay = new FloPay('sk_test_...');\n *\n * // Create session via billing API\n * const result = await flopay.checkout.sessions.create({\n * billingApiUrl: 'https://billing.example.com',\n * checkoutBaseUrl: 'https://checkout.example.com',\n * clientId: 'client_123',\n * currency: 'USD',\n * items: [{ code: 'prod_abc' }],\n * account: { userId: 'user_1', email: 'user@example.com' },\n * successUrl: '/success',\n * cancelUrl: '/cancel',\n * });\n * ```\n */\nexport class FloPay {\n private readonly stripe: Stripe;\n\n readonly checkout: {\n sessions: {\n /** Create a checkout session via the billing API. */\n create: (params: CreateSessionParams) => Promise<CheckoutSessionResult>;\n /** Retrieve a checkout session from Stripe. */\n retrieve: (id: string) => Promise<CheckoutSession>;\n /** Expire a checkout session on Stripe. */\n expire: (id: string) => Promise<CheckoutSession>;\n /** List line items for a Stripe checkout session. */\n listLineItems: (id: string) => Promise<LineItem[]>;\n };\n };\n\n readonly webhooks: {\n constructEvent: (\n payload: string | Buffer,\n signature: string,\n secret: string,\n ) => WebhookEvent;\n };\n\n readonly customers: {\n create: (params: CreateCustomerParams) => Promise<Customer>;\n retrieve: (id: string) => Promise<Customer>;\n update: (id: string, params: UpdateCustomerParams) => Promise<Customer>;\n };\n\n constructor(secretKey: string, options?: FloPayNodeOptions) {\n if (!secretKey) {\n throw new FloPayError(\n 'A secret key is required to initialize the FloPay server SDK.',\n 'authentication_error',\n );\n }\n\n this.stripe = new Stripe(options?.stripeSecretKey ?? secretKey, {\n apiVersion: (options?.apiVersion ?? STRIPE_API_VERSION) as StripeConfig['apiVersion'],\n appInfo: {\n name: 'flopay-node',\n version: SDK_VERSION,\n },\n });\n\n this.checkout = {\n sessions: {\n create: this.createSession.bind(this),\n retrieve: this.retrieveSession.bind(this),\n expire: this.expireSession.bind(this),\n listLineItems: this.listLineItems.bind(this),\n },\n };\n\n this.webhooks = {\n constructEvent: this.constructWebhookEvent.bind(this),\n };\n\n this.customers = {\n create: this.createCustomer.bind(this),\n retrieve: this.retrieveCustomer.bind(this),\n update: this.updateCustomer.bind(this),\n };\n }\n\n /**\n * Create a checkout session via the billing API.\n *\n * Mirrors `createCheckoutSession` from `clicktech-core-ui/modules`.\n * Posts to `{billingApiUrl}/v1/checkouts/sessions` and returns\n * the session UUID for redirect.\n */\n private async createSession(\n params: CreateSessionParams,\n ): Promise<CheckoutSessionResult> {\n const {\n billingApiUrl,\n checkoutBaseUrl,\n items = [],\n subscriptions = [],\n products,\n account,\n successUrl,\n cancelUrl,\n checkoutMode = 'confirm',\n couponCodes = [],\n tagsData,\n redirectParams = {},\n timeoutMs = 12000,\n clientId,\n currency,\n utmMetadata,\n } = params;\n\n const wireProducts = products ?? foldIntoProducts(items, subscriptions);\n const sessionCurrency = resolveSessionCurrency(currency, items, subscriptions, wireProducts);\n if (!sessionCurrency) {\n throw new FloPayError(\n 'currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.',\n 'validation_error',\n { code: 'CurrencyRequired', param: 'currency' },\n );\n }\n\n const payload: Record<string, unknown> = {\n clientId,\n checkoutVersion: SDK_VERSION,\n successUrl,\n cancelUrl,\n currency: sessionCurrency,\n checkoutMode,\n products: wireProducts.map((product) => buildProductPayload(product, sessionCurrency)),\n accountData: {\n userId: account.userId,\n firstName: account.firstName ?? null,\n lastName: account.lastName ?? null,\n email: account.email,\n country: account.country ?? null,\n gender: account.gender ?? null,\n city: account.city ?? null,\n state: account.state ?? null,\n zip: account.zip ?? null,\n },\n couponCodes,\n };\n\n if (tagsData) {\n payload['tagsData'] = tagsData;\n }\n\n if (utmMetadata?.length) {\n payload['utmMetadata'] = utmMetadata;\n }\n\n const url = `${billingApiUrl.replace(/\\/+$/, '')}/v1/checkouts/sessions`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let status: number;\n let body: { data?: { uuid?: string; nonce?: string } } | undefined;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n status = response.status;\n\n try {\n body = await response.json() as { data?: { uuid?: string; nonce?: string } };\n } catch {\n // 204 or empty body\n }\n } finally {\n clearTimeout(timer);\n }\n\n if (status === 201) {\n const uuid = body?.data?.uuid;\n const nonce = body?.data?.nonce;\n\n if (!uuid) {\n throw new Error('Checkout session created but no UUID was returned by the billing API');\n }\n\n if (!nonce) {\n throw new FloPayError(\n 'Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.',\n 'api_error',\n { code: 'MissingCheckoutSessionToken' },\n );\n }\n\n const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\\/+$/, '')}/secure`);\n redirectUrl.searchParams.set('id', uuid);\n\n for (const [key, value] of Object.entries(redirectParams)) {\n redirectUrl.searchParams.set(key, value);\n }\n\n return { status: 201, redirectUrl: redirectUrl.toString(), nonce };\n }\n\n if (status === 204) {\n return { status: 204 };\n }\n\n return { status };\n }\n\n private normalizeStripeSession(\n session: Stripe.Checkout.Session,\n ): CheckoutSession {\n const statusMap: Record<string, CheckoutSession['status']> = {\n open: 'open',\n complete: 'complete',\n expired: 'expired',\n };\n\n return {\n id: session.id,\n clientSecret: session.client_secret ?? '',\n mode: session.mode as CheckoutSession['mode'],\n status: statusMap[session.status ?? 'open'] ?? 'open',\n amount: session.amount_total ?? 0,\n currency: session.currency ?? 'usd',\n metadata: (session.metadata as Record<string, string>) ?? {},\n };\n }\n\n private async retrieveSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.retrieve(id);\n return this.normalizeStripeSession(session);\n }\n\n private async expireSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.expire(id);\n return this.normalizeStripeSession(session);\n }\n\n private async listLineItems(id: string): Promise<LineItem[]> {\n const items = await this.stripe.checkout.sessions.listLineItems(id);\n return items.data.map((item) => ({\n price: item.price?.id,\n quantity: item.quantity ?? 1,\n }));\n }\n\n private constructWebhookEvent(\n payload: string | Buffer,\n signature: string,\n secret: string,\n ): WebhookEvent {\n const event = this.stripe.webhooks.constructEvent(payload, signature, secret);\n\n return {\n id: event.id,\n type: event.type,\n data: event.data as unknown as Record<string, unknown>,\n created: event.created,\n };\n }\n\n private async createCustomer(params: CreateCustomerParams): Promise<Customer> {\n const customer = await this.stripe.customers.create({\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? params.email,\n firstName: params.name?.split(' ')[0],\n lastName: params.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async retrieveCustomer(id: string): Promise<Customer> {\n const customer = await this.stripe.customers.retrieve(id);\n\n if (customer.deleted) {\n throw new FloPayError(\n `Customer ${id} has been deleted`,\n 'api_error',\n { code: 'resource_missing' },\n );\n }\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async updateCustomer(\n id: string,\n params: UpdateCustomerParams,\n ): Promise<Customer> {\n const customer = await this.stripe.customers.update(id, {\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n}\n"],"mappings":"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","names":["Stripe"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/flopay-node.ts"],"sourcesContent":["import Stripe from 'stripe';\nimport type {\n CheckoutSession,\n LineItem,\n CreateSessionParams,\n CheckoutSessionResult,\n CreateCustomerParams,\n UpdateCustomerParams,\n WebhookEvent,\n Customer,\n} from '@flopay/shared';\nimport {\n FloPayError,\n SDK_VERSION,\n buildProductPayload,\n foldIntoProducts,\n resolveSessionCurrency,\n} from '@flopay/shared';\n\nexport const STRIPE_API_VERSION = '2026-04-22.dahlia';\n\ntype StripeConfig = NonNullable<ConstructorParameters<typeof Stripe>[1]>;\n\n/** Options for the FloPay Node SDK constructor. */\nexport interface FloPayNodeOptions {\n apiVersion?: string;\n /** Stripe secret key, required when using Stripe-specific operations. */\n stripeSecretKey?: string;\n}\n\n/**\n * Server-side FloPay SDK.\n *\n * Supports two modes of operation:\n *\n * 1. **Billing API mode** — Creates checkout sessions via the FloPay/ClickTech\n * billing API (`POST /v1/checkouts/sessions`). This mirrors the\n * `createCheckoutSession` function from `clicktech-core-ui`.\n *\n * 2. **Stripe direct mode** — When a `stripeSecretKey` is provided, also\n * exposes direct Stripe operations (customers, webhooks).\n *\n * @example\n * ```ts\n * import { FloPay } from '@flopay/node';\n *\n * const flopay = new FloPay('sk_test_...');\n *\n * // Create session via billing API\n * const result = await flopay.checkout.sessions.create({\n * billingApiUrl: 'https://billing.example.com',\n * checkoutBaseUrl: 'https://checkout.example.com',\n * clientId: 'client_123',\n * currency: 'USD',\n * items: [{ code: 'prod_abc' }],\n * account: { userId: 'user_1', email: 'user@example.com' },\n * successUrl: '/success',\n * cancelUrl: '/cancel',\n * });\n * ```\n */\nexport class FloPay {\n private readonly stripe: Stripe;\n\n readonly checkout: {\n sessions: {\n /** Create a checkout session via the billing API. */\n create: (params: CreateSessionParams) => Promise<CheckoutSessionResult>;\n /** Retrieve a checkout session from Stripe. */\n retrieve: (id: string) => Promise<CheckoutSession>;\n /** Expire a checkout session on Stripe. */\n expire: (id: string) => Promise<CheckoutSession>;\n /** List line items for a Stripe checkout session. */\n listLineItems: (id: string) => Promise<LineItem[]>;\n };\n };\n\n readonly webhooks: {\n constructEvent: (\n payload: string | Buffer,\n signature: string,\n secret: string,\n ) => WebhookEvent;\n };\n\n readonly customers: {\n create: (params: CreateCustomerParams) => Promise<Customer>;\n retrieve: (id: string) => Promise<Customer>;\n update: (id: string, params: UpdateCustomerParams) => Promise<Customer>;\n };\n\n constructor(secretKey: string, options?: FloPayNodeOptions) {\n if (!secretKey) {\n throw new FloPayError(\n 'A secret key is required to initialize the FloPay server SDK.',\n 'authentication_error',\n );\n }\n\n this.stripe = new Stripe(options?.stripeSecretKey ?? secretKey, {\n apiVersion: (options?.apiVersion ?? STRIPE_API_VERSION) as StripeConfig['apiVersion'],\n appInfo: {\n name: 'flopay-node',\n version: SDK_VERSION,\n },\n });\n\n this.checkout = {\n sessions: {\n create: this.createSession.bind(this),\n retrieve: this.retrieveSession.bind(this),\n expire: this.expireSession.bind(this),\n listLineItems: this.listLineItems.bind(this),\n },\n };\n\n this.webhooks = {\n constructEvent: this.constructWebhookEvent.bind(this),\n };\n\n this.customers = {\n create: this.createCustomer.bind(this),\n retrieve: this.retrieveCustomer.bind(this),\n update: this.updateCustomer.bind(this),\n };\n }\n\n /**\n * Create a checkout session via the billing API.\n *\n * Mirrors `createCheckoutSession` from `clicktech-core-ui/modules`.\n * Posts to `{billingApiUrl}/v1/checkouts/sessions` and returns\n * the session UUID for redirect.\n */\n private async createSession(\n params: CreateSessionParams,\n ): Promise<CheckoutSessionResult> {\n const {\n billingApiUrl,\n checkoutBaseUrl,\n items = [],\n subscriptions = [],\n products,\n account,\n successUrl,\n cancelUrl,\n checkoutMode = 'confirm',\n couponCodes = [],\n tagsData,\n redirectParams = {},\n timeoutMs = 12000,\n clientId,\n currency,\n utmMetadata,\n } = params;\n\n const wireProducts = products ?? foldIntoProducts(items, subscriptions);\n const sessionCurrency = resolveSessionCurrency(currency, items, subscriptions, wireProducts);\n if (!sessionCurrency) {\n throw new FloPayError(\n 'currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.',\n 'validation_error',\n { code: 'CurrencyRequired', param: 'currency' },\n );\n }\n\n const payload: Record<string, unknown> = {\n clientId,\n checkoutVersion: SDK_VERSION,\n successUrl,\n cancelUrl,\n currency: sessionCurrency,\n checkoutMode,\n products: wireProducts.map((product) => buildProductPayload(product, sessionCurrency)),\n accountData: {\n userId: account.userId,\n firstName: account.firstName ?? null,\n lastName: account.lastName ?? null,\n email: account.email,\n country: account.country ?? null,\n gender: account.gender ?? null,\n city: account.city ?? null,\n state: account.state ?? null,\n zip: account.zip ?? null,\n },\n couponCodes,\n };\n\n if (tagsData) {\n payload['tagsData'] = tagsData;\n }\n\n if (utmMetadata?.length) {\n payload['utmMetadata'] = utmMetadata;\n }\n\n const url = `${billingApiUrl.replace(/\\/+$/, '')}/v1/checkouts/sessions`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let status: number;\n let body: { data?: { uuid?: string; nonce?: string } } | undefined;\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: controller.signal,\n });\n\n status = response.status;\n\n try {\n body = await response.json() as { data?: { uuid?: string; nonce?: string } };\n } catch {\n // 204 or empty body\n }\n } finally {\n clearTimeout(timer);\n }\n\n if (status === 201) {\n const uuid = body?.data?.uuid;\n const nonce = body?.data?.nonce;\n\n if (!uuid) {\n throw new Error('Checkout session created but no UUID was returned by the billing API');\n }\n\n if (!nonce) {\n throw new FloPayError(\n 'Checkout session created but no `nonce` was returned by the billing API. Upgrade the billing service to TeamFloPay/backend#640 or later.',\n 'api_error',\n { code: 'MissingCheckoutSessionToken' },\n );\n }\n\n const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\\/+$/, '')}/secure`);\n redirectUrl.searchParams.set('id', uuid);\n\n for (const [key, value] of Object.entries(redirectParams)) {\n redirectUrl.searchParams.set(key, value);\n }\n\n return { status: 201, redirectUrl: redirectUrl.toString(), nonce };\n }\n\n if (status === 204) {\n return { status: 204 };\n }\n\n return { status };\n }\n\n private normalizeStripeSession(\n session: Stripe.Checkout.Session,\n ): CheckoutSession {\n const statusMap: Record<string, CheckoutSession['status']> = {\n open: 'open',\n complete: 'complete',\n expired: 'expired',\n };\n\n return {\n id: session.id,\n clientSecret: session.client_secret ?? '',\n mode: session.mode as CheckoutSession['mode'],\n status: statusMap[session.status ?? 'open'] ?? 'open',\n amount: session.amount_total ?? 0,\n currency: session.currency ?? 'usd',\n metadata: (session.metadata as Record<string, string>) ?? {},\n };\n }\n\n private async retrieveSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.retrieve(id);\n return this.normalizeStripeSession(session);\n }\n\n private async expireSession(id: string): Promise<CheckoutSession> {\n const session = await this.stripe.checkout.sessions.expire(id);\n return this.normalizeStripeSession(session);\n }\n\n private async listLineItems(id: string): Promise<LineItem[]> {\n const items = await this.stripe.checkout.sessions.listLineItems(id);\n return items.data.map((item) => ({\n price: item.price?.id,\n quantity: item.quantity ?? 1,\n }));\n }\n\n private constructWebhookEvent(\n payload: string | Buffer,\n signature: string,\n secret: string,\n ): WebhookEvent {\n const event = this.stripe.webhooks.constructEvent(payload, signature, secret);\n\n return {\n id: event.id,\n type: event.type,\n data: event.data as unknown as Record<string, unknown>,\n created: event.created,\n };\n }\n\n private async createCustomer(params: CreateCustomerParams): Promise<Customer> {\n const customer = await this.stripe.customers.create({\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? params.email,\n firstName: params.name?.split(' ')[0],\n lastName: params.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async retrieveCustomer(id: string): Promise<Customer> {\n const customer = await this.stripe.customers.retrieve(id);\n\n if (customer.deleted) {\n throw new FloPayError(\n `Customer ${id} has been deleted`,\n 'api_error',\n { code: 'resource_missing' },\n );\n }\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n\n private async updateCustomer(\n id: string,\n params: UpdateCustomerParams,\n ): Promise<Customer> {\n const customer = await this.stripe.customers.update(id, {\n email: params.email,\n name: params.name,\n metadata: params.metadata,\n });\n\n return {\n id: customer.id,\n email: customer.email ?? '',\n firstName: customer.name?.split(' ')[0],\n lastName: customer.name?.split(' ').slice(1).join(' '),\n };\n }\n}\n"],"mappings":"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","names":[]}