@lacagnottedesproches/lcdp-js-lite 1.6.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/src/index.ts ADDED
@@ -0,0 +1,151 @@
1
+ // @see https://github.com/stripe/stripe-js
2
+ import { LCDPRequest } from "./api";
3
+ import { LCDP, LCDPConstructor, LoadLCDP } from "./types";
4
+ export * from "./types";
5
+
6
+ let lcdpPromise: Promise<LCDPConstructor | null> | null = null;
7
+ let loadCalled: boolean = false;
8
+
9
+ const API_URL = "https://api.lacagnottedesproches.fr";
10
+ const API_URL_REGEX = (url: string) => new RegExp(`/^${url}?(.*)$/`);
11
+ let onErrorListener: (() => void) | null = null;
12
+ let onLoadListener: (() => void) | null = null;
13
+
14
+ const onError = (reject: (reason?: any) => void) => () => {
15
+ reject(new Error("Failed to load lcdp.js"));
16
+ };
17
+
18
+ const onLoad =
19
+ (
20
+ resolve: (value: LCDPConstructor | null) => void,
21
+ reject: (reason?: any) => void,
22
+ ) =>
23
+ () => {
24
+ if (window.lcdp?.Afina) {
25
+ resolve(window.lcdp.Afina);
26
+ } else {
27
+ reject(new Error("lcdp.js not available"));
28
+ }
29
+ };
30
+
31
+ const findScript = (
32
+ url: string,
33
+ url_regex: RegExp,
34
+ ): HTMLScriptElement | null => {
35
+ const scripts = document.querySelectorAll<HTMLScriptElement>(
36
+ `script[src^="${url}"]`,
37
+ );
38
+ for (let i = 0; i < scripts.length; i++) {
39
+ const script = scripts[i];
40
+ if (!url_regex.test(script.src)) {
41
+ continue;
42
+ }
43
+ return script;
44
+ }
45
+ return null;
46
+ };
47
+
48
+ const injectScript = (url: string): HTMLScriptElement => {
49
+ const script = document.createElement("script");
50
+ script.src = `${url}/js`;
51
+ const headOrBody = document.head || document.body;
52
+ if (!headOrBody) {
53
+ throw new Error(
54
+ "Expected document.body not to be null. Stripe.js requires a <body> element.",
55
+ );
56
+ }
57
+ headOrBody.appendChild(script);
58
+ const divModalRoot = document.createElement("div");
59
+ divModalRoot.id = "lcdp-modal-root";
60
+ document.body.appendChild(divModalRoot);
61
+ return script;
62
+ };
63
+
64
+ const getLCDPPromise: (
65
+ args?: Parameters<LCDPConstructor>,
66
+ ) => Promise<LCDPConstructor | null> = (args) => {
67
+ if (lcdpPromise) {
68
+ return lcdpPromise;
69
+ }
70
+ lcdpPromise = new Promise((resolve, reject) => {
71
+ if (typeof window === "undefined" || typeof document === "undefined") {
72
+ resolve(null);
73
+ return;
74
+ }
75
+
76
+ if (window.lcdp?.Afina) {
77
+ resolve(window.lcdp.Afina);
78
+ return;
79
+ }
80
+
81
+ let url = API_URL;
82
+ let url_regex = API_URL_REGEX(url);
83
+
84
+ if (args && args.length >= 2) {
85
+ let opts = args[1];
86
+ if (opts?.api_url) {
87
+ url = opts.api_url;
88
+ url_regex = API_URL_REGEX(url);
89
+ }
90
+ }
91
+
92
+ try {
93
+ let script: HTMLScriptElement | null = findScript(url, url_regex);
94
+ if (script) {
95
+ console.warn("it seems a lcdp script already exists.");
96
+ } else if (!script) {
97
+ script = injectScript(url);
98
+ } else if (
99
+ script &&
100
+ onLoadListener !== null &&
101
+ onErrorListener !== null
102
+ ) {
103
+ // remove event listeners
104
+ (script as HTMLScriptElement).removeEventListener(
105
+ "load",
106
+ onLoadListener,
107
+ );
108
+ (script as HTMLScriptElement).removeEventListener(
109
+ "error",
110
+ onErrorListener,
111
+ );
112
+
113
+ // if script exists, but we are reloading due to an error,
114
+ // reload script to trigger 'load' event
115
+ (script as HTMLScriptElement).parentNode?.removeChild(script);
116
+ script = injectScript(url);
117
+ }
118
+
119
+ onLoadListener = onLoad(resolve, reject);
120
+ onErrorListener = onError(reject);
121
+ script.addEventListener("load", onLoadListener);
122
+
123
+ script.addEventListener("error", onErrorListener);
124
+ } catch (error) {
125
+ reject(error);
126
+ return;
127
+ }
128
+ });
129
+ return lcdpPromise;
130
+ };
131
+
132
+ const initLCDP = async (
133
+ maybeLCDP: LCDPConstructor | null,
134
+ args: Parameters<LCDPConstructor>,
135
+ ): Promise<LCDP | null> => {
136
+ if (maybeLCDP === null) {
137
+ return Promise.resolve(null);
138
+ }
139
+ const lcdp: LCDP = new (maybeLCDP as any)(...args);
140
+ return lcdp.init().then((lcdpReady) => lcdpReady);
141
+ };
142
+
143
+ export const loadLCDP: LoadLCDP = async (...args) => {
144
+ loadCalled = true;
145
+ return getLCDPPromise(args).then((lcdp) => {
146
+ return initLCDP(lcdp, args);
147
+ });
148
+ };
149
+
150
+ export const requestLCDP = (secret: string, apiUrl: string = API_URL) =>
151
+ new LCDPRequest(secret, apiUrl);
package/src/types.ts ADDED
@@ -0,0 +1,320 @@
1
+ import { Stripe } from "@stripe/stripe-js";
2
+ import { UUID } from "crypto";
3
+
4
+ export enum AfinaError {
5
+ Verify = "Fail to verify origin request",
6
+ Stripe = "Stripe is not available",
7
+ ClientSide = "Should be on Client Side",
8
+ Key = "A publishable key is mandatory",
9
+ }
10
+
11
+ export enum TokenObjectType {
12
+ Account = "account",
13
+ Pot = "pot",
14
+ Wizard = "wizard",
15
+ }
16
+
17
+ export interface Country {
18
+ id: string;
19
+ name: string;
20
+ }
21
+
22
+ export interface Category {
23
+ id: string;
24
+ name: string;
25
+ slug: string;
26
+ icon: string | null;
27
+ icon_url: string | null;
28
+ }
29
+
30
+ export interface TokenObject {
31
+ id: string;
32
+ type: TokenObjectType;
33
+ client_ip: string;
34
+ }
35
+
36
+ interface Pagination {
37
+ has_next: boolean;
38
+ has_prev: boolean;
39
+ page: number;
40
+ pages: number;
41
+ per_page: number;
42
+ total: number;
43
+ }
44
+
45
+ export interface ProfitAccount {
46
+ entity_name: string | null;
47
+ entity_url: string | null;
48
+ }
49
+
50
+ export interface Pot {
51
+ id: string;
52
+ date_created_ts: number;
53
+ slug: string;
54
+ name: string;
55
+ date_end_ts: number | null;
56
+ objectif: number | null;
57
+ status_online: boolean;
58
+ status_public: boolean;
59
+ status_closed: boolean;
60
+ amount_collected_hidden: boolean;
61
+ public_pot_url: string;
62
+ public_donation_url: string;
63
+ stat_amount_donation: number | null;
64
+ stat_nb_donation: number;
65
+ profit_acct: ProfitAccount | null;
66
+ profit_ghost: ProfitAccount | null;
67
+ description: string | null;
68
+ video: string | null;
69
+ visual_main_url: string | null;
70
+ visual_secondary_url: string | null;
71
+ with_tax_receipt: boolean;
72
+ }
73
+
74
+ export interface PotsPagination extends Pagination {
75
+ items: Pot[];
76
+ }
77
+
78
+ export interface PayloadAccount {
79
+ email: string;
80
+ password?: string | null;
81
+ first_name: string;
82
+ last_name: string;
83
+ birth_date: Date;
84
+ address: string;
85
+ postcode: string;
86
+ city: string;
87
+ phone_number: string;
88
+ country_code: string;
89
+ tags?: string[] | undefined;
90
+ }
91
+
92
+ interface PayloadPotBase {
93
+ name: string;
94
+ category_id: string;
95
+ amount_target?: number | undefined;
96
+ ts_end?: number | undefined;
97
+ status_public?: boolean | undefined;
98
+ total_amount_hidden?: boolean | undefined;
99
+ description?: string | undefined;
100
+ assoid?: string | undefined;
101
+ }
102
+
103
+ export interface PayloadPot extends PayloadPotBase {
104
+ acct_id: string;
105
+ }
106
+
107
+ export interface PayloadPotUpdate {
108
+ pot_id: UUID;
109
+ name?: string | undefined;
110
+ category_id?: UUID | undefined;
111
+ objectif?: number | undefined;
112
+ date_end_ts?: number | undefined;
113
+ status_public?: boolean | undefined;
114
+ amount_collected_hidden?: boolean | undefined;
115
+ description?: string | undefined;
116
+ }
117
+
118
+ export interface PayloadAccountAndPot extends PayloadAccount, PayloadPotBase {}
119
+
120
+ export interface DataDonation {
121
+ first_name: string;
122
+ last_name: string;
123
+ email: string;
124
+ pot_id: UUID;
125
+ return_url: string;
126
+ message?: string | null | undefined;
127
+ stay_anonymous?: boolean | null | undefined;
128
+ hide_amount_donation?: boolean | null | undefined;
129
+ is_newsletter?: boolean | null | undefined;
130
+ address?: string | null | undefined;
131
+ postcode?: string | null | undefined;
132
+ city?: string | null | undefined;
133
+ country?: string | null | undefined;
134
+ org_name?: string | null | undefined;
135
+ org_siren?: string | null | undefined;
136
+ org_status?: string | null | undefined;
137
+ }
138
+
139
+ export interface PayloadDonation extends DataDonation {
140
+ confirmation_token_id: string;
141
+ idempotency_key: string;
142
+ amount_donation: number;
143
+ amount_tip: number;
144
+ }
145
+
146
+ export interface DonationResult {
147
+ error?: string;
148
+ status: string | "requires_action";
149
+ client_secret: string;
150
+ donation_id?: UUID;
151
+ }
152
+
153
+ export interface AccountCreated {
154
+ acct_id: string;
155
+ first_name: string;
156
+ last_name: string;
157
+ email: string;
158
+ }
159
+
160
+ export interface PotCreated {
161
+ pot_id: string;
162
+ name: string;
163
+ slug: string;
164
+ }
165
+
166
+ export interface AccountAndPotCreated {
167
+ account: AccountCreated;
168
+ pot: PotCreated;
169
+ }
170
+
171
+ export interface StatMetric {
172
+ nb_pots: number;
173
+ amount_donations: number;
174
+ amount_moy_donations: number;
175
+ nb_donations: number;
176
+ }
177
+
178
+ export interface StatMetricTips {
179
+ nb_tips: number;
180
+ amount: number;
181
+ amount_real: number;
182
+ }
183
+
184
+ export interface StatDashboard {
185
+ total: StatMetric;
186
+ date_range: StatMetric;
187
+ tips?: {
188
+ total: StatMetricTips;
189
+ date_range: StatMetricTips;
190
+ };
191
+ }
192
+
193
+ export interface StatsPot {
194
+ id: string;
195
+ date_created_ts: number;
196
+ slug: string;
197
+ name: string;
198
+ status_online: boolean;
199
+ status_public: boolean;
200
+ status_closed: boolean;
201
+ owner_first_name: string | null;
202
+ owner_last_name: string | null;
203
+ tags?: string | null;
204
+ nb_donations: number;
205
+ amount_donations: number | null;
206
+ amount_moy_donations: number | null;
207
+ nb_tips?: number;
208
+ amount_tips?: number;
209
+ amount_real_tips?: number;
210
+ }
211
+
212
+ export interface StatsPotsPagination extends Pagination {
213
+ items: StatsPot[];
214
+ }
215
+
216
+ export interface Association {
217
+ id: string;
218
+ nom: string;
219
+ recu_fiscal: 0 | 1;
220
+ mise_en_avant: 0 | 1;
221
+ categories?: string[];
222
+ description_courte?: string;
223
+ link_logo: string;
224
+ alt_logo: string;
225
+ link_cta_pot: string;
226
+ lien_page_wordpress: string;
227
+ assoid: string;
228
+ }
229
+
230
+ export interface Associations {
231
+ hits: Association[];
232
+ offset: number;
233
+ limit: number;
234
+ query: string;
235
+ }
236
+
237
+ export interface PayloadQueryAssociations {
238
+ q?: string;
239
+ limit?: number;
240
+ offset?: number;
241
+ filter?: string[][];
242
+ }
243
+
244
+ export interface AssociationCategory {
245
+ id: string;
246
+ name: string;
247
+ picto?: string;
248
+ }
249
+
250
+ /**
251
+ * Represents available features from Afina API.
252
+ */
253
+ export interface LCDP {
254
+ stripe: Stripe | undefined | null;
255
+ init: () => Promise<LCDP>;
256
+ createTokenAccount: (payload: PayloadAccount) => Promise<TokenObject>;
257
+ createTokenPot: (payload: PayloadPot) => Promise<TokenObject>;
258
+ createTokenAccountAndPot: (
259
+ payload: PayloadAccountAndPot,
260
+ ) => Promise<TokenObject>;
261
+ createTokenPotUpdate: (payload: PayloadPotUpdate) => Promise<TokenObject>;
262
+ createDonation: (payload: PayloadDonation) => Promise<DonationResult>;
263
+ getAvailableCountries: () => Promise<Country[]>;
264
+ getCategories: () => Promise<Category[]>;
265
+ getPots: (params?: ParamsPagination) => Promise<PotsPagination>;
266
+ getPot: (potId: string) => Promise<Pot>;
267
+ getStatsDashboard: (
268
+ ts_start?: number,
269
+ ts_end?: number,
270
+ ) => Promise<StatDashboard>;
271
+ getStatsPots: (
272
+ ts_start?: number,
273
+ ts_end?: number,
274
+ page?: number,
275
+ per_page?: number,
276
+ ) => Promise<StatsPotsPagination>;
277
+ retrieveAssociations: (
278
+ payload?: PayloadQueryAssociations,
279
+ ) => Promise<Associations>;
280
+ getAssociation: (id: string) => Promise<Association>;
281
+ getAssociationCategories: () => Promise<AssociationCategory[]>;
282
+ }
283
+
284
+ export interface LCDPApi {
285
+ createAccount: (payload: TokenObject) => Promise<AccountCreated>;
286
+ createPot: (payload: TokenObject) => Promise<PotCreated>;
287
+ createAccountAndPot: (payload: TokenObject) => Promise<AccountAndPotCreated>;
288
+ updatePot: (payload: TokenObject) => Promise<void>;
289
+ }
290
+
291
+ export type LCDPOpts = {
292
+ api_url?: string;
293
+ };
294
+
295
+ export interface LCDPConstructor {
296
+ (lcdpPubKey: string, options?: LCDPOpts): LCDP;
297
+ }
298
+
299
+ export type LoadLCDP = (
300
+ ...args: Parameters<LCDPConstructor>
301
+ ) => Promise<LCDP | null>;
302
+
303
+ export interface ParamsPagination {
304
+ page?: number;
305
+ per_page?: number;
306
+ }
307
+
308
+ declare global {
309
+ interface Window {
310
+ lcdp?: {
311
+ Afina: LCDPConstructor;
312
+ };
313
+ }
314
+ }
315
+
316
+ export interface DataAccount extends Omit<PayloadAccount, "birth_date"> {
317
+ birth_date: number;
318
+ }
319
+
320
+ export {};
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "ESNext",
4
+ "target": "ESNext",
5
+ "outDir": "dist",
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "declaration": true,
11
+ "emitDeclarationOnly": true,
12
+ "moduleResolution": "node"
13
+ },
14
+ "include": ["./src/**/*"]
15
+ }