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.
Files changed (2) hide show
  1. package/index.js +120 -30
  2. 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
- const { exec } = require('child_process');
4
+ // child_process used in openBrowser()
5
5
 
6
6
  const API_URL = 'api.dltpays.com';
7
- const VERSION = '2.1.1';
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
- resolve(JSON.parse(body));
123
+ const parsed = JSON.parse(body);
124
+ resolve(parsed);
123
125
  } catch {
124
- resolve(body);
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
- command = `open "${url}"`;
146
+ cmd = 'open';
147
+ args = [url];
140
148
  } else if (platform === 'win32') {
141
- command = `start "" "${url}"`;
149
+ cmd = 'cmd';
150
+ args = ['/c', 'start', '', url];
142
151
  } else {
143
- command = `xdg-open "${url}"`;
152
+ cmd = 'xdg-open';
153
+ args = [url];
144
154
  }
145
155
 
146
- exec(command, (err) => {
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.clearLine(0);
215
- process.stdout.cursorTo(0);
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(/GY\d{1,2}\s?\d[A-Z]{2}/i);
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
- const parishChoice = await ask(`${c.cyan} Select parish [1-10]: ${c.reset}`);
450
- const addressCity = PARISHES[parishChoice.trim()] || parishChoice.trim();
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.length >= 3,
459
- 'Registration number is required'
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
- const lookupResult = await apiCall('GET', `/store/lookup-referral/${referralCode.trim()}`);
470
- if (lookupResult.success) {
471
- referred_by_store = lookupResult.store_id;
472
- console.log(`${c.green} ✓ Referred by: ${lookupResult.store_name}${c.reset}`);
473
- } else {
474
- console.log(`${c.yellow} Referral code not found - continuing without referral${c.reset}`);
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
- const platformChoice = await ask(`${c.cyan} Select platform [1-7]: ${c.reset}`);
481
- const platform = PLATFORMS[platformChoice] || PLATFORMS['3'];
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: addressStreet,
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
- const result = await apiCall('POST', '/store/register', registerBody);
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.error) {
511
- console.log(`\n${c.red} ✗ Error: ${result.error}${c.reset}\n`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soundbip",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "CLI for SoundBip POS - instant affiliate commissions on XRPL",
5
5
  "bin": {
6
6
  "soundbip": "./index.js"