amcoin-sdk 1.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 (3) hide show
  1. package/README.md +157 -0
  2. package/index.js +218 -0
  3. package/package.json +9 -0
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # amcoin-sdk
2
+
3
+ Официальный JavaScript SDK для [AmCoin Merchant API](https://api.am-coin.online).
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ npm install amcoin-sdk
9
+ ```
10
+
11
+ ## Быстрый старт
12
+
13
+ ```javascript
14
+ const AmCoin = require('amcoin-sdk');
15
+ const amcoin = new AmCoin('AC_ваш_ключ');
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Баланс
21
+
22
+ ```javascript
23
+ const data = await amcoin.getBalance();
24
+ console.log(data.balance); // 15000
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Инвойсы
30
+
31
+ ```javascript
32
+ // Создать
33
+ const inv = await amcoin.invoice.create({
34
+ amount: 500,
35
+ description: 'Подписка Pro',
36
+ externalId: 'order_123',
37
+ expiresInMinutes: 60,
38
+ });
39
+ console.log(inv.invoiceId); // INV1A2B3C
40
+ console.log(inv.payLink); // t.me/AmCoinMerchantBot?start=pay_...
41
+
42
+ // Получить
43
+ const inv = await amcoin.invoice.get('INV1A2B3C');
44
+ console.log(inv.status); // pending | paid | expired | cancelled
45
+
46
+ // Список
47
+ const list = await amcoin.invoice.list({ status: 'pending', limit: 10 });
48
+
49
+ // Отменить
50
+ await amcoin.invoice.cancel('INV1A2B3C');
51
+
52
+ // Ждать оплаты (polling)
53
+ const paid = await amcoin.invoice.waitForPayment('INV1A2B3C', {
54
+ timeout: 10 * 60 * 1000, // 10 минут
55
+ interval: 3000, // проверять каждые 3 сек
56
+ });
57
+ console.log('Оплачено!', paid.paidBy);
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Чеки
63
+
64
+ ```javascript
65
+ // Создать чек на 100 AC × 10 активаций
66
+ const check = await amcoin.check.create({ amount: 100, activations: 10 });
67
+ console.log(check.checkLink); // t.me/AmCoinAppbot?start=check_...
68
+
69
+ // Получить статус
70
+ const ch = await amcoin.check.get('A1B2C3D4');
71
+ console.log(ch.remainingActivations);
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Переводы
77
+
78
+ ```javascript
79
+ await amcoin.transfer({
80
+ userId: 123456789,
81
+ amount: 100,
82
+ comment: 'Бонус за регистрацию',
83
+ });
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Пользователь
89
+
90
+ ```javascript
91
+ const user = await amcoin.getUser(123456789);
92
+ console.log(user.username, user.balance, user.isBanned);
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Транзакции
98
+
99
+ ```javascript
100
+ const txns = await amcoin.getTransactions({ type: 'deposit', limit: 20 });
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Вебхуки
106
+
107
+ ### Установить URL
108
+ ```javascript
109
+ await amcoin.setWebhook('https://мой-сайт.com/webhook');
110
+ await amcoin.removeWebhook(); // удалить
111
+ ```
112
+
113
+ ### Слушать вебхуки локально
114
+ ```javascript
115
+ amcoin.listenWebhook({ port: 3000, path: '/webhook' });
116
+
117
+ amcoin.on('invoice_paid', (data) => {
118
+ console.log('Оплачен:', data.invoiceId, 'на', data.amount, 'AC');
119
+ });
120
+
121
+ amcoin.on('payment', (data) => {
122
+ console.log('Свободная оплата:', data.amount, 'AC от', data.userId);
123
+ });
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Обработка ошибок
129
+
130
+ ```javascript
131
+ const { AmCoinError } = require('amcoin-sdk');
132
+
133
+ try {
134
+ await amcoin.transfer({ userId: 123, amount: 99999 });
135
+ } catch(e) {
136
+ if (e instanceof AmCoinError) {
137
+ console.log(e.message); // Недостаточно средств
138
+ console.log(e.code); // 400
139
+ }
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Опции
146
+
147
+ ```javascript
148
+ const amcoin = new AmCoin('AC_ключ', {
149
+ timeout: 15000, // таймаут запроса в мс (по умолч. 10000)
150
+ });
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Лицензия
156
+
157
+ MIT
package/index.js ADDED
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+
3
+ const https = require('https');
4
+ const http = require('http');
5
+ const { EventEmitter } = require('events');
6
+
7
+ const BASE_URL = 'https://api.am-coin.online/api';
8
+
9
+ class AmCoinError extends Error {
10
+ constructor(message, code) {
11
+ super(message);
12
+ this.name = 'AmCoinError';
13
+ this.code = code || null;
14
+ }
15
+ }
16
+
17
+ class AmCoin extends EventEmitter {
18
+ constructor(apiKey, options) {
19
+ super();
20
+ if (!apiKey) throw new AmCoinError('API ключ обязателен.');
21
+ this._key = apiKey;
22
+ this._timeout = (options && options.timeout) || 10000;
23
+ this._webhook = null;
24
+
25
+ this.invoice = new InvoiceModule(this);
26
+ this.check = new CheckModule(this);
27
+ }
28
+
29
+ // ─── HTTP ───────────────────────────────────────────────
30
+ _request(method, path, body) {
31
+ return new Promise((resolve, reject) => {
32
+ var url = BASE_URL + path;
33
+ var payload = body ? JSON.stringify(body) : null;
34
+ var opts = {
35
+ method : method,
36
+ headers : {
37
+ 'X-Api-Key' : this._key,
38
+ 'Content-Type': 'application/json',
39
+ 'Accept' : 'application/json',
40
+ },
41
+ };
42
+ if (payload) opts.headers['Content-Length'] = Buffer.byteLength(payload);
43
+
44
+ var lib = url.startsWith('https') ? https : http;
45
+ var req = lib.request(url, opts, (res) => {
46
+ var data = '';
47
+ res.on('data', (c) => data += c);
48
+ res.on('end', () => {
49
+ try {
50
+ var json = JSON.parse(data);
51
+ if (!json.success) return reject(new AmCoinError(json.error || 'Ошибка API', res.statusCode));
52
+ resolve(json);
53
+ } catch(e) {
54
+ reject(new AmCoinError('Неверный ответ сервера'));
55
+ }
56
+ });
57
+ });
58
+
59
+ req.setTimeout(this._timeout, () => {
60
+ req.destroy();
61
+ reject(new AmCoinError('Таймаут запроса'));
62
+ });
63
+
64
+ req.on('error', (e) => reject(new AmCoinError(e.message)));
65
+ if (payload) req.write(payload);
66
+ req.end();
67
+ });
68
+ }
69
+
70
+ _get(path) { return this._request('GET', path, null); }
71
+ _post(path, body) { return this._request('POST', path, body); }
72
+
73
+ // ─── BALANCE ────────────────────────────────────────────
74
+ async getBalance() {
75
+ return this._get('/balance');
76
+ }
77
+
78
+ // ─── TRANSFER ───────────────────────────────────────────
79
+ async transfer({ userId, amount, comment } = {}) {
80
+ if (!userId) throw new AmCoinError('userId обязателен');
81
+ if (!amount || amount <= 0) throw new AmCoinError('amount должен быть > 0');
82
+ return this._post('/transfer', { userId, amount, comment });
83
+ }
84
+
85
+ // ─── USER ───────────────────────────────────────────────
86
+ async getUser(userId) {
87
+ if (!userId) throw new AmCoinError('userId обязателен');
88
+ return this._get('/user/' + userId);
89
+ }
90
+
91
+ // ─── TRANSACTIONS ───────────────────────────────────────
92
+ async getTransactions({ type, limit, offset } = {}) {
93
+ var q = [];
94
+ if (type) q.push('type=' + type);
95
+ if (limit) q.push('limit=' + limit);
96
+ if (offset) q.push('offset=' + offset);
97
+ return this._get('/transactions' + (q.length ? '?' + q.join('&') : ''));
98
+ }
99
+
100
+ // ─── WEBHOOK ────────────────────────────────────────────
101
+ async setWebhook(url) {
102
+ return this._post('/webhook/set', { url: url || '' });
103
+ }
104
+
105
+ async removeWebhook() {
106
+ return this._post('/webhook/set', { url: '' });
107
+ }
108
+
109
+ /**
110
+ * Запускает локальный HTTP сервер для приёма вебхуков.
111
+ * Эмитит события: 'invoice_paid', 'payment'
112
+ *
113
+ * amcoin.listenWebhook({ port: 3000, path: '/webhook' })
114
+ * amcoin.on('invoice_paid', (data) => console.log(data))
115
+ */
116
+ listenWebhook({ port, path: whPath } = {}) {
117
+ whPath = whPath || '/webhook';
118
+ port = port || 3000;
119
+
120
+ var server = http.createServer((req, res) => {
121
+ if (req.method !== 'POST' || req.url !== whPath) {
122
+ res.writeHead(404); res.end(); return;
123
+ }
124
+ var data = '';
125
+ req.on('data', (c) => data += c);
126
+ req.on('end', () => {
127
+ try {
128
+ var json = JSON.parse(data);
129
+ if (json.event) this.emit(json.event, json);
130
+ this.emit('update', json);
131
+ } catch(e) {}
132
+ res.writeHead(200); res.end('OK');
133
+ });
134
+ });
135
+
136
+ server.listen(port, () => {
137
+ console.log('[AmCoin] Webhook слушает на порту ' + port + whPath);
138
+ });
139
+
140
+ this._webhook = server;
141
+ return server;
142
+ }
143
+
144
+ stopWebhook() {
145
+ if (this._webhook) { this._webhook.close(); this._webhook = null; }
146
+ }
147
+ }
148
+
149
+ // ─── INVOICE MODULE ─────────────────────────────────────────
150
+ class InvoiceModule {
151
+ constructor(client) { this._c = client; }
152
+
153
+ async create({ amount, description, externalId, expiresInMinutes } = {}) {
154
+ if (!amount || amount <= 0) throw new AmCoinError('amount должен быть > 0');
155
+ return this._c._post('/invoice/create', { amount, description, externalId, expiresInMinutes });
156
+ }
157
+
158
+ async get(id) {
159
+ if (!id) throw new AmCoinError('id обязателен');
160
+ return this._c._get('/invoice/' + id);
161
+ }
162
+
163
+ async cancel(id) {
164
+ if (!id) throw new AmCoinError('id обязателен');
165
+ return this._c._post('/invoice/' + id + '/cancel');
166
+ }
167
+
168
+ async list({ status, limit, offset } = {}) {
169
+ var q = [];
170
+ if (status) q.push('status=' + status);
171
+ if (limit) q.push('limit=' + limit);
172
+ if (offset) q.push('offset=' + offset);
173
+ return this._c._get('/invoices' + (q.length ? '?' + q.join('&') : ''));
174
+ }
175
+
176
+ /**
177
+ * Ждёт оплаты инвойса — резолвится когда статус станет 'paid'.
178
+ * Опционально: timeout в мс (по умолч. 30 минут), interval в мс (по умолч. 3 сек)
179
+ */
180
+ waitForPayment(id, { timeout, interval } = {}) {
181
+ timeout = timeout || 30 * 60 * 1000;
182
+ interval = interval || 3000;
183
+ return new Promise((resolve, reject) => {
184
+ var elapsed = 0;
185
+ var timer = setInterval(async () => {
186
+ elapsed += interval;
187
+ if (elapsed >= timeout) {
188
+ clearInterval(timer);
189
+ return reject(new AmCoinError('Таймаут ожидания оплаты'));
190
+ }
191
+ try {
192
+ var inv = await this.get(id);
193
+ if (inv.status === 'paid') { clearInterval(timer); resolve(inv); }
194
+ if (inv.status === 'expired') { clearInterval(timer); reject(new AmCoinError('Инвойс истёк')); }
195
+ if (inv.status === 'cancelled') { clearInterval(timer); reject(new AmCoinError('Инвойс отменён')); }
196
+ } catch(e) { clearInterval(timer); reject(e); }
197
+ }, interval);
198
+ });
199
+ }
200
+ }
201
+
202
+ // ─── CHECK MODULE ────────────────────────────────────────────
203
+ class CheckModule {
204
+ constructor(client) { this._c = client; }
205
+
206
+ async create({ amount, activations } = {}) {
207
+ if (!amount || amount <= 0) throw new AmCoinError('amount должен быть > 0');
208
+ return this._c._post('/check/create', { amount, activations });
209
+ }
210
+
211
+ async get(id) {
212
+ if (!id) throw new AmCoinError('id обязателен');
213
+ return this._c._get('/check/' + id);
214
+ }
215
+ }
216
+
217
+ module.exports = AmCoin;
218
+ module.exports.AmCoinError = AmCoinError;
package/package.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "amcoin-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official AmCoin Merchant API SDK",
5
+ "main": "index.js",
6
+ "license": "MIT",
7
+ "keywords": ["amcoin", "payment", "telegram", "crypto"],
8
+ "author": "AmCoin"
9
+ }