soundbip 2.1.1 → 2.2.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/index.js +120 -30
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const readline = require('readline');
|
|
3
3
|
const https = require('https');
|
|
4
|
-
|
|
4
|
+
// child_process used in openBrowser()
|
|
5
5
|
|
|
6
6
|
const API_URL = 'api.dltpays.com';
|
|
7
|
-
const VERSION = '2.
|
|
7
|
+
const VERSION = '2.2.0';
|
|
8
8
|
|
|
9
9
|
// Colors
|
|
10
10
|
const c = {
|
|
@@ -112,19 +112,25 @@ function apiCall(method, path, data = null) {
|
|
|
112
112
|
port: 443,
|
|
113
113
|
path: `/api/v1${path}`,
|
|
114
114
|
method,
|
|
115
|
-
headers: { 'Content-Type': 'application/json' }
|
|
115
|
+
headers: { 'Content-Type': 'application/json' },
|
|
116
|
+
timeout: 30000
|
|
116
117
|
};
|
|
117
118
|
const req = https.request(options, res => {
|
|
118
119
|
let body = '';
|
|
119
120
|
res.on('data', chunk => body += chunk);
|
|
120
121
|
res.on('end', () => {
|
|
121
122
|
try {
|
|
122
|
-
|
|
123
|
+
const parsed = JSON.parse(body);
|
|
124
|
+
resolve(parsed);
|
|
123
125
|
} catch {
|
|
124
|
-
|
|
126
|
+
reject(new Error(`Server returned invalid response (HTTP ${res.statusCode})`));
|
|
125
127
|
}
|
|
126
128
|
});
|
|
127
129
|
});
|
|
130
|
+
req.on('timeout', () => {
|
|
131
|
+
req.destroy();
|
|
132
|
+
reject(new Error('Request timed out — check your internet connection'));
|
|
133
|
+
});
|
|
128
134
|
req.on('error', reject);
|
|
129
135
|
if (data) req.write(JSON.stringify(data));
|
|
130
136
|
req.end();
|
|
@@ -132,18 +138,22 @@ function apiCall(method, path, data = null) {
|
|
|
132
138
|
}
|
|
133
139
|
|
|
134
140
|
function openBrowser(url) {
|
|
141
|
+
const { execFile } = require('child_process');
|
|
135
142
|
const platform = process.platform;
|
|
136
|
-
let command;
|
|
137
143
|
|
|
144
|
+
let cmd, args;
|
|
138
145
|
if (platform === 'darwin') {
|
|
139
|
-
|
|
146
|
+
cmd = 'open';
|
|
147
|
+
args = [url];
|
|
140
148
|
} else if (platform === 'win32') {
|
|
141
|
-
|
|
149
|
+
cmd = 'cmd';
|
|
150
|
+
args = ['/c', 'start', '', url];
|
|
142
151
|
} else {
|
|
143
|
-
|
|
152
|
+
cmd = 'xdg-open';
|
|
153
|
+
args = [url];
|
|
144
154
|
}
|
|
145
155
|
|
|
146
|
-
|
|
156
|
+
execFile(cmd, args, (err) => {
|
|
147
157
|
if (err) {
|
|
148
158
|
console.log(`${c.dim} Could not open browser automatically.${c.reset}`);
|
|
149
159
|
}
|
|
@@ -198,6 +208,10 @@ function info(label, value, indent = ' ') {
|
|
|
198
208
|
}
|
|
199
209
|
|
|
200
210
|
function spinner(text) {
|
|
211
|
+
if (!process.stdout.isTTY) {
|
|
212
|
+
console.log(` ${text}`);
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
201
215
|
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
202
216
|
let i = 0;
|
|
203
217
|
process.stdout.write(`\n${c.yellow} ${frames[0]} ${text}${c.reset}`);
|
|
@@ -210,9 +224,12 @@ function spinner(text) {
|
|
|
210
224
|
}
|
|
211
225
|
|
|
212
226
|
function stopSpinner(interval) {
|
|
227
|
+
if (!interval) return;
|
|
213
228
|
clearInterval(interval);
|
|
214
|
-
process.stdout.
|
|
215
|
-
|
|
229
|
+
if (process.stdout.isTTY) {
|
|
230
|
+
process.stdout.clearLine(0);
|
|
231
|
+
process.stdout.cursorTo(0);
|
|
232
|
+
}
|
|
216
233
|
}
|
|
217
234
|
|
|
218
235
|
const PLATFORMS = {
|
|
@@ -382,7 +399,30 @@ async function main() {
|
|
|
382
399
|
console.log(`${c.dim} Takes about 2 minutes for WooCommerce, 5-10 for custom integrations${c.reset}`);
|
|
383
400
|
console.log('');
|
|
384
401
|
|
|
402
|
+
// Terms of Service
|
|
403
|
+
section('📋', 'Vendor Terms of Service');
|
|
404
|
+
console.log('');
|
|
405
|
+
console.log(`${c.dim} By registering, you agree to the following:${c.reset}`);
|
|
406
|
+
console.log('');
|
|
407
|
+
console.log(` ${c.white}1.${c.reset} SoundBip is a ${c.bold}non-custodial${c.reset} POS platform — we never hold your funds`);
|
|
408
|
+
console.log(` ${c.white}2.${c.reset} Blockchain transactions on XRPL are ${c.bold}irreversible${c.reset} once confirmed`);
|
|
409
|
+
console.log(` ${c.white}3.${c.reset} You are responsible for your wallet security and tax compliance`);
|
|
410
|
+
console.log(` ${c.white}4.${c.reset} Platform fee: ${c.bold}0.5%${c.reset} on eligible transactions`);
|
|
411
|
+
console.log(` ${c.white}5.${c.reset} Governed by the laws of ${c.bold}Guernsey${c.reset}`);
|
|
412
|
+
console.log('');
|
|
413
|
+
console.log(` ${c.dim}Full terms: ${c.cyan}https://soundbip.com/terms${c.reset}`);
|
|
414
|
+
console.log('');
|
|
415
|
+
|
|
416
|
+
const termsAccepted = await ask(`${c.cyan} Do you accept the Vendor Terms of Service? [y/n]: ${c.reset}`);
|
|
417
|
+
if (termsAccepted.trim().toLowerCase() !== 'y' && termsAccepted.trim().toLowerCase() !== 'yes') {
|
|
418
|
+
console.log(`\n${c.yellow} Registration cancelled. You must accept the terms to continue.${c.reset}\n`);
|
|
419
|
+
rl.close();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
success('Terms accepted');
|
|
423
|
+
|
|
385
424
|
// Store details with validation
|
|
425
|
+
section('🏪', 'Business Details');
|
|
386
426
|
const storeName = await askWithValidation(
|
|
387
427
|
`${c.cyan} Store name: ${c.reset}`,
|
|
388
428
|
(name) => name.length >= 2,
|
|
@@ -425,10 +465,12 @@ async function main() {
|
|
|
425
465
|
);
|
|
426
466
|
|
|
427
467
|
// Auto-extract postcode from address if present
|
|
428
|
-
const postcodeMatch = addressStreet.match(
|
|
468
|
+
const postcodeMatch = addressStreet.match(/,?\s*GY\d{1,2}\s?\d[A-Z]{2}\s*$/i);
|
|
429
469
|
let addressPostcode;
|
|
470
|
+
let cleanStreet = addressStreet;
|
|
430
471
|
if (postcodeMatch) {
|
|
431
|
-
addressPostcode = postcodeMatch[0].trim().toUpperCase();
|
|
472
|
+
addressPostcode = postcodeMatch[0].replace(/^,?\s*/, '').trim().toUpperCase();
|
|
473
|
+
cleanStreet = addressStreet.replace(postcodeMatch[0], '').trim().replace(/,\s*$/, '');
|
|
432
474
|
success(`Postcode detected: ${addressPostcode}`);
|
|
433
475
|
} else {
|
|
434
476
|
addressPostcode = await askWithValidation(
|
|
@@ -446,8 +488,16 @@ async function main() {
|
|
|
446
488
|
console.log(`${c.dim} ${c.reset}${c.cyan}[${num.padStart(2)}]${c.reset} ${name}`);
|
|
447
489
|
});
|
|
448
490
|
console.log('');
|
|
449
|
-
|
|
450
|
-
|
|
491
|
+
let addressCity;
|
|
492
|
+
while (true) {
|
|
493
|
+
const parishChoice = await ask(`${c.cyan} Select parish [1-10]: ${c.reset}`);
|
|
494
|
+
const picked = PARISHES[parishChoice.trim()];
|
|
495
|
+
if (picked) {
|
|
496
|
+
addressCity = picked;
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
console.log(`${c.red} ✗ Please enter a number between 1 and 10${c.reset}`);
|
|
500
|
+
}
|
|
451
501
|
|
|
452
502
|
// Optional fields
|
|
453
503
|
section('📝', 'Additional Details');
|
|
@@ -455,8 +505,8 @@ async function main() {
|
|
|
455
505
|
|
|
456
506
|
const registrationNumber = await askWithValidation(
|
|
457
507
|
`${c.cyan} Guernsey registration number: ${c.reset}`,
|
|
458
|
-
(n) => n.
|
|
459
|
-
'
|
|
508
|
+
(n) => /^\d{4,10}$/.test(n.trim()),
|
|
509
|
+
'Must be a valid numeric registration number (4-10 digits)'
|
|
460
510
|
);
|
|
461
511
|
|
|
462
512
|
const description = await ask(`${c.cyan} Describe your business (optional, max 250 chars): ${c.reset}`);
|
|
@@ -466,19 +516,51 @@ async function main() {
|
|
|
466
516
|
|
|
467
517
|
let referred_by_store = null;
|
|
468
518
|
if (referralCode.trim()) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
519
|
+
try {
|
|
520
|
+
const lookupResult = await apiCall('GET', `/store/lookup-referral/${encodeURIComponent(referralCode.trim())}`);
|
|
521
|
+
if (lookupResult.success) {
|
|
522
|
+
referred_by_store = lookupResult.store_id;
|
|
523
|
+
console.log(`${c.green} ✓ Referred by: ${lookupResult.store_name}${c.reset}`);
|
|
524
|
+
} else {
|
|
525
|
+
console.log(`${c.yellow} ⚠ Referral code not found - continuing without referral${c.reset}`);
|
|
526
|
+
}
|
|
527
|
+
} catch {
|
|
528
|
+
console.log(`${c.yellow} ⚠ Could not verify referral code - continuing without referral${c.reset}`);
|
|
475
529
|
}
|
|
476
530
|
}
|
|
477
531
|
|
|
478
532
|
// Platform selection
|
|
479
533
|
showPlatformMenu();
|
|
480
|
-
|
|
481
|
-
|
|
534
|
+
let platform;
|
|
535
|
+
while (true) {
|
|
536
|
+
const platformChoice = await ask(`${c.cyan} Select platform [1-7]: ${c.reset}`);
|
|
537
|
+
platform = PLATFORMS[platformChoice.trim()];
|
|
538
|
+
if (platform) break;
|
|
539
|
+
console.log(`${c.red} ✗ Please enter a number between 1 and 7${c.reset}`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Review before submitting
|
|
543
|
+
section('✅', 'Review Your Details');
|
|
544
|
+
console.log('');
|
|
545
|
+
info('Business', storeName);
|
|
546
|
+
info('Website', storeUrl);
|
|
547
|
+
info('Email', email);
|
|
548
|
+
info('Owner', ownerName);
|
|
549
|
+
info('Address', cleanStreet);
|
|
550
|
+
info('Parish', addressCity);
|
|
551
|
+
info('Postcode', addressPostcode);
|
|
552
|
+
info('Reg Number', registrationNumber.trim());
|
|
553
|
+
if (description.trim()) info('Description', description.trim());
|
|
554
|
+
info('Platform', platform.name);
|
|
555
|
+
if (referred_by_store) info('Referral', '✓ Applied');
|
|
556
|
+
console.log('');
|
|
557
|
+
|
|
558
|
+
const confirmReg = await ask(`${c.cyan} Everything correct? [y/n]: ${c.reset}`);
|
|
559
|
+
if (confirmReg.trim().toLowerCase() !== 'y' && confirmReg.trim().toLowerCase() !== 'yes') {
|
|
560
|
+
console.log(`\n${c.yellow} Registration cancelled. Run npx soundbip again to start over.${c.reset}\n`);
|
|
561
|
+
rl.close();
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
482
564
|
|
|
483
565
|
const spin = spinner('Registering your store...');
|
|
484
566
|
|
|
@@ -488,7 +570,7 @@ async function main() {
|
|
|
488
570
|
email: email,
|
|
489
571
|
owner_name: ownerName,
|
|
490
572
|
business_address: {
|
|
491
|
-
street:
|
|
573
|
+
street: cleanStreet,
|
|
492
574
|
city: addressCity,
|
|
493
575
|
postcode: addressPostcode
|
|
494
576
|
},
|
|
@@ -503,12 +585,20 @@ async function main() {
|
|
|
503
585
|
registerBody.description = description.trim();
|
|
504
586
|
}
|
|
505
587
|
|
|
506
|
-
|
|
588
|
+
let result;
|
|
589
|
+
try {
|
|
590
|
+
result = await apiCall('POST', '/store/register', registerBody);
|
|
591
|
+
} catch (err) {
|
|
592
|
+
stopSpinner(spin);
|
|
593
|
+
console.log(`\n${c.red} ✗ ${err.message}${c.reset}\n`);
|
|
594
|
+
rl.close();
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
507
597
|
|
|
508
598
|
stopSpinner(spin);
|
|
509
599
|
|
|
510
|
-
if (result.
|
|
511
|
-
console.log(`\n${c.red} ✗ Error: ${result
|
|
600
|
+
if (!result || !result.store_id) {
|
|
601
|
+
console.log(`\n${c.red} ✗ Error: ${result?.error || 'Registration failed — unexpected server response'}${c.reset}\n`);
|
|
512
602
|
rl.close();
|
|
513
603
|
return;
|
|
514
604
|
}
|