soundbip 2.0.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 +513 -0
  2. package/package.json +30 -0
package/index.js ADDED
@@ -0,0 +1,513 @@
1
+ #!/usr/bin/env node
2
+ const readline = require('readline');
3
+ const https = require('https');
4
+ const { exec } = require('child_process');
5
+
6
+ const API_URL = 'api.dltpays.com';
7
+
8
+ // Colors
9
+ const c = {
10
+ reset: '\x1b[0m',
11
+ bold: '\x1b[1m',
12
+ dim: '\x1b[2m',
13
+ green: '\x1b[32m',
14
+ cyan: '\x1b[36m',
15
+ yellow: '\x1b[33m',
16
+ red: '\x1b[31m',
17
+ magenta: '\x1b[35m',
18
+ blue: '\x1b[34m',
19
+ bgBlue: '\x1b[44m',
20
+ bgGreen: '\x1b[42m',
21
+ bgMagenta: '\x1b[45m',
22
+ white: '\x1b[37m',
23
+ gray: '\x1b[90m'
24
+ };
25
+
26
+ const rl = readline.createInterface({
27
+ input: process.stdin,
28
+ output: process.stdout
29
+ });
30
+
31
+ function ask(question) {
32
+ return new Promise(resolve => rl.question(question, resolve));
33
+ }
34
+
35
+ function isValidEmail(email) {
36
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
37
+ return emailRegex.test(email);
38
+ }
39
+
40
+ function isValidUrl(url) {
41
+ try {
42
+ const parsed = new URL(url.startsWith('http') ? url : `https://${url}`);
43
+ return parsed.hostname.includes('.');
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ function normalizeUrl(url) {
50
+ if (!url.startsWith('http')) {
51
+ return `https://${url}`;
52
+ }
53
+ return url;
54
+ }
55
+
56
+ async function askWithValidation(question, validator, errorMessage, normalizer = null) {
57
+ while (true) {
58
+ const answer = await ask(question);
59
+
60
+ if (!answer.trim()) {
61
+ console.log(`${c.red} ✗ This field is required${c.reset}`);
62
+ continue;
63
+ }
64
+
65
+ if (validator && !validator(answer.trim())) {
66
+ console.log(`${c.red} ✗ ${errorMessage}${c.reset}`);
67
+ continue;
68
+ }
69
+
70
+ return normalizer ? normalizer(answer.trim()) : answer.trim();
71
+ }
72
+ }
73
+
74
+ function apiCall(method, path, data = null) {
75
+ return new Promise((resolve, reject) => {
76
+ const options = {
77
+ hostname: API_URL,
78
+ port: 443,
79
+ path: `/api/v1${path}`,
80
+ method,
81
+ headers: { 'Content-Type': 'application/json' }
82
+ };
83
+ const req = https.request(options, res => {
84
+ let body = '';
85
+ res.on('data', chunk => body += chunk);
86
+ res.on('end', () => {
87
+ try {
88
+ resolve(JSON.parse(body));
89
+ } catch {
90
+ resolve(body);
91
+ }
92
+ });
93
+ });
94
+ req.on('error', reject);
95
+ if (data) req.write(JSON.stringify(data));
96
+ req.end();
97
+ });
98
+ }
99
+
100
+ function openBrowser(url) {
101
+ const platform = process.platform;
102
+ let command;
103
+
104
+ if (platform === 'darwin') {
105
+ command = `open "${url}"`;
106
+ } else if (platform === 'win32') {
107
+ command = `start "" "${url}"`;
108
+ } else {
109
+ command = `xdg-open "${url}"`;
110
+ }
111
+
112
+ exec(command, (err) => {
113
+ if (err) {
114
+ console.log(`${c.dim} Could not open browser automatically.${c.reset}`);
115
+ }
116
+ });
117
+ }
118
+
119
+ function banner() {
120
+ console.log('');
121
+ console.log(`${c.cyan} ███████╗ ██████╗ ██╗ ██╗███╗ ██╗██████╗ ██████╗ ██╗██████╗${c.reset}`);
122
+ console.log(`${c.cyan} ██╔════╝██╔═══██╗██║ ██║████╗ ██║██╔══██╗██╔══██╗██║██╔══██╗${c.reset}`);
123
+ console.log(`${c.green} ███████╗██║ ██║██║ ██║██╔██╗ ██║██║ ██║██████╔╝██║██████╔╝${c.reset}`);
124
+ console.log(`${c.green} ╚════██║██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══██╗██║██╔═══╝${c.reset}`);
125
+ console.log(`${c.cyan} ███████║╚██████╔╝╚██████╔╝██║ ╚████║██████╔╝██████╔╝██║██║${c.reset}`);
126
+ console.log(`${c.cyan} ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝╚═════╝ ╚═════╝ ╚═╝╚═╝${c.reset}`);
127
+ console.log('');
128
+ console.log(`${c.bold}${c.white} ⚡ POS & Affiliate Commissions on XRPL ⚡${c.reset}`);
129
+ console.log(`${c.dim} Pay affiliates in seconds, not months${c.reset}`);
130
+ console.log('');
131
+ console.log(`${c.gray} ════════════════════════════════════════════════════════════════════════════════════${c.reset}`);
132
+ console.log('');
133
+ }
134
+
135
+ function section(emoji, title) {
136
+ console.log('');
137
+ console.log(`${c.gray} ──────────────────────────────────────────────────────────────────────────────────${c.reset}`);
138
+ console.log(`${c.bold} ${emoji} ${title}${c.reset}`);
139
+ console.log(`${c.gray} ──────────────────────────────────────────────────────────────────────────────────${c.reset}`);
140
+ }
141
+
142
+ function box(lines, color = c.cyan) {
143
+ const maxLen = Math.max(...lines.map(l => l.replace(/\x1b\[[0-9;]*m/g, '').length));
144
+ const pad = (s) => {
145
+ const plainLen = s.replace(/\x1b\[[0-9;]*m/g, '').length;
146
+ return s + ' '.repeat(maxLen - plainLen);
147
+ };
148
+ console.log(`${color} ╭${'─'.repeat(maxLen + 2)}╮${c.reset}`);
149
+ lines.forEach(line => {
150
+ console.log(`${color} │${c.reset} ${pad(line)} ${color}│${c.reset}`);
151
+ });
152
+ console.log(`${color} ╰${'─'.repeat(maxLen + 2)}╯${c.reset}`);
153
+ }
154
+
155
+ function success(msg) {
156
+ console.log(`${c.green} ✓ ${msg}${c.reset}`);
157
+ }
158
+
159
+ function info(label, value, indent = ' ') {
160
+ const labelPad = 15;
161
+ const paddedLabel = label.padEnd(labelPad);
162
+ console.log(`${indent}${c.dim}${paddedLabel}${c.reset}${c.bold}${value}${c.reset}`);
163
+ }
164
+
165
+ function spinner(text) {
166
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
167
+ let i = 0;
168
+ process.stdout.write(`\n${c.yellow} ${frames[0]} ${text}${c.reset}`);
169
+ return setInterval(() => {
170
+ i = (i + 1) % frames.length;
171
+ process.stdout.clearLine(0);
172
+ process.stdout.cursorTo(0);
173
+ process.stdout.write(`${c.yellow} ${frames[i]} ${text}${c.reset}`);
174
+ }, 80);
175
+ }
176
+
177
+ function stopSpinner(interval) {
178
+ clearInterval(interval);
179
+ process.stdout.clearLine(0);
180
+ process.stdout.cursorTo(0);
181
+ }
182
+
183
+ const PLATFORMS = {
184
+ '1': { name: 'WooCommerce / WordPress', key: 'woocommerce' },
185
+ '2': { name: 'Shopify', key: 'shopify' },
186
+ '3': { name: 'Custom / API Integration', key: 'custom' },
187
+ '4': { name: 'Stripe Checkout', key: 'stripe' },
188
+ '5': { name: 'Gumroad', key: 'gumroad' },
189
+ '6': { name: 'LemonSqueezy', key: 'lemonsqueezy' },
190
+ '7': { name: 'Paddle', key: 'paddle' }
191
+ };
192
+
193
+ function showPlatformMenu() {
194
+ console.log('');
195
+ console.log(`${c.bold} What platform is your store on?${c.reset}`);
196
+ console.log('');
197
+ Object.entries(PLATFORMS).forEach(([num, platform]) => {
198
+ const icon = num === '1' ? '🟢' : num === '2' ? '🟣' : num === '3' ? '⚡' : '○';
199
+ console.log(`${c.dim} ${icon}${c.reset} ${c.cyan}[${num}]${c.reset} ${platform.name}`);
200
+ });
201
+ console.log('');
202
+ }
203
+
204
+ function getWooCommerceInstructions(storeId, apiSecret) {
205
+ return `
206
+ ${c.bold}${c.green} WooCommerce Setup (Easiest - 2 minutes)${c.reset}
207
+
208
+ ${c.bold}Step 1:${c.reset} Download the plugin
209
+ ${c.cyan}https://github.com/TokenCanvasIO/YesAllofUs-wordpress/releases/download/v1.0.0/YesAllofUs.zip${c.reset}
210
+
211
+ ${c.bold}Step 2:${c.reset} Install in WordPress
212
+ ${c.dim}WordPress Admin → Plugins → Add New → Upload Plugin → Select the .zip${c.reset}
213
+
214
+ ${c.bold}Step 3:${c.reset} Activate & Configure
215
+ ${c.dim}Go to Settings → YesAllofUs and enter your API credentials${c.reset}
216
+
217
+ ${c.bold}What you get:${c.reset}
218
+ ${c.green}✓${c.reset} Affiliate signup page ${c.dim}[yesallofus_affiliate_signup]${c.reset}
219
+ ${c.green}✓${c.reset} Affiliate dashboard ${c.dim}[yesallofus_affiliate_dashboard]${c.reset}
220
+ ${c.green}✓${c.reset} Auto commission tracking ${c.dim}on every WooCommerce order${c.reset}
221
+ ${c.green}✓${c.reset} Store admin dashboard ${c.dim}manage affiliates & payouts${c.reset}
222
+ ${c.green}✓${c.reset} Instant RLUSD payouts ${c.dim}via Xaman or Crossmark auto-sign${c.reset}
223
+ `;
224
+ }
225
+
226
+ function getShopifyInstructions(storeId, apiSecret) {
227
+ return `
228
+ ${c.bold}${c.magenta} Shopify Setup${c.reset}
229
+
230
+ ${c.bold}Step 1:${c.reset} Add tracking script to your theme
231
+ ${c.dim}Online Store → Themes → Edit Code → theme.liquid (before </head>)${c.reset}
232
+
233
+ ${c.blue} <script>
234
+ (function() {
235
+ const ref = new URLSearchParams(window.location.search).get('ref');
236
+ if (ref) {
237
+ document.cookie = 'yesallofus_ref=' + ref + ';path=/;max-age=2592000';
238
+ }
239
+ })();
240
+ </script>${c.reset}
241
+
242
+ ${c.bold}Step 2:${c.reset} Create a Shopify Flow or use webhooks
243
+ ${c.dim}Settings → Notifications → Webhooks → Create webhook${c.reset}
244
+ ${c.dim}Event: Order payment → URL: Your server endpoint${c.reset}
245
+
246
+ ${c.bold}Step 3:${c.reset} Call SoundBip API on order completion
247
+ ${c.dim}(See API integration below)${c.reset}
248
+
249
+ ${c.yellow}⚠ Shopify requires a small backend to call our API.${c.reset}
250
+ ${c.dim}We're building a native Shopify app - join the waitlist: support@soundbip.com${c.reset}
251
+ `;
252
+ }
253
+
254
+ function getCustomInstructions(storeId, apiSecret) {
255
+ return `
256
+ ${c.bold}${c.cyan} Custom API Integration${c.reset}
257
+
258
+ ${c.bold}Step 1:${c.reset} Track referral codes
259
+ ${c.dim}Capture the ?ref= parameter and store it with the user/order${c.reset}
260
+
261
+ ${c.bold}Step 2:${c.reset} Call payout endpoint on successful order
262
+
263
+ ${c.blue} // When order completes successfully:
264
+ const response = await fetch('https://api.dltpays.com/api/v1/payout', {
265
+ method: 'POST',
266
+ headers: {
267
+ 'Authorization': 'Bearer ${c.yellow}${apiSecret}${c.blue}',
268
+ 'Content-Type': 'application/json'
269
+ },
270
+ body: JSON.stringify({
271
+ order_id: orderId, // Your unique order ID
272
+ order_total: 49.99, // Order amount in USD
273
+ referral_code: refCode // The ?ref= code from cookie
274
+ })
275
+ });${c.reset}
276
+
277
+ ${c.bold}Step 3:${c.reset} Share your affiliate signup link
278
+ ${c.cyan}https://soundbip.com/member-login?store=${storeId}${c.reset}
279
+ `;
280
+ }
281
+
282
+ function getStripeInstructions(storeId, apiSecret) {
283
+ return `
284
+ ${c.bold}${c.blue} Stripe Checkout Integration${c.reset}
285
+
286
+ ${c.bold}Step 1:${c.reset} Add metadata to checkout session
287
+
288
+ ${c.blue} const session = await stripe.checkout.sessions.create({
289
+ // ... your config
290
+ metadata: {
291
+ referral_code: req.cookies.yesallofus_ref || ''
292
+ }
293
+ });${c.reset}
294
+
295
+ ${c.bold}Step 2:${c.reset} Handle checkout.session.completed webhook
296
+
297
+ ${c.blue} // In your Stripe webhook handler:
298
+ if (event.type === 'checkout.session.completed') {
299
+ const session = event.data.object;
300
+
301
+ await fetch('https://api.dltpays.com/api/v1/payout', {
302
+ method: 'POST',
303
+ headers: {
304
+ 'Authorization': 'Bearer ${c.yellow}${apiSecret}${c.blue}',
305
+ 'Content-Type': 'application/json'
306
+ },
307
+ body: JSON.stringify({
308
+ order_id: session.id,
309
+ order_total: session.amount_total / 100,
310
+ referral_code: session.metadata.referral_code
311
+ })
312
+ });
313
+ }${c.reset}
314
+ `;
315
+ }
316
+
317
+ function getGenericInstructions(platform, storeId, apiSecret) {
318
+ return `
319
+ ${c.bold}${c.yellow} ${PLATFORMS[platform].name} Integration${c.reset}
320
+
321
+ ${c.dim}We're working on native integrations for ${PLATFORMS[platform].name}.${c.reset}
322
+ ${c.dim}For now, use the webhook/API approach:${c.reset}
323
+
324
+ ${c.bold}Core concept:${c.reset}
325
+ ${c.cyan}1.${c.reset} Track ?ref= parameter in cookies
326
+ ${c.cyan}2.${c.reset} On successful payment, POST to our API
327
+ ${c.cyan}3.${c.reset} We handle commission calculation & instant payout
328
+
329
+ ${c.bold}API Endpoint:${c.reset}
330
+ ${c.cyan}POST https://api.dltpays.com/api/v1/payout${c.reset}
331
+
332
+ ${c.bold}Request body:${c.reset}
333
+ ${c.blue} {
334
+ "order_id": "unique_order_123",
335
+ "order_total": 99.99,
336
+ "referral_code": "ABC123"
337
+ }${c.reset}
338
+
339
+ ${c.bold}Need help?${c.reset} ${c.cyan}support@soundbip.com${c.reset}
340
+ `;
341
+ }
342
+
343
+ async function main() {
344
+ banner();
345
+
346
+ console.log(`${c.bold} Let's get your store set up with SoundBip POS & affiliate payouts${c.reset}`);
347
+ console.log(`${c.dim} Takes about 2 minutes for WooCommerce, 5-10 for custom integrations${c.reset}`);
348
+ console.log('');
349
+
350
+ // Store details with validation
351
+ const storeName = await askWithValidation(
352
+ `${c.cyan} Store name: ${c.reset}`,
353
+ (name) => name.length >= 2,
354
+ 'Store name must be at least 2 characters'
355
+ );
356
+
357
+ const storeUrl = await askWithValidation(
358
+ `${c.cyan} Website URL: ${c.reset}`,
359
+ isValidUrl,
360
+ 'Please enter a valid URL (e.g. mystore.com or https://mystore.com)',
361
+ normalizeUrl
362
+ );
363
+
364
+ const email = await askWithValidation(
365
+ `${c.cyan} Email: ${c.reset}`,
366
+ isValidEmail,
367
+ 'Please enter a valid email address'
368
+ );
369
+
370
+ // Referral code (optional)
371
+ const referralCode = await ask(`${c.cyan} Referral code (optional): ${c.reset}`);
372
+
373
+ let referred_by_store = null;
374
+ if (referralCode.trim()) {
375
+ const lookupResult = await apiCall('GET', `/store/lookup-referral/${referralCode.trim()}`);
376
+ if (lookupResult.success) {
377
+ referred_by_store = lookupResult.store_id;
378
+ console.log(`${c.green} ✓ Referred by: ${lookupResult.store_name} (50% off first month!)${c.reset}`);
379
+ } else {
380
+ console.log(`${c.yellow} ⚠ Referral code not found - continuing without referral${c.reset}`);
381
+ }
382
+ }
383
+
384
+ // Platform selection
385
+ showPlatformMenu();
386
+ const platformChoice = await ask(`${c.cyan} Select platform [1-7]: ${c.reset}`);
387
+ const platform = PLATFORMS[platformChoice] || PLATFORMS['3'];
388
+
389
+ const spin = spinner('Registering your store...');
390
+
391
+ const result = await apiCall('POST', '/store/register', {
392
+ store_name: storeName,
393
+ store_url: storeUrl,
394
+ email: email,
395
+ platform: platform.key,
396
+ referred_by_store: referred_by_store
397
+ });
398
+
399
+ stopSpinner(spin);
400
+
401
+ if (result.error) {
402
+ console.log(`\n${c.red} ✗ Error: ${result.error}${c.reset}\n`);
403
+ rl.close();
404
+ return;
405
+ }
406
+
407
+ if (result.reconnected) {
408
+ console.log('');
409
+ console.log(`${c.yellow} ⚠ Store already registered for ${storeUrl}${c.reset}`);
410
+ console.log('');
411
+ console.log(`${c.bold} Log in to your vendor dashboard:${c.reset}`);
412
+ console.log(`${c.cyan} https://soundbip.com/partner-login${c.reset}`);
413
+ console.log('');
414
+ console.log(`${c.dim} Need a new API secret? Regenerate it from the dashboard.${c.reset}`);
415
+ console.log('');
416
+ rl.close();
417
+ return;
418
+ }
419
+
420
+ console.log('');
421
+ success('Store registered successfully!');
422
+
423
+ // Credentials
424
+ section('🔐', 'Your Credentials');
425
+ console.log('');
426
+ box([
427
+ `${c.dim}Store ID${c.reset} ${c.bold}${result.store_id}${c.reset}`,
428
+ `${c.dim}API Key${c.reset} ${c.bold}${result.api_key}${c.reset}`,
429
+ `${c.dim}API Secret${c.reset} ${c.bold}${c.yellow}${result.api_secret}${c.reset}`,
430
+ `${c.dim}Referral Code${c.reset} ${c.bold}${result.store_referral_code}${c.reset}`
431
+ ]);
432
+ console.log(`\n${c.yellow} ⚠ Save your API Secret now - it cannot be retrieved later${c.reset}`);
433
+
434
+ // Platform-specific instructions
435
+ section('📦', `${platform.name} Setup`);
436
+
437
+ switch (platform.key) {
438
+ case 'woocommerce':
439
+ console.log(getWooCommerceInstructions(result.store_id, result.api_secret));
440
+ break;
441
+ case 'shopify':
442
+ console.log(getShopifyInstructions(result.store_id, result.api_secret));
443
+ console.log(getCustomInstructions(result.store_id, result.api_secret));
444
+ break;
445
+ case 'stripe':
446
+ console.log(getStripeInstructions(result.store_id, result.api_secret));
447
+ break;
448
+ case 'custom':
449
+ console.log(getCustomInstructions(result.store_id, result.api_secret));
450
+ break;
451
+ default:
452
+ console.log(getGenericInstructions(platformChoice, result.store_id, result.api_secret));
453
+ console.log(getCustomInstructions(result.store_id, result.api_secret));
454
+ }
455
+
456
+ // Affiliate signup
457
+ section('🎯', 'Affiliate Signup Page');
458
+ console.log('');
459
+ console.log(`${c.dim} Share this link for affiliates to register:${c.reset}`);
460
+ console.log('');
461
+ console.log(`${c.bold}${c.cyan} https://soundbip.com/member-login?store=${result.store_id}${c.reset}`);
462
+
463
+ if (platform.key === 'woocommerce') {
464
+ console.log('');
465
+ console.log(`${c.dim} Or use the shortcode on any WordPress page:${c.reset}`);
466
+ console.log(`${c.blue} [yesallofus_affiliate_signup]${c.reset}`);
467
+ }
468
+
469
+ // Resources
470
+ section('📚', 'Resources');
471
+ console.log('');
472
+ info('Documentation', 'https://soundbip.com/docs');
473
+ info('Support', 'support@soundbip.com');
474
+ info('Twitter', '@PayByBip');
475
+
476
+ console.log('');
477
+ console.log(`${c.gray} ════════════════════════════════════════════════════════════════════════════════════${c.reset}`);
478
+ console.log('');
479
+ console.log(`${c.bold}${c.green} 🚀 You're ready to pay affiliates instantly!${c.reset}`);
480
+ console.log(`${c.dim} Lagos to London, same speed. That's the future.${c.reset}`);
481
+ console.log('');
482
+
483
+ // Wallet connection
484
+ section('👛', 'Connect Your Wallet');
485
+ console.log('');
486
+ console.log(`${c.dim} Payout options:${c.reset}`);
487
+ console.log(`${c.white} • Google${c.reset} ${c.dim}- Easiest setup, automatic 24/7 payouts${c.reset}`);
488
+ console.log(`${c.green} • Xaman${c.reset} ${c.dim}- Manual approval via push notification${c.reset}`);
489
+ console.log(`${c.blue} • Crossmark${c.reset} ${c.dim}- Automatic 24/7 payouts (browser extension)${c.reset}`);
490
+ console.log('');
491
+
492
+ const dashboardUrl = `https://soundbip.com/partner-login?claim=${result.claim_token}`;
493
+
494
+ console.log(`${c.bold} Open this URL to connect your wallet and access your vendor dashboard:${c.reset}`);
495
+ console.log('');
496
+ console.log(`${c.cyan} ${dashboardUrl}${c.reset}`);
497
+ console.log('');
498
+ console.log(`${c.yellow} 💡 Use Chrome or Brave for best experience (required for Crossmark)${c.reset}`)
499
+
500
+ console.log('');
501
+ console.log(`${c.gray} ════════════════════════════════════════════════════════════════════════════════════${c.reset}`);
502
+ console.log('');
503
+ console.log(`${c.bold}${c.white} Thanks for choosing SoundBip! 🤝${c.reset}`);
504
+ console.log(`${c.dim} Questions? support@soundbip.com${c.reset}`);
505
+ console.log('');
506
+
507
+ rl.close();
508
+ }
509
+
510
+ main().catch(err => {
511
+ console.error(`${c.red}Error: ${err.message}${c.reset}`);
512
+ rl.close();
513
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "soundbip",
3
+ "version": "2.0.0",
4
+ "description": "CLI for SoundBip POS - instant affiliate commissions on XRPL",
5
+ "bin": {
6
+ "soundbip": "./index.js"
7
+ },
8
+ "keywords": [
9
+ "pos",
10
+ "point-of-sale",
11
+ "affiliate",
12
+ "xrpl",
13
+ "rlusd",
14
+ "commissions",
15
+ "payouts",
16
+ "crypto",
17
+ "instant-payments",
18
+ "woocommerce",
19
+ "shopify",
20
+ "ecommerce",
21
+ "soundbip"
22
+ ],
23
+ "author": "SoundBip <support@soundbip.com>",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/TokenCanvasIO/yesallofus-cli.git"
28
+ },
29
+ "homepage": "https://soundbip.com"
30
+ }