mayar 0.1.9 → 1.0.1

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.
@@ -1,8 +1,33 @@
1
1
  const api = require('../api');
2
2
  const ui = require('../ui');
3
- const { checkResp } = require('../util');
3
+ const { checkResp, readData, pagination, cursorFooter } = require('../util');
4
4
 
5
- const USAGE = 'Usage: mayar product <list|get|close|reopen|search|type>';
5
+ const USAGE = 'Usage: mayar product <list|search|type|get|close|reopen|status|transactions|create|edit|sort>';
6
+ const SORT_TYPES = ['generic_link', 'event', 'webinar', 'digital_product'];
7
+ const STATUS_ACTIONS = ['open', 'close', 'active', 'closed', 'unlisted'];
8
+
9
+ // Map user-friendly --type values to their v2 endpoint paths.
10
+ const CREATE_PATHS = {
11
+ ebook: '/hl/v2/products/digital-product/create',
12
+ digital: '/hl/v2/products/digital-product/create',
13
+ 'digital-product': '/hl/v2/products/digital-product/create',
14
+ event: '/hl/v2/products/event/create',
15
+ webinar: '/hl/v2/products/webinar/create',
16
+ generic: '/hl/v2/products/create',
17
+ 'generic-link': '/hl/v2/products/create',
18
+ 'payment-link': '/hl/v2/products/payment-link/create',
19
+ paymentlink: '/hl/v2/products/payment-link/create',
20
+ };
21
+
22
+ const EDIT_PATHS = {
23
+ ebook: (id) => `/hl/v2/products/digital-product/${id}/update`,
24
+ digital: (id) => `/hl/v2/products/digital-product/${id}/update`,
25
+ 'digital-product': (id) => `/hl/v2/products/digital-product/${id}/update`,
26
+ event: (id) => `/hl/v2/products/event/${id}/update`,
27
+ webinar: (id) => `/hl/v2/products/webinar/${id}/update`,
28
+ 'payment-link': (id) => `/hl/v2/products/payment-link/${id}/update`,
29
+ paymentlink: (id) => `/hl/v2/products/payment-link/${id}/update`,
30
+ };
6
31
 
7
32
  function renderList(body) {
8
33
  const data = (body && body.data) || [];
@@ -16,16 +41,16 @@ function renderList(body) {
16
41
  checkoutLink: p.linkPayment || '',
17
42
  }));
18
43
  ui.table(rows, ['id', 'name', 'type', 'amount', 'status', 'productLink', 'checkoutLink']);
19
- const m = body || {};
20
- process.stdout.write(ui.dim(`page ${m.page ?? '?'} / ${m.pageCount ?? '?'} · total ${m.total ?? data.length}`) + '\n');
44
+ cursorFooter(body, data.length);
21
45
  }
22
46
 
23
47
  async function run({ apiKey, flags, positional }) {
24
48
  const [sub, ...rest] = positional;
25
49
  switch (sub) {
26
50
  case 'list': {
27
- const res = await api.request('GET', '/hl/v1/product', {
28
- apiKey, query: { page: flags.page, pageSize: flags.pageSize },
51
+ const res = await api.request('GET', '/hl/v2/products', {
52
+ apiKey,
53
+ query: pagination(flags, { search: flags.search, type: flags.type, stock: flags.stock }),
29
54
  });
30
55
  checkResp(res);
31
56
  if (flags.json) return ui.jsonOut(res.body);
@@ -33,18 +58,17 @@ async function run({ apiKey, flags, positional }) {
33
58
  }
34
59
  case 'search': {
35
60
  if (!rest[0]) throw new Error('Usage: mayar product search <keyword>');
36
- const search = rest.join(' ');
37
- const res = await api.request('GET', '/hl/v1/product', {
38
- apiKey, query: { search, page: flags.page, pageSize: flags.pageSize },
61
+ const res = await api.request('GET', '/hl/v2/products', {
62
+ apiKey, query: pagination(flags, { search: rest.join(' ') }),
39
63
  });
40
64
  checkResp(res);
41
65
  if (flags.json) return ui.jsonOut(res.body);
42
66
  renderList(res.body); return;
43
67
  }
44
68
  case 'type': {
45
- if (!rest[0]) throw new Error('Usage: mayar product type <ebook|course|membership|saas|event|webinar|...>');
46
- const res = await api.request('GET', `/hl/v1/product/type/${encodeURIComponent(rest[0])}`, {
47
- apiKey, query: { page: flags.page, pageSize: flags.pageSize },
69
+ if (!rest[0]) throw new Error('Usage: mayar product type <type>');
70
+ const res = await api.request('GET', `/hl/v2/products/types/${encodeURIComponent(rest[0])}`, {
71
+ apiKey, query: pagination(flags, { search: flags.search }),
48
72
  });
49
73
  checkResp(res);
50
74
  if (flags.json) return ui.jsonOut(res.body);
@@ -52,17 +76,72 @@ async function run({ apiKey, flags, positional }) {
52
76
  }
53
77
  case 'get': {
54
78
  if (!rest[0]) throw new Error('Usage: mayar product get <id>');
55
- const res = await api.request('GET', `/hl/v1/product/${encodeURIComponent(rest[0])}`, { apiKey });
79
+ const res = await api.request('GET', `/hl/v2/products/${encodeURIComponent(rest[0])}`, { apiKey });
56
80
  checkResp(res); ui.jsonOut(res.body); return;
57
81
  }
58
- case 'close': {
59
- if (!rest[0]) throw new Error('Usage: mayar product close <id>');
60
- const res = await api.request('GET', `/hl/v1/product/close/${encodeURIComponent(rest[0])}`, { apiKey });
82
+ case 'transactions': {
83
+ if (!rest[0]) throw new Error('Usage: mayar product transactions <id>');
84
+ const res = await api.request('GET', `/hl/v2/products/${encodeURIComponent(rest[0])}/transactions`, {
85
+ apiKey,
86
+ query: pagination(flags, { status: flags.status, customerId: flags.customerId, fields: flags.fields }),
87
+ });
61
88
  checkResp(res); ui.jsonOut(res.body); return;
62
89
  }
90
+ case 'close':
63
91
  case 'reopen': {
64
- if (!rest[0]) throw new Error('Usage: mayar product reopen <id>');
65
- const res = await api.request('GET', `/hl/v1/product/open/${encodeURIComponent(rest[0])}`, { apiKey });
92
+ if (!rest[0]) throw new Error(`Usage: mayar product ${sub} <id>`);
93
+ const action = sub === 'reopen' ? 'open' : 'close';
94
+ const res = await api.request('POST', `/hl/v2/products/${encodeURIComponent(rest[0])}/${action}`, { apiKey });
95
+ checkResp(res); ui.jsonOut(res.body); return;
96
+ }
97
+ case 'status': {
98
+ if (!rest[0] || !rest[1]) throw new Error(`Usage: mayar product status <id> <${STATUS_ACTIONS.join('|')}>`);
99
+ if (!STATUS_ACTIONS.includes(rest[1])) {
100
+ throw new Error(`Invalid action "${rest[1]}". Must be one of: ${STATUS_ACTIONS.join(', ')}`);
101
+ }
102
+ const res = await api.request('POST', `/hl/v2/products/${encodeURIComponent(rest[0])}/${rest[1]}`, { apiKey });
103
+ checkResp(res); ui.jsonOut(res.body); return;
104
+ }
105
+ case 'create': {
106
+ const type = flags.type;
107
+ if (!type) throw new Error(`mayar product create requires --type <${Object.keys(CREATE_PATHS).join('|')}>`);
108
+ const path = CREATE_PATHS[type];
109
+ if (!path) throw new Error(`Unknown type "${type}". Valid: ${Object.keys(CREATE_PATHS).join(', ')}`);
110
+ const body = readData(flags.data);
111
+ if (!body) throw new Error('mayar product create requires --data <json|@file>');
112
+ const res = await api.request('POST', path, { apiKey, body });
113
+ checkResp(res); ui.jsonOut(res.body); return;
114
+ }
115
+ case 'sort': {
116
+ if (!rest[0]) throw new Error(`Usage: mayar product sort <${SORT_TYPES.join('|')}>`);
117
+ if (!SORT_TYPES.includes(rest[0])) {
118
+ throw new Error(`Invalid type "${rest[0]}". Must be one of: ${SORT_TYPES.join(', ')}`);
119
+ }
120
+ // Sort endpoint uses snake_case `starting_after` (unlike every other v2 list endpoint
121
+ // which uses camelCase `startingAfter`). Build the query directly rather than via
122
+ // pagination() to preserve the exact wire name.
123
+ const query = {};
124
+ const limit = flags.limit ?? flags.pageSize;
125
+ const after = flags.after ?? flags.startingAfter ?? flags['starting-after'];
126
+ if (limit) query.limit = limit;
127
+ if (after) query.starting_after = after;
128
+ const res = await api.request('POST', `/hl/v2/payment-links/sort/${encodeURIComponent(rest[0])}`, {
129
+ apiKey, query,
130
+ });
131
+ checkResp(res);
132
+ if (flags.json) return ui.jsonOut(res.body);
133
+ renderList(res.body); return;
134
+ }
135
+ case 'edit': {
136
+ if (!rest[0]) throw new Error('Usage: mayar product edit <id> --type <T> --data <json|@file>');
137
+ const type = flags.type;
138
+ if (!type) throw new Error(`mayar product edit requires --type <${Object.keys(EDIT_PATHS).join('|')}>`);
139
+ const pathFn = EDIT_PATHS[type];
140
+ if (!pathFn) throw new Error(`Unknown type "${type}". Valid: ${Object.keys(EDIT_PATHS).join(', ')}`);
141
+ const body = readData(flags.data);
142
+ if (!body) throw new Error('mayar product edit requires --data <json|@file>');
143
+ body.id = body.id || rest[0];
144
+ const res = await api.request('POST', pathFn(encodeURIComponent(rest[0])), { apiKey, body });
66
145
  checkResp(res); ui.jsonOut(res.body); return;
67
146
  }
68
147
  default:
@@ -2,12 +2,42 @@ const api = require('../api');
2
2
  const ui = require('../ui');
3
3
  const { checkResp } = require('../util');
4
4
 
5
+ const USAGE = 'Usage: mayar qrcode <amount> | mayar qrcode static | mayar qrcode channels';
6
+
5
7
  async function run({ apiKey, flags, positional }) {
6
- const [amountArg] = positional;
7
- if (!amountArg) throw new Error('Usage: mayar qrcode <amount>');
8
- const amount = Number(amountArg);
9
- if (!Number.isFinite(amount) || amount <= 0) throw new Error('Amount must be a positive number.');
10
- const res = await api.request('POST', '/hl/v1/qrcode/create', { apiKey, body: { amount } });
8
+ const [first] = positional;
9
+
10
+ if (first === 'static') {
11
+ const res = await api.request('GET', '/hl/v2/qr-codes/static', { apiKey });
12
+ checkResp(res);
13
+ if (flags.json) return ui.jsonOut(res.body);
14
+ const d = (res.body && res.body.data) || {};
15
+ if (d.url) process.stdout.write(`${ui.bold('Static QRIS:')} ${ui.cyan(d.url)}\n`);
16
+ else ui.jsonOut(res.body);
17
+ return;
18
+ }
19
+
20
+ if (first === 'channels') {
21
+ const res = await api.request('GET', '/hl/v2/payment-channels', { apiKey });
22
+ checkResp(res);
23
+ if (flags.json) return ui.jsonOut(res.body);
24
+ const body = res.body || {};
25
+ const channels = (body.data && body.data.channels) || body.data || [];
26
+ if (!Array.isArray(channels)) return ui.jsonOut(res.body);
27
+ const rows = channels.map((c) => ({
28
+ name: c.name || '',
29
+ type: c.type || '',
30
+ code: c.code || '',
31
+ status: c.status === true ? 'enabled' : (c.status === false ? 'disabled' : String(c.status ?? '')),
32
+ }));
33
+ ui.table(rows, ['name', 'type', 'code', 'status']);
34
+ return;
35
+ }
36
+
37
+ if (!first) throw new Error(USAGE);
38
+ const amount = Number(first);
39
+ if (!Number.isFinite(amount) || amount <= 0) throw new Error(USAGE);
40
+ const res = await api.request('POST', '/hl/v2/qr-codes/create', { apiKey, body: { amount } });
11
41
  checkResp(res);
12
42
  if (flags.json) return ui.jsonOut(res.body);
13
43
  const d = (res.body && res.body.data) || {};
@@ -1,8 +1,8 @@
1
1
  const api = require('../api');
2
2
  const ui = require('../ui');
3
- const { checkResp } = require('../util');
3
+ const { checkResp, readData, pagination, cursorFooter } = require('../util');
4
4
 
5
- const USAGE = 'Usage: mayar review <list>';
5
+ const USAGE = 'Usage: mayar review <list|stats|create|update|bulk-status|product|product-customer>';
6
6
 
7
7
  function fmtDate(v) {
8
8
  if (v == null) return '';
@@ -10,29 +10,101 @@ function fmtDate(v) {
10
10
  return String(v);
11
11
  }
12
12
 
13
+ function renderList(body) {
14
+ const data = (body && body.data) || [];
15
+ const rows = data.map((r) => ({
16
+ id: r.id,
17
+ rating: r.rating,
18
+ customer: (r.customer && (r.customer.name || r.customer.email)) || '',
19
+ message: r.message || '',
20
+ status: r.status || '',
21
+ createdAt: fmtDate(r.createdAt),
22
+ }));
23
+ ui.table(rows, ['id', 'rating', 'customer', 'message', 'status', 'createdAt']);
24
+ cursorFooter(body, data.length);
25
+ }
26
+
27
+ function renderStats(body) {
28
+ const d = (body && body.data) || {};
29
+ process.stdout.write(`${ui.bold('Total reviews:')} ${d.total ?? 0}\n`);
30
+ process.stdout.write(`${ui.bold('Average rating:')} ${d.average ?? '—'}\n`);
31
+ if (Array.isArray(d.stats) && d.stats.length) {
32
+ process.stdout.write('\n');
33
+ ui.table(
34
+ d.stats.map((s) => ({
35
+ rating: s.rating,
36
+ totalRating: s.totalRating,
37
+ percentage: typeof s.percentage === 'number' ? `${s.percentage}%` : String(s.percentage ?? ''),
38
+ })),
39
+ ['rating', 'totalRating', 'percentage'],
40
+ );
41
+ }
42
+ }
43
+
13
44
  async function run({ apiKey, flags, positional }) {
14
- const [sub] = positional;
45
+ const [sub, ...rest] = positional;
15
46
  switch (sub) {
16
47
  case undefined:
17
48
  case 'list': {
18
- const res = await api.request('GET', '/hl/v1/reviews', {
19
- apiKey, query: { page: flags.page, pageSize: flags.pageSize },
49
+ const res = await api.request('GET', '/hl/v2/reviews', {
50
+ apiKey,
51
+ query: pagination(flags, {
52
+ status: flags.status, paymentLinkId: flags.paymentLinkId, rating: flags.rating,
53
+ }),
20
54
  });
21
55
  checkResp(res);
22
56
  if (flags.json) return ui.jsonOut(res.body);
23
- const data = (res.body && res.body.data) || [];
24
- const rows = data.map((r) => ({
25
- id: r.id,
26
- rating: r.rating,
27
- customer: (r.customer && (r.customer.name || r.customer.email)) || '',
28
- message: r.message || '',
29
- status: r.status || '',
30
- createdAt: fmtDate(r.createdAt),
31
- }));
32
- ui.table(rows, ['id', 'rating', 'customer', 'message', 'status', 'createdAt']);
33
- const m = res.body || {};
34
- process.stdout.write(ui.dim(`page ${m.page ?? '?'} / ${m.pageCount ?? '?'} · total ${m.total ?? data.length}`) + '\n');
35
- return;
57
+ renderList(res.body); return;
58
+ }
59
+ case 'stats': {
60
+ const productId = rest[0];
61
+ const path = productId
62
+ ? `/hl/v2/products/${encodeURIComponent(productId)}/reviews/stats`
63
+ : '/hl/v2/merchants/reviews/stats';
64
+ const res = await api.request('GET', path, { apiKey });
65
+ checkResp(res);
66
+ if (flags.json) return ui.jsonOut(res.body);
67
+ renderStats(res.body); return;
68
+ }
69
+ case 'create': {
70
+ const body = readData(flags.data);
71
+ if (!body) throw new Error('mayar review create requires --data <json|@file> ({paymentLinkId,customerEmail,rating})');
72
+ const res = await api.request('POST', '/hl/v2/reviews/create', { apiKey, body });
73
+ checkResp(res); ui.jsonOut(res.body); return;
74
+ }
75
+ case 'update': {
76
+ if (!rest[0]) throw new Error('Usage: mayar review update <id> --data <json|@file>');
77
+ const body = readData(flags.data);
78
+ if (!body) throw new Error('mayar review update requires --data <json|@file>');
79
+ body.id = body.id || rest[0];
80
+ const res = await api.request('POST', `/hl/v2/reviews/${encodeURIComponent(rest[0])}/update`, { apiKey, body });
81
+ checkResp(res); ui.jsonOut(res.body); return;
82
+ }
83
+ case 'bulk-status':
84
+ case 'bulkstatus': {
85
+ const data = readData(flags.data);
86
+ if (!data) throw new Error('mayar review bulk-status requires --data <json|@file> (array of {id,status})');
87
+ const body = Array.isArray(data) ? { input: data } : data;
88
+ const res = await api.request('POST', '/hl/v2/reviews/bulk-status/update', { apiKey, body });
89
+ checkResp(res); ui.jsonOut(res.body); return;
90
+ }
91
+ case 'product': {
92
+ if (!rest[0]) throw new Error('Usage: mayar review product <paymentLinkId>');
93
+ const res = await api.request('GET', `/hl/v2/products/${encodeURIComponent(rest[0])}/reviews`, {
94
+ apiKey, query: pagination(flags, { rating: flags.rating, prioritizeMessage: flags.prioritizeMessage }),
95
+ });
96
+ checkResp(res);
97
+ if (flags.json) return ui.jsonOut(res.body);
98
+ renderList(res.body); return;
99
+ }
100
+ case 'product-customer':
101
+ case 'productcustomer': {
102
+ if (!rest[0]) throw new Error('Usage: mayar review product-customer <paymentLinkId> --customerId <id>');
103
+ if (!flags.customerId) throw new Error('mayar review product-customer requires --customerId <id>');
104
+ const res = await api.request('GET', `/hl/v2/products/${encodeURIComponent(rest[0])}/reviews/customer`, {
105
+ apiKey, query: { customerId: flags.customerId },
106
+ });
107
+ checkResp(res); ui.jsonOut(res.body); return;
36
108
  }
37
109
  default:
38
110
  throw new Error(USAGE);
@@ -0,0 +1,18 @@
1
+ const api = require('../api');
2
+ const ui = require('../ui');
3
+ const { checkResp } = require('../util');
4
+
5
+ const USAGE = 'Usage: mayar saas <activate|deactivate|verify> <licenseCode> <productId>';
6
+
7
+ async function run({ apiKey, flags, positional }) {
8
+ const [sub, licenseCode, productId] = positional;
9
+ if (!['activate', 'deactivate', 'verify'].includes(sub)) throw new Error(USAGE);
10
+ if (!licenseCode || !productId) throw new Error(USAGE);
11
+ // SaaS licensing lives under /saas/v2/license/* — NOT /hl/v2.
12
+ const res = await api.request('POST', `/saas/v2/license/${sub}`, {
13
+ apiKey, body: { licenseCode, productId },
14
+ });
15
+ checkResp(res); ui.jsonOut(res.body);
16
+ }
17
+
18
+ module.exports = { run };
@@ -0,0 +1,17 @@
1
+ const api = require('../api');
2
+ const ui = require('../ui');
3
+ const { checkResp } = require('../util');
4
+
5
+ const USAGE = 'Usage: mayar software verify <licenseCode> <productId>';
6
+
7
+ async function run({ apiKey, flags, positional }) {
8
+ const [sub, licenseCode, productId] = positional;
9
+ if (sub !== 'verify') throw new Error(USAGE);
10
+ if (!licenseCode || !productId) throw new Error(USAGE);
11
+ const res = await api.request('POST', '/software/v2/license/verify', {
12
+ apiKey, body: { licenseCode, productId },
13
+ });
14
+ checkResp(res); ui.jsonOut(res.body);
15
+ }
16
+
17
+ module.exports = { run };
@@ -1,8 +1,8 @@
1
1
  const api = require('../api');
2
2
  const ui = require('../ui');
3
- const { checkResp } = require('../util');
3
+ const { checkResp, pagination, cursorFooter } = require('../util');
4
4
 
5
- const USAGE = 'Usage: mayar tx <list|unpaid|daily>';
5
+ const USAGE = 'Usage: mayar tx <list|unpaid|daily|product>';
6
6
 
7
7
  function fmtDate(v) {
8
8
  if (v == null) return '';
@@ -22,40 +22,67 @@ function renderTx(body) {
22
22
  createdAt: fmtDate(t.createdAt),
23
23
  }));
24
24
  ui.table(rows, ['id', 'customer', 'amount', 'status', 'createdAt']);
25
+ cursorFooter(body, data.length);
25
26
  }
26
27
 
27
28
  async function run({ apiKey, flags, positional }) {
28
- const [sub] = positional;
29
+ const [sub, ...rest] = positional;
29
30
  switch (sub) {
30
31
  case undefined:
31
32
  case 'list':
32
33
  case 'paid': {
33
- const res = await api.request('GET', '/hl/v1/transactions', {
34
- apiKey, query: { page: flags.page, pageSize: flags.pageSize },
34
+ const res = await api.request('GET', '/hl/v2/transactions', {
35
+ apiKey,
36
+ query: pagination(flags, {
37
+ status: flags.status,
38
+ customerId: flags.customerId,
39
+ type: flags.type,
40
+ paymentLinkId: flags.paymentLinkId,
41
+ startAt: flags.startAt,
42
+ endAt: flags.endAt,
43
+ fields: flags.fields,
44
+ }),
35
45
  });
36
46
  checkResp(res);
37
47
  if (flags.json) return ui.jsonOut(res.body);
38
48
  renderTx(res.body); return;
39
49
  }
40
50
  case 'unpaid': {
41
- const res = await api.request('GET', '/hl/v1/transactions/unpaid', {
42
- apiKey, query: { page: flags.page, pageSize: flags.pageSize },
51
+ const res = await api.request('GET', '/hl/v2/transactions/unpaid', {
52
+ apiKey,
53
+ query: pagination(flags, {
54
+ status: flags.status,
55
+ customerId: flags.customerId,
56
+ paymentLinkId: flags.paymentLinkId,
57
+ startAt: flags.startAt,
58
+ endAt: flags.endAt,
59
+ fields: flags.fields,
60
+ }),
43
61
  });
44
62
  checkResp(res);
45
63
  if (flags.json) return ui.jsonOut(res.body);
46
64
  renderTx(res.body); return;
47
65
  }
48
66
  case 'daily': {
49
- const res = await api.request('GET', '/hl/v1/transactions/daily', { apiKey });
67
+ const res = await api.request('GET', '/hl/v2/transactions/daily', { apiKey });
50
68
  checkResp(res);
51
69
  if (flags.json) return ui.jsonOut(res.body);
52
70
  const d = (res.body && res.body.data) || {};
53
- if (d.date) process.stdout.write(`${ui.bold('Date:')} ${d.date}\n`);
54
- if (d.tpvCount != null) process.stdout.write(`${ui.bold('Total volume:')} ${Number(d.tpvCount).toLocaleString('id-ID')}\n`);
55
- if (d.trxCount != null) process.stdout.write(`${ui.bold('Transactions:')} ${d.trxCount}\n`);
56
- if (!d.date && !d.tpvCount && !d.trxCount) ui.jsonOut(res.body);
71
+ if (d.date) process.stdout.write(`${ui.bold('Date:')} ${d.date}\n`);
72
+ if (d.tpvCount != null) process.stdout.write(`${ui.bold('Total volume:')} ${Number(d.tpvCount).toLocaleString('id-ID')}\n`);
73
+ if (d.trxCount != null) process.stdout.write(`${ui.bold('Transactions:')} ${d.trxCount}\n`);
74
+ if (!d.date && d.tpvCount == null && d.trxCount == null) ui.jsonOut(res.body);
57
75
  return;
58
76
  }
77
+ case 'product': {
78
+ if (!rest[0]) throw new Error('Usage: mayar tx product <productId>');
79
+ const res = await api.request('GET', `/hl/v2/products/${encodeURIComponent(rest[0])}/transactions`, {
80
+ apiKey, query: pagination(flags, { status: flags.status, customerId: flags.customerId, fields: flags.fields }),
81
+ });
82
+ checkResp(res);
83
+ if (flags.json) return ui.jsonOut(res.body);
84
+ renderTx(res.body); return;
85
+ }
59
86
  default:
60
87
  throw new Error(USAGE);
61
88
  }
@@ -1,38 +1,71 @@
1
1
  const api = require('../api');
2
2
  const ui = require('../ui');
3
- const { checkResp } = require('../util');
3
+ const { checkResp, pagination, cursorFooter } = require('../util');
4
4
 
5
- const USAGE = 'Usage: mayar webhook <register|test|history>';
5
+ const USAGE = 'Usage: mayar webhook <register|test|history|new-history|retry>';
6
+
7
+ function fmtDate(v) {
8
+ if (v == null) return '';
9
+ if (typeof v === 'number') return new Date(v).toISOString();
10
+ return String(v);
11
+ }
12
+
13
+ function renderHistory(body) {
14
+ const data = (body && body.data) || [];
15
+ const rows = data.map((w) => ({
16
+ id: w.id, type: w.type, status: w.status,
17
+ urlDestination: w.urlDestination || '',
18
+ createdAt: fmtDate(w.createdAt),
19
+ }));
20
+ ui.table(rows, ['id', 'type', 'status', 'urlDestination', 'createdAt']);
21
+ cursorFooter(body, data.length);
22
+ }
6
23
 
7
24
  async function run({ apiKey, flags, positional }) {
8
25
  const [sub, ...rest] = positional;
9
26
  switch (sub) {
10
27
  case 'register': {
11
28
  if (!rest[0]) throw new Error('Usage: mayar webhook register <url>');
12
- const res = await api.request('GET', '/hl/v1/webhook/register', { apiKey, body: { urlHook: rest[0] } });
29
+ // v2: register is now POST /webhooks/update
30
+ const res = await api.request('POST', '/hl/v2/webhooks/update', {
31
+ apiKey, body: { urlHook: rest[0] },
32
+ });
13
33
  checkResp(res); ui.jsonOut(res.body); return;
14
34
  }
15
35
  case 'test': {
16
36
  if (!rest[0]) throw new Error('Usage: mayar webhook test <url>');
17
- const res = await api.request('POST', '/hl/v1/webhook/test', { apiKey, body: { urlHook: rest[0] } });
37
+ const res = await api.request('POST', '/hl/v2/webhooks/test', {
38
+ apiKey, body: { urlHook: rest[0] },
39
+ });
18
40
  checkResp(res); ui.jsonOut(res.body); return;
19
41
  }
20
42
  case 'history': {
21
- const res = await api.request('GET', '/hl/v1/webhook/history', {
22
- apiKey, query: { page: flags.page, pageSize: flags.pageSize },
43
+ const res = await api.request('GET', '/hl/v2/webhooks/history', {
44
+ apiKey,
45
+ query: pagination(flags, {
46
+ status: flags.status, type: flags.type,
47
+ urlDestination: flags.urlDestination,
48
+ startAt: flags.startAt, endAt: flags.endAt,
49
+ }),
23
50
  });
24
51
  checkResp(res);
25
52
  if (flags.json) return ui.jsonOut(res.body);
26
- const data = (res.body && res.body.data) || [];
27
- const rows = data.map((w) => ({
28
- id: w.id, type: w.type, status: w.status, urlDestination: w.urlDestination, createdAt: w.createdAt,
29
- }));
30
- ui.table(rows, ['id', 'type', 'status', 'urlDestination', 'createdAt']);
31
- return;
53
+ renderHistory(res.body); return;
54
+ }
55
+ case 'new-history':
56
+ case 'newhistory': {
57
+ const res = await api.request('GET', '/hl/v2/webhooks/new-history', {
58
+ apiKey, query: pagination(flags),
59
+ });
60
+ checkResp(res);
61
+ if (flags.json) return ui.jsonOut(res.body);
62
+ renderHistory(res.body); return;
32
63
  }
33
64
  case 'retry': {
34
65
  if (!rest[0]) throw new Error('Usage: mayar webhook retry <historyId>');
35
- const res = await api.request('GET', `/hl/v1/webhook/history/retry/${encodeURIComponent(rest[0])}`, { apiKey });
66
+ const res = await api.request('POST', '/hl/v2/webhooks/retry', {
67
+ apiKey, body: { webhookHistoryId: rest[0] },
68
+ });
36
69
  checkResp(res); ui.jsonOut(res.body); return;
37
70
  }
38
71
  default:
package/src/config.js CHANGED
@@ -40,4 +40,21 @@ function clear() {
40
40
  try { fs.unlinkSync(file); return true; } catch (_) { return false; }
41
41
  }
42
42
 
43
- module.exports = { load, save, clear, file, dir };
43
+ // Environment resolution -----------------------------------------------------
44
+ // NODE_ENV=development targets the *.mayar.club sandbox; anything else targets
45
+ // production *.mayar.id. Explicit env overrides always win.
46
+ function isDev() {
47
+ return process.env.NODE_ENV === 'development';
48
+ }
49
+
50
+ function apiBaseUrl() {
51
+ if (process.env.MAYAR_API_URL) return process.env.MAYAR_API_URL;
52
+ return isDev() ? 'https://api.mayar.club' : 'https://api.mayar.id';
53
+ }
54
+
55
+ function authBaseUrl() {
56
+ if (process.env.MAYAR_AUTH_URL) return process.env.MAYAR_AUTH_URL;
57
+ return isDev() ? 'https://auth.mayar.club' : 'https://auth.mayar.id';
58
+ }
59
+
60
+ module.exports = { load, save, clear, file, dir, isDev, apiBaseUrl, authBaseUrl };
package/src/util.js CHANGED
@@ -19,6 +19,33 @@ function readData(value) {
19
19
  return JSON.parse(value);
20
20
  }
21
21
 
22
+ // Build a v2 cursor pagination query from flags.
23
+ // v2 uses limit + startingAfter. --pageSize is accepted as an alias for --limit,
24
+ // --after is the short form of --starting-after.
25
+ function pagination(flags, extra) {
26
+ const q = { ...(extra || {}) };
27
+ const limit = flags.limit ?? flags.pageSize;
28
+ const after = flags.after ?? flags.startingAfter ?? flags['starting-after'];
29
+ if (limit !== undefined && limit !== '') q.limit = limit;
30
+ if (after !== undefined && after !== '') q.startingAfter = after;
31
+ return q;
32
+ }
33
+
34
+ // Print the cursor footer after a paginated list. Mayar v2 returns
35
+ // nextStartingAfter + hasMore on the response envelope.
36
+ function cursorFooter(body, count) {
37
+ const m = body || {};
38
+ const parts = [];
39
+ if (typeof m.pageSize === 'number' || typeof m.limit === 'number') {
40
+ parts.push(`limit ${m.pageSize ?? m.limit}`);
41
+ }
42
+ if (typeof count === 'number') parts.push(`showing ${count}`);
43
+ if (m.nextStartingAfter) parts.push(`next: --after ${m.nextStartingAfter}`);
44
+ if (m.hasMore === true) parts.push('hasMore');
45
+ if (!parts.length) return;
46
+ process.stdout.write(ui.dim(parts.join(' · ')) + '\n');
47
+ }
48
+
22
49
  function maybeJson(flags, body, fallback) {
23
50
  if (flags.json) {
24
51
  ui.jsonOut(body);
@@ -31,4 +58,4 @@ function maybeJson(flags, body, fallback) {
31
58
  return false;
32
59
  }
33
60
 
34
- module.exports = { checkResp, readData, maybeJson };
61
+ module.exports = { checkResp, readData, maybeJson, pagination, cursorFooter };