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.
- package/README.md +157 -0
- package/index.js +218 -0
- 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;
|