@jupiterdo/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.
@@ -0,0 +1,613 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Jupiter = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ /******************************************************************************
8
+ Copyright (c) Microsoft Corporation.
9
+
10
+ Permission to use, copy, modify, and/or distribute this software for any
11
+ purpose with or without fee is hereby granted.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
14
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
16
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
17
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19
+ PERFORMANCE OF THIS SOFTWARE.
20
+ ***************************************************************************** */
21
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
22
+
23
+
24
+ function __awaiter(thisArg, _arguments, P, generator) {
25
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
26
+ return new (P || (P = Promise))(function (resolve, reject) {
27
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
28
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
29
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
30
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
31
+ });
32
+ }
33
+
34
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
35
+ var e = new Error(message);
36
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
37
+ };
38
+
39
+ /**
40
+ * API base URLs for different environments
41
+ */
42
+ const API_URLS = {
43
+ production: "https://api.jupiter.do",
44
+ sandbox: "https://api-sandbox.jupiter.do",
45
+ localhost: "http://localhost:8000",
46
+ };
47
+ /**
48
+ * App URLs for different environments
49
+ */
50
+ const APP_URLS = {
51
+ production: "https://jupiter.do",
52
+ sandbox: "https://sandbox.jupiter.do",
53
+ localhost: "http://localhost:3000",
54
+ };
55
+
56
+ /**
57
+ * API client for communicating with Jupiter backend
58
+ */
59
+ class JupiterAPI {
60
+ constructor(config) {
61
+ this.apiKey = config.apiKey;
62
+ this.baseUrl = API_URLS[config.environment || "production"];
63
+ }
64
+ createPaymentLink(amount, referenceId) {
65
+ return __awaiter(this, void 0, void 0, function* () {
66
+ var _a;
67
+ let response;
68
+ try {
69
+ response = yield fetch(`${this.baseUrl}/sdk/link/`, {
70
+ method: "POST",
71
+ headers: {
72
+ "Content-Type": "application/json",
73
+ Authorization: `Bearer ${this.apiKey}`,
74
+ },
75
+ body: JSON.stringify({
76
+ amount,
77
+ reference_id: referenceId,
78
+ }),
79
+ });
80
+ }
81
+ catch (error) {
82
+ // Handle network errors
83
+ throw {
84
+ code: "NETWORK_ERROR",
85
+ message: error instanceof Error ? error.message : "Unknown error",
86
+ };
87
+ }
88
+ if (!response.ok) {
89
+ const error = yield response.json().catch(() => ({}));
90
+ throw {
91
+ code: error.code || `HTTP_${response.status}`,
92
+ message: error.detail ||
93
+ ((_a = error.amount) === null || _a === void 0 ? void 0 : _a[0]) ||
94
+ `API error: ${response.status} ${response.statusText}`,
95
+ details: error,
96
+ };
97
+ }
98
+ return response.json();
99
+ });
100
+ }
101
+ }
102
+
103
+ const MODAL_STYLES = `
104
+ /* Animations */
105
+ @keyframes jupiter-fadeIn {
106
+ from { opacity: 0; }
107
+ to { opacity: 1; }
108
+ }
109
+
110
+ @keyframes jupiter-fadeOut {
111
+ from { opacity: 1; }
112
+ to { opacity: 0; }
113
+ }
114
+
115
+ @keyframes jupiter-fadeInZoom {
116
+ from {
117
+ opacity: 0;
118
+ transform: translate(-50%, -50%) scale(0.95);
119
+ }
120
+ to {
121
+ opacity: 1;
122
+ transform: translate(-50%, -50%) scale(1);
123
+ }
124
+ }
125
+
126
+ @keyframes jupiter-fadeOutZoom {
127
+ from {
128
+ opacity: 1;
129
+ transform: translate(-50%, -50%) scale(1);
130
+ }
131
+ to {
132
+ opacity: 0;
133
+ transform: translate(-50%, -50%) scale(0.95);
134
+ }
135
+ }
136
+
137
+ @keyframes jupiter-slideInFromRight {
138
+ from { transform: translateX(100%); }
139
+ to { transform: translateX(0); }
140
+ }
141
+
142
+ @keyframes jupiter-slideOutToRight {
143
+ from { transform: translateX(0); }
144
+ to { transform: translateX(100%); }
145
+ }
146
+
147
+ /* Overlay */
148
+ .jupiter-modal-overlay {
149
+ position: fixed;
150
+ inset: 0;
151
+ z-index: 9999;
152
+ background-color: rgba(0, 0, 0, 0.5);
153
+ display: none;
154
+ }
155
+
156
+ .jupiter-modal-overlay.open {
157
+ display: block;
158
+ animation: jupiter-fadeIn 0.2s ease-out;
159
+ }
160
+
161
+ .jupiter-modal-overlay.closing {
162
+ animation: jupiter-fadeOut 0.2s ease-out;
163
+ }
164
+
165
+ /* Container */
166
+ .jupiter-modal-container {
167
+ display: none;
168
+ position: relative;
169
+ border-radius: 0.625rem;
170
+ overflow: hidden;
171
+ background-color: oklch(0.145 0 0);
172
+
173
+ }
174
+
175
+ .jupiter-modal-container.open {
176
+ display: flex;
177
+ flex-direction: column;
178
+ }
179
+
180
+ /* Container - Mobile */
181
+ @media (max-width: 767px) {
182
+ .jupiter-modal-container {
183
+ border-radius: 0;
184
+ }
185
+
186
+ .jupiter-modal-container.open {
187
+ position: fixed;
188
+ inset: 0;
189
+ z-index: 10000;
190
+ animation: jupiter-slideInFromRight 0.5s ease-out;
191
+ }
192
+
193
+ .jupiter-modal-container.closing {
194
+ animation: jupiter-slideOutToRight 0.3s ease-out;
195
+ }
196
+
197
+ .jupiter-modal-overlay {
198
+ pointer-events: none;
199
+ }
200
+ }
201
+
202
+ /* Container - Desktop */
203
+ @media (min-width: 768px) {
204
+ .jupiter-modal-container.open {
205
+ position: fixed;
206
+ top: 50%;
207
+ left: 50%;
208
+ transform: translate(-50%, -50%);
209
+ z-index: 10000;
210
+ width: calc(100% - 2rem);
211
+ max-width: 440px;
212
+ max-height: calc(100vh - 4rem);
213
+ padding: 18px;
214
+ animation: jupiter-fadeInZoom 0.2s ease-out;
215
+ }
216
+
217
+ .jupiter-modal-container.closing {
218
+ animation: jupiter-fadeOutZoom 0.2s ease-out;
219
+ }
220
+ }
221
+
222
+ /* Close Button */
223
+ .jupiter-modal-close {
224
+ position: absolute;
225
+ top: 8px;
226
+ right: 8px;
227
+ background: transparent;
228
+ border: none;
229
+ cursor: pointer;
230
+ opacity: 0.7;
231
+ transition: opacity 0.2s;
232
+ padding: 0.25rem;
233
+ border-radius: 0.25rem;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ z-index: 10001;
238
+ }
239
+
240
+ .jupiter-modal-close:hover {
241
+ opacity: 1;
242
+ }
243
+
244
+ /* Iframe - Mobile */
245
+ @media (max-width: 767px) {
246
+ .jupiter-modal-iframe {
247
+ width: 100%;
248
+ height: 100%;
249
+ flex: 1;
250
+ border: none;
251
+ }
252
+ }
253
+
254
+ /* Iframe - Desktop */
255
+ @media (min-width: 768px) {
256
+ .jupiter-modal-iframe {
257
+ width: 100%;
258
+ height: 600px;
259
+ border: none;
260
+ }
261
+ }
262
+
263
+ /* Loading Skeleton */
264
+ .jupiter-modal-skeleton {
265
+ position: absolute;
266
+ inset: 0;
267
+ background-color: #1a1a1a;
268
+ padding: 1.5rem;
269
+ padding-top: 32px;
270
+ display: flex;
271
+ flex-direction: column;
272
+ gap: 32px;
273
+ z-index: 1;
274
+ }
275
+
276
+ .jupiter-modal-skeleton.hidden {
277
+ display: none;
278
+ }
279
+
280
+ @media (min-width: 768px) {
281
+ .jupiter-modal-skeleton {
282
+ height: 600px;
283
+ }
284
+ }
285
+
286
+ .jupiter-skeleton-item {
287
+ background-color: #2a2a2a;
288
+ border-radius: 0.5rem;
289
+ animation: skeleton-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
290
+ }
291
+
292
+ @keyframes skeleton-pulse {
293
+ 0%, 100% {
294
+ opacity: 1;
295
+ }
296
+ 50% {
297
+ opacity: 0.5;
298
+ }
299
+ }
300
+ `;
301
+ const SKELETON_HTML = `
302
+ <div style="display: flex; gap: 12px;">
303
+ <div class="jupiter-skeleton-item" style="width: 48px; height: 48px; border-radius: 50%;"></div>
304
+ <div >
305
+ <p class="jupiter-skeleton-item" style="width: 180px; height: 20px; border-radius: 12px; margin-bottom: 8px"></p>
306
+ <p class="jupiter-skeleton-item" style="width: 100px; height: 16px; border-radius: 12px"></p>
307
+ </div>
308
+ </div>
309
+ <div style="display: flex; justify-content: center; align-items: center; flex-direction: column; gap: 12px">
310
+ <p class="jupiter-skeleton-item" style="width: 80px; height: 28px; border-radius: 18px"></p>
311
+ <p class="jupiter-skeleton-item" style="width: 220px; height: 40px; border-radius: 24px"></p>
312
+ </div>
313
+ <div style="height:2px background-color: #e5e7eb;"></div>
314
+ <div class="jupiter-skeleton-item" style="width: 300px; height: 28px; align-self: center; border-radius: 18px"></div>
315
+ <div style="display: flex; flex-direction: column; gap: 12px; justify-content: center; align-items: center;">
316
+ <div class="jupiter-skeleton-item" style="width: 375px; height: 64px; border-radius: calc(infinity * 1px)"></div>
317
+ <div class="jupiter-skeleton-item" style="width: 375px; height: 64px; border-radius: calc(infinity * 1px)"></div>
318
+ <div class="jupiter-skeleton-item" style="width: 375px; height: 64px; border-radius: calc(infinity * 1px)"></div>
319
+ </div>
320
+ `;
321
+ class Modal {
322
+ constructor() {
323
+ this.isOpen = false;
324
+ this.overlay = null;
325
+ this.container = null;
326
+ this.closeButton = null;
327
+ this.iframe = null;
328
+ this.skeleton = null;
329
+ this.initialized = false;
330
+ this.onCloseCallback = null;
331
+ this.messageHandler = null;
332
+ }
333
+ static getInstance() {
334
+ if (!Modal.instance) {
335
+ Modal.instance = new Modal();
336
+ }
337
+ return Modal.instance;
338
+ }
339
+ injectStyles() {
340
+ if (document.getElementById("jupiter-modal-styles")) {
341
+ return;
342
+ }
343
+ const style = document.createElement("style");
344
+ style.id = "jupiter-modal-styles";
345
+ style.textContent = MODAL_STYLES;
346
+ document.head.appendChild(style);
347
+ }
348
+ initializeElements() {
349
+ if (this.initialized) {
350
+ return;
351
+ }
352
+ this.injectStyles();
353
+ // Create overlay
354
+ this.overlay = document.createElement("div");
355
+ this.overlay.className = "jupiter-modal-overlay";
356
+ this.overlay.addEventListener("click", (e) => {
357
+ if (e.target === this.overlay) {
358
+ this.close();
359
+ }
360
+ });
361
+ document.body.appendChild(this.overlay);
362
+ // Create container
363
+ this.container = document.createElement("div");
364
+ this.container.className = "jupiter-modal-container";
365
+ // Create close button
366
+ this.closeButton = document.createElement("button");
367
+ this.closeButton.className = "jupiter-modal-close";
368
+ this.closeButton.setAttribute("aria-label", "Close");
369
+ this.closeButton.innerHTML = `
370
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
371
+ <path d="M18 6 6 18"></path>
372
+ <path d="m6 6 12 12"></path>
373
+ </svg>
374
+ `;
375
+ this.closeButton.addEventListener("click", (e) => {
376
+ e.preventDefault();
377
+ e.stopPropagation();
378
+ this.close();
379
+ });
380
+ this.container.appendChild(this.closeButton);
381
+ // Create skeleton
382
+ this.skeleton = document.createElement("div");
383
+ this.skeleton.className = "jupiter-modal-skeleton hidden";
384
+ this.skeleton.innerHTML = SKELETON_HTML;
385
+ this.container.appendChild(this.skeleton);
386
+ // Add container to body
387
+ document.body.appendChild(this.container);
388
+ this.initialized = true;
389
+ }
390
+ showModal(url) {
391
+ // Show skeleton loader
392
+ this.skeleton.classList.remove("hidden");
393
+ // Create iframe (hidden initially)
394
+ this.iframe = document.createElement("iframe");
395
+ this.iframe.className = "jupiter-modal-iframe";
396
+ this.iframe.src = url;
397
+ this.iframe.setAttribute("allow", "payment");
398
+ this.iframe.style.opacity = "0";
399
+ this.container.appendChild(this.iframe);
400
+ // Show iframe and hide skeleton once loaded
401
+ this.iframe.addEventListener("load", () => {
402
+ if (this.iframe && this.skeleton) {
403
+ this.skeleton.classList.add("hidden");
404
+ this.iframe.style.opacity = "1";
405
+ this.iframe.style.transition = "opacity 0.3s ease-out";
406
+ }
407
+ }, { once: true });
408
+ // Show overlay and container
409
+ this.overlay.classList.add("open");
410
+ this.container.classList.add("open");
411
+ // Prevent body scroll when modal is open
412
+ document.body.style.overflow = "hidden";
413
+ }
414
+ hideModal() {
415
+ var _a, _b, _c, _d;
416
+ // Remove open class first, then add closing class
417
+ (_a = this.overlay) === null || _a === void 0 ? void 0 : _a.classList.remove("open");
418
+ (_b = this.container) === null || _b === void 0 ? void 0 : _b.classList.remove("open");
419
+ (_c = this.overlay) === null || _c === void 0 ? void 0 : _c.classList.add("closing");
420
+ (_d = this.container) === null || _d === void 0 ? void 0 : _d.classList.add("closing");
421
+ // Remove classes and iframe after animation completes
422
+ setTimeout(() => {
423
+ var _a, _b;
424
+ (_a = this.overlay) === null || _a === void 0 ? void 0 : _a.classList.remove("closing");
425
+ (_b = this.container) === null || _b === void 0 ? void 0 : _b.classList.remove("closing");
426
+ // Restore body scroll
427
+ document.body.style.overflow = "";
428
+ // Remove iframe
429
+ if (this.iframe) {
430
+ this.iframe.remove();
431
+ this.iframe = null;
432
+ }
433
+ }, 300);
434
+ }
435
+ open(url, onClose, onMessage) {
436
+ // If already open, do nothing
437
+ if (this.isOpen) {
438
+ return;
439
+ }
440
+ this.isOpen = true;
441
+ this.onCloseCallback = onClose || null;
442
+ // Set up message listener if callback provided
443
+ if (onMessage) {
444
+ this.messageHandler = onMessage;
445
+ window.addEventListener("message", this.messageHandler);
446
+ }
447
+ this.initializeElements();
448
+ this.showModal(url);
449
+ }
450
+ close() {
451
+ if (!this.isOpen) {
452
+ return;
453
+ }
454
+ this.isOpen = false;
455
+ this.hideModal();
456
+ // Remove message listener if it exists
457
+ if (this.messageHandler) {
458
+ window.removeEventListener("message", this.messageHandler);
459
+ this.messageHandler = null;
460
+ }
461
+ // Call onClose callback if provided
462
+ if (this.onCloseCallback) {
463
+ this.onCloseCallback();
464
+ this.onCloseCallback = null;
465
+ }
466
+ }
467
+ destroy() {
468
+ this.close();
469
+ // Wait for close animation to complete before removing elements
470
+ setTimeout(() => {
471
+ var _a, _b;
472
+ (_a = this.overlay) === null || _a === void 0 ? void 0 : _a.remove();
473
+ (_b = this.container) === null || _b === void 0 ? void 0 : _b.remove();
474
+ this.overlay = null;
475
+ this.container = null;
476
+ this.closeButton = null;
477
+ this.iframe = null;
478
+ this.skeleton = null;
479
+ this.initialized = false;
480
+ Modal.instance = null;
481
+ }, 350);
482
+ }
483
+ static destroyInstance() {
484
+ if (Modal.instance) {
485
+ Modal.instance.destroy();
486
+ }
487
+ }
488
+ }
489
+ Modal.instance = null;
490
+
491
+ /**
492
+ * Main Jupiter SDK class
493
+ */
494
+ class Jupiter {
495
+ /**
496
+ * Initialize the Jupiter SDK
497
+ *
498
+ * @param config - Configuration options
499
+ *
500
+ * @example
501
+ * ```js
502
+ * const jupiter = new Jupiter({
503
+ * apiKey: 'jup_prod-xxxxx',
504
+ * environment: 'production'
505
+ * });
506
+ * ```
507
+ */
508
+ constructor(config) {
509
+ this.isInUse = false;
510
+ // Validate API key
511
+ if (!config.apiKey) {
512
+ throw {
513
+ code: "INVALID_CONFIG",
514
+ message: "apiKey is required",
515
+ };
516
+ }
517
+ // Set configuration
518
+ this.environment = config.environment || "production";
519
+ this.api = new JupiterAPI(config);
520
+ }
521
+ /**
522
+ * Authorize a payment amount and open loan application flow
523
+ *
524
+ * @example
525
+ * ```js
526
+ * jupiter.authorize({
527
+ * amount: 100000, // $1,000.00 in cents
528
+ * referenceId: "order_12345",
529
+ * onComplete: ({ loanId, status, referenceId }) => {
530
+ * console.log('Loan', loanId, 'is', status);
531
+ * },
532
+ * onCancel: () => console.log('User cancelled'),
533
+ * onError: (error) => console.error(error)
534
+ * });
535
+ * ```
536
+ */
537
+ authorize({ amount, referenceId, onComplete, onCancel, onError, modalTimeout, }) {
538
+ // Prevent multiple concurrent authorizations
539
+ if (this.isInUse) {
540
+ return;
541
+ }
542
+ // Validate inputs (throw immediately, don't call onError)
543
+ if (typeof amount !== "number" || amount <= 0) {
544
+ throw {
545
+ code: "INVALID_AMOUNT",
546
+ message: "Amount must be a positive number in cents",
547
+ };
548
+ }
549
+ if (referenceId && referenceId.length > 255) {
550
+ throw {
551
+ code: "INVALID_REFERENCE_ID",
552
+ message: "Reference ID must be 255 characters or less",
553
+ };
554
+ }
555
+ this.isInUse = true;
556
+ // Handle API call asynchronously internally
557
+ this.api
558
+ .createPaymentLink(amount, referenceId)
559
+ .then((paymentLink) => {
560
+ const paymentUrl = `${APP_URLS[this.environment]}/pay/${paymentLink.slug}`;
561
+ // Track if authorization was completed via message
562
+ let wasCompleted = false;
563
+ const modal = Modal.getInstance();
564
+ modal.open(paymentUrl,
565
+ // onClose
566
+ () => {
567
+ this.isInUse = false;
568
+ // If modal closes without completion message, treat as cancel
569
+ if (!wasCompleted) {
570
+ onCancel();
571
+ }
572
+ },
573
+ // onMessage
574
+ (event) => {
575
+ if (event.origin !== new URL(APP_URLS[this.environment]).origin) {
576
+ return;
577
+ }
578
+ if (event.data.type === "jupiter_loan_status") {
579
+ wasCompleted = true;
580
+ setTimeout(() => {
581
+ modal.close();
582
+ }, modalTimeout !== null && modalTimeout !== void 0 ? modalTimeout : 5000);
583
+ this.isInUse = false;
584
+ onComplete({
585
+ loanId: event.data.loanId,
586
+ status: event.data.status,
587
+ });
588
+ }
589
+ });
590
+ })
591
+ .catch((error) => {
592
+ this.isInUse = false;
593
+ onError(error);
594
+ });
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Jupiter JavaScript SDK
600
+ *
601
+ * @packageDocumentation
602
+ */
603
+ // For UMD build - expose on window object
604
+ if (typeof window !== "undefined") {
605
+ window.Jupiter = Jupiter;
606
+ window.JupiterModal = Modal;
607
+ }
608
+
609
+ exports.Jupiter = Jupiter;
610
+ exports.Modal = Modal;
611
+
612
+ }));
613
+ //# sourceMappingURL=index.umd.js.map