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