@rewloy/node 0.1.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/CHANGELOG.md +34 -0
- package/LICENSE +21 -0
- package/README.md +453 -0
- package/dist/client.d.ts +120 -0
- package/dist/client.js +404 -0
- package/dist/errors.d.ts +73 -0
- package/dist/errors.js +81 -0
- package/dist/generated/methods.d.ts +3297 -0
- package/dist/generated/methods.js +3793 -0
- package/dist/generated/operations.d.ts +12 -0
- package/dist/generated/operations.js +368 -0
- package/dist/generated/types.d.ts +13164 -0
- package/dist/generated/types.js +3 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +16 -0
- package/dist/sse.d.ts +68 -0
- package/dist/sse.js +251 -0
- package/dist/types.d.ts +104 -0
- package/dist/types.js +5 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/dist/webhooks.d.ts +91 -0
- package/dist/webhooks.js +98 -0
- package/package.json +56 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Değişiklik günlüğü / Changelog
|
|
2
|
+
|
|
3
|
+
Bu kütüphanenin sürümleri. API'nin kendi değişiklikleri:
|
|
4
|
+
https://rewloy.com/gelistiriciler/degisiklikler
|
|
5
|
+
|
|
6
|
+
This library's releases. The API's own changes are listed at the link above.
|
|
7
|
+
|
|
8
|
+
## 0.1.0 (2026-10-04)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
İlk önizleme, npm'de ilk sürüm. Rewloy API 1.0.0'a göre üretildi: 237 işlem.
|
|
12
|
+
|
|
13
|
+
First preview and first npm release, generated from Rewloy API 1.0.0 (237 operations):
|
|
14
|
+
|
|
15
|
+
- **Client.** `new Rewloy({ apiKey } | { staffSession, merchant } | { holderSession })`
|
|
16
|
+
with `baseUrl`, `timeoutMs`, `maxRetries`, `fetch` and `userAgent`.
|
|
17
|
+
- **Methods.** One method per operation, named by its operationId, typed
|
|
18
|
+
from the OpenAPI document. `request()` returns the whole answer (`status`,
|
|
19
|
+
`requestId`, `mode`, `replayed`).
|
|
20
|
+
- **Retries** on network errors, timeouts, 429 and 502–504, with
|
|
21
|
+
exponential backoff, jitter and `Retry-After`. Only safe requests are
|
|
22
|
+
retried.
|
|
23
|
+
- **`Idempotency-Key`** for till actions and campaigns: generated when
|
|
24
|
+
omitted, reused across retries.
|
|
25
|
+
- **Pagination** with `paginate()`.
|
|
26
|
+
- **Server-sent events** with `stream()`, `liveFeed()` and
|
|
27
|
+
`holderCardEvents()`, with reconnection and `Last-Event-ID`.
|
|
28
|
+
- **Webhooks:** `verifyWebhook()` and `signWebhook()`.
|
|
29
|
+
- **Errors:** `RewloyError`, `RateLimitError`, `RewloyConnectionError` and
|
|
30
|
+
`RewloyTimeoutError`.
|
|
31
|
+
- **Deprecations:** a `DeprecationWarning` per deprecated operation, and
|
|
32
|
+
`@deprecated` in the types.
|
|
33
|
+
- **Regeneration:** `npm run generate`, plus a daily workflow that opens a
|
|
34
|
+
pull request when the live document changes.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rewloy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# Rewloy Node.js
|
|
2
|
+
|
|
3
|
+
**Rewloy API'nin resmî Node.js ve TypeScript kütüphanesi.**
|
|
4
|
+
|
|
5
|
+
> **Durum: önizleme (0.x), npm'de yayımlandı. API kararlı; kütüphane arayüzü 1.0'a kadar değişebilir.**
|
|
6
|
+
|
|
7
|
+
[Rewloy](https://rewloy.com), işletmelerin dijital sadakat kartlarını
|
|
8
|
+
müşterinin telefonuna koyar. Kart türleri damga, puan, VIP, cashback, hediye
|
|
9
|
+
kartı, kupon ve indirimdir:
|
|
10
|
+
- iPhone'da Apple Cüzdan;
|
|
11
|
+
- Android'de Rewloy Cüzdan ve Google Cüzdan;
|
|
12
|
+
- her yerde web kartı.
|
|
13
|
+
|
|
14
|
+
Kasada QR okutulur; bakiye, ödül ve kampanyalar kartın kendisinde güncellenir.
|
|
15
|
+
Panelde yapılabilen her şey [Rewloy API v1](https://rewloy.com/gelistiriciler)
|
|
16
|
+
ile de yapılabilir; bu kütüphane onu Node.js'ten kullanır:
|
|
17
|
+
|
|
18
|
+
- **Tam tipli.** API'nin her işlemi, `operationId` adıyla bir metottur.
|
|
19
|
+
Parametreler, gövdeler ve yanıtlar OpenAPI belgesinden
|
|
20
|
+
([`openapi.json`](https://app.rewloy.com/v1/openapi.json)) üretilen
|
|
21
|
+
tiplerle gelir. CI belgeyi her gün okur ve değişince yeniden üretir.
|
|
22
|
+
- **Bağımlılıksız.** Node 22 ve üstü; yerleşik `fetch` ve `node:crypto`.
|
|
23
|
+
- **Güvenli tekrar.** Geçici hatalarda ölçülü yeniden deneme; kasa işleminde
|
|
24
|
+
ve kampanyada `Idempotency-Key`.
|
|
25
|
+
- **Ötesi:** sayfalama, canlı akış (SSE), webhook imzası doğrulama,
|
|
26
|
+
kullanımdan kalkma uyarıları.
|
|
27
|
+
|
|
28
|
+
## Kurulum
|
|
29
|
+
|
|
30
|
+
Node 22 ya da üstü gerekir:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
npm install @rewloy/node
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Başlarken
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { Rewloy } from '@rewloy/node';
|
|
40
|
+
|
|
41
|
+
const rewloy = new Rewloy({ apiKey: process.env.REWLOY_API_KEY! });
|
|
42
|
+
|
|
43
|
+
const kart = await rewloy.getPass({ params: { serial: 'ABCD-EFGH-JKLM' } });
|
|
44
|
+
console.log(kart.type, kart.balance, kart.rewardReady);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Her işlem, adı `operationId` olan bir metottur
|
|
48
|
+
([API referansı](https://rewloy.com/gelistiriciler/api)). Tek bir argüman alır,
|
|
49
|
+
işlemin gerektirdikleriyle:
|
|
50
|
+
- `params`: adresteki parametreler (`{serial}`, `{id}`…);
|
|
51
|
+
- `query`: sorgu parametreleri;
|
|
52
|
+
- `body`: JSON gövde;
|
|
53
|
+
- `merchant`: `Rewloy-Merchant` başlığı;
|
|
54
|
+
- `idempotencyKey`: `Idempotency-Key` başlığı (kasa işlemi ve kampanya);
|
|
55
|
+
- `signal`, `timeoutMs`, `maxRetries`.
|
|
56
|
+
|
|
57
|
+
Metot yanıttaki `data`yı döndürür. Sayfalı listelerde `{ data, meta }`,
|
|
58
|
+
gövdesiz yanıtta (`204`) `undefined`, dosyada (QR, harita, CSV, `.pkpass`) bir
|
|
59
|
+
`Blob` döner.
|
|
60
|
+
|
|
61
|
+
Tipler de dışa açıktır: `IssuePassBody`, `GetPassData`, `ListCustomersItem`,
|
|
62
|
+
`ErrorCode`… Hepsi işlem adıyla `Operations` içinde de bulunur.
|
|
63
|
+
|
|
64
|
+
### Kimlik
|
|
65
|
+
|
|
66
|
+
| İstemci | Ne için |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `new Rewloy({ apiKey: 'rwk_…' })` | API anahtarı: kasa, e-ticaret, kendi sisteminiz |
|
|
69
|
+
| `new Rewloy({ staffSession: 'rws_…', merchant })` | ekip oturumu: bir kişinin işletme uygulaması |
|
|
70
|
+
| `new Rewloy({ holderSession: 'rwh_…' })` | kart sahibi oturumu: Rewloy Cüzdan gibi müşteri uygulamaları |
|
|
71
|
+
| `new Rewloy()` | kimlik istemeyen uç noktalar: giriş, katılım, kod |
|
|
72
|
+
|
|
73
|
+
`merchant`, ekip oturumu birden fazla işletmede koltuk taşıyorsa hangi işletme
|
|
74
|
+
için çalıştığını söyler (`Rewloy-Merchant`). Her çağrıda `merchant` ile
|
|
75
|
+
değiştirilebilir. Oturumlar kimliksiz bir istemciyle açılır:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const { token, mfaRequired } = await new Rewloy().login({ body: { email, password } });
|
|
79
|
+
const ekip = new Rewloy({ staffSession: token, merchant: isletmeId });
|
|
80
|
+
if (mfaRequired) await ekip.proveMfa({ body: { code: '123456' } });
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Bir işlem istemcinin kimlik türünü kabul etmiyor ama kimliksiz de çalışıyorsa
|
|
84
|
+
(örneğin `login`), istemci onu kimliksiz çağırır. API, işlemin kabul etmediği
|
|
85
|
+
bir kimliği reddeder (`CREDENTIAL_NOT_ALLOWED`).
|
|
86
|
+
|
|
87
|
+
Diğer seçenekler:
|
|
88
|
+
- `baseUrl` (varsayılan `https://app.rewloy.com`);
|
|
89
|
+
- `timeoutMs` (60000);
|
|
90
|
+
- `maxRetries` (2);
|
|
91
|
+
- `fetch`: kendi `fetch`iniz;
|
|
92
|
+
- `userAgent`: gönderilen `User-Agent`a eklenir, örneğin `"KasaPOS/4.2"`.
|
|
93
|
+
|
|
94
|
+
## Kart vermek ve kasada işlem
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const { serial, cardUrl } = await rewloy.issuePass({
|
|
98
|
+
body: { programId, email: 'ayse@ornek.com', firstName: 'Ayşe', kvkkConsent: true },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const sonuc = await rewloy.passAction({
|
|
102
|
+
params: { serial },
|
|
103
|
+
body: { action: 'earn-stamps', locationId, count: 1 },
|
|
104
|
+
idempotencyKey: `fis-${fisNo}`,
|
|
105
|
+
});
|
|
106
|
+
if (sonuc.duplicate) console.log('Bu fiş zaten işlenmiş');
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`passAction` ve `sendCampaign` bir `Idempotency-Key` ister. Verilmezse
|
|
110
|
+
kütüphane bir UUID üretir ve aynı çağrının her denemesinde aynısını gönderir.
|
|
111
|
+
Kasada fiş numarası gibi kendi anahtarınızı vermek daha iyidir: uygulama
|
|
112
|
+
çöküp yeniden başlasa bile aynı fiş ikinci kez işlenmez, aynı anahtarla tekrar
|
|
113
|
+
ilk sonucu `duplicate: true` ile döndürür.
|
|
114
|
+
|
|
115
|
+
## Sayfalama
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
for await (const musteri of rewloy.paginate('listCustomers', { query: { consent: 'yes', limit: 200 } })) {
|
|
119
|
+
console.log(musteri.displayName, musteri.email);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`paginate` sayfalı her listeyi (`page`/`limit` ve `meta`) öğe öğe dolaşır ve
|
|
124
|
+
son sayfada durur. Tek bir sayfa için metodun kendisi yeter:
|
|
125
|
+
`const { data, meta } = await rewloy.listCustomers({ query: { page: 2 } })`.
|
|
126
|
+
|
|
127
|
+
## Canlı akış
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const ac = new AbortController();
|
|
131
|
+
for await (const olay of rewloy.liveFeed({ signal: ac.signal })) {
|
|
132
|
+
if (olay.event === 'event') {
|
|
133
|
+
const { kind, location, program, delta, unit, name } = JSON.parse(olay.data);
|
|
134
|
+
console.log(kind, location, program, delta, unit, name);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`liveFeed` (işletmenin tezgâh akışı) ve `holderCardEvents` (kart sahibinin
|
|
140
|
+
kartındaki değişiklik) sunucu olayları (`text/event-stream`) yayınlar.
|
|
141
|
+
`rewloy.stream('liveFeed', argüman)` aynı işi görür. Her olay `event`, `data`
|
|
142
|
+
ve `id` taşır.
|
|
143
|
+
|
|
144
|
+
- **Yeniden bağlanma.** Bağlantı koparsa akış kendiliğinden yeniden bağlanır:
|
|
145
|
+
sunucunun `retry:` süresi kadar bekler, bir olay `id` taşıdıysa
|
|
146
|
+
`Last-Event-ID` gönderir. `reconnect: false` bunu kapatır.
|
|
147
|
+
- **Sessiz bağlantı.** API 25 saniyede bir `: hb` gönderir; 60 saniye hiç veri
|
|
148
|
+
gelmezse bağlantı kopmuş sayılır (`idleTimeoutMs`).
|
|
149
|
+
- **Durdurmak:** `signal`, döngüden `break` ya da `akis.close()`.
|
|
150
|
+
- **Bitiren hatalar.** Yeniden bağlanmanın düzeltemeyeceği bir hata (`401`,
|
|
151
|
+
`403`, `404`) akışı `RewloyError` ile bitirir.
|
|
152
|
+
|
|
153
|
+
## Webhook doğrulama
|
|
154
|
+
|
|
155
|
+
Rewloy her teslimi imzalar:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
Rewloy-Signature: t=<unix saniye>,v1=<hex HMAC-SHA256(sır, "<t>.<ham gövde>")>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`verifyWebhook` imzayı **ham gövdeyle** ve webhook oluşturulurken bir kez
|
|
162
|
+
gösterilen sırla (`whsec_…`) doğrular:
|
|
163
|
+
- karşılaştırmayı sabit sürede yapar;
|
|
164
|
+
- `t` şimdiden 300 saniyeden (`toleranceSeconds`) uzaksa reddeder;
|
|
165
|
+
- gövdeyi ayrıştırılmış olarak döndürür.
|
|
166
|
+
|
|
167
|
+
Tutmazsa `WebhookSignatureError` atar: 400 ile yanıtlayın ve hiçbir işlem
|
|
168
|
+
yapmayın. Gövde mutlaka ham olmalıdır. JSON olarak ayrıştırılıp yeniden yazılan
|
|
169
|
+
bir gövde imzayı tutturmaz.
|
|
170
|
+
|
|
171
|
+
Express:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import express from 'express';
|
|
175
|
+
import { verifyWebhook, WebhookSignatureError } from '@rewloy/node';
|
|
176
|
+
|
|
177
|
+
const app = express();
|
|
178
|
+
app.post('/rewloy/webhook', express.raw({ type: 'application/json' }), (req, res) => {
|
|
179
|
+
let olay;
|
|
180
|
+
try {
|
|
181
|
+
olay = verifyWebhook({
|
|
182
|
+
payload: req.body,
|
|
183
|
+
header: req.get('Rewloy-Signature'),
|
|
184
|
+
secret: process.env.REWLOY_WEBHOOK_SECRET!,
|
|
185
|
+
});
|
|
186
|
+
} catch (err) {
|
|
187
|
+
if (err instanceof WebhookSignatureError) return res.sendStatus(400);
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
190
|
+
// Rewloy-Delivery bir teslimin her denemesinde aynıdır: işlediyseniz atlayın.
|
|
191
|
+
if (dahaOnceIslendi(req.get('Rewloy-Delivery'))) return res.sendStatus(200);
|
|
192
|
+
if (olay.type === 'pass.activity') {
|
|
193
|
+
console.log(olay.data.card, olay.data.kind, olay.data.delta);
|
|
194
|
+
}
|
|
195
|
+
res.sendStatus(200);
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Fastify (gövde yalnız bu yolda ham kalsın diye ayrı bir kapsamda):
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
app.register(async (scope) => {
|
|
203
|
+
scope.addContentTypeParser('application/json', { parseAs: 'buffer' }, (_req, body, done) => done(null, body));
|
|
204
|
+
scope.post('/rewloy/webhook', async (req, reply) => {
|
|
205
|
+
try {
|
|
206
|
+
const olay = verifyWebhook({
|
|
207
|
+
payload: req.body as Buffer,
|
|
208
|
+
header: req.headers['rewloy-signature'],
|
|
209
|
+
secret: process.env.REWLOY_WEBHOOK_SECRET!,
|
|
210
|
+
});
|
|
211
|
+
// …
|
|
212
|
+
return reply.code(200).send();
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (err instanceof WebhookSignatureError) return reply.code(400).send();
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Başlıklar:
|
|
222
|
+
- `Rewloy-Event`: olay türü (`pass.issued`, `pass.activity`, `pass.voided`,
|
|
223
|
+
`webhook.test`); gövdedeki `type` ile aynı.
|
|
224
|
+
- `Rewloy-Delivery`: teslimin kimliği. Teslim "en az bir kez"dir: çift gelen
|
|
225
|
+
teslimi bununla ayıklayın.
|
|
226
|
+
|
|
227
|
+
Gövde kişinin iletişim bilgisini taşımaz; kişiyi `customer_id` ile API'den
|
|
228
|
+
okuyun. 2xx dışı bir yanıt yaklaşık 45 saat boyunca 8 kez yeniden denenir ve
|
|
229
|
+
her deneme yeni bir `t` ile imzalanır. Kendi işleyicinizi test etmek için
|
|
230
|
+
`signWebhook({ payload, secret })` aynı başlığı üretir.
|
|
231
|
+
|
|
232
|
+
## Hatalar ve yeniden deneme
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { RateLimitError, RewloyError } from '@rewloy/node';
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await rewloy.passAction({
|
|
239
|
+
params: { serial },
|
|
240
|
+
body: { action: 'spend', locationId, amountMinor: 5000 },
|
|
241
|
+
idempotencyKey: `fis-${fisNo}`,
|
|
242
|
+
});
|
|
243
|
+
} catch (err) {
|
|
244
|
+
if (err instanceof RateLimitError) console.log(`${err.retryAfter} saniye sonra yeniden deneyin`);
|
|
245
|
+
else if (err instanceof RewloyError && err.code === 'INSUFFICIENT_BALANCE') console.log(err.detail);
|
|
246
|
+
else throw err;
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`RewloyError` şunları taşır:
|
|
251
|
+
- `status`: HTTP durumu;
|
|
252
|
+
- `code`: API'nin sabit kodu ([hata kodları](https://rewloy.com/gelistiriciler/hatalar));
|
|
253
|
+
kodunuz buna göre davranmalı;
|
|
254
|
+
- `title`: kodun katalogdaki başlığı;
|
|
255
|
+
- `detail`: API'nin açıklaması (Türkçe, değişebilir);
|
|
256
|
+
- `details`: varsa ayrıntı; doğrulama hatasında `[{ field, rule, message }]`;
|
|
257
|
+
- `requestId`: `x-request-id`; destek talebinde bunu verin;
|
|
258
|
+
- `body`, `headers`, `docs` ve `operation`.
|
|
259
|
+
|
|
260
|
+
Alt sınıflar:
|
|
261
|
+
- `RateLimitError`: `429`; `retryAfter` saniye;
|
|
262
|
+
- `RewloyConnectionError`: yanıt gelmedi (`status` 0, `code`
|
|
263
|
+
`CONNECTION_ERROR`);
|
|
264
|
+
- `RewloyTimeoutError`: zaman aşımı (`TIMEOUT`).
|
|
265
|
+
|
|
266
|
+
Rewloy'un olmayan bir hata gövdesi (örneğin bir vekil sunucunun 502 sayfası)
|
|
267
|
+
`HTTP_502` gibi bir kodla gelir.
|
|
268
|
+
|
|
269
|
+
**Yeniden deneme.** Şunlar en çok `maxRetries` kez (varsayılan 2) yeniden
|
|
270
|
+
denenir: bağlantı hatası, zaman aşımı, `429`, `502`, `503`, `504` ve
|
|
271
|
+
Cloudflare'in `520`–`524` hataları.
|
|
272
|
+
- **Bekleme:** üstel ve rastgele (0,5 sn, 1 sn, 2 sn… en çok 8 sn); yanıt
|
|
273
|
+
`Retry-After` taşıyorsa o kadar. `Retry-After` 60 saniyeden uzunsa
|
|
274
|
+
beklenmez, hata size gelir.
|
|
275
|
+
- **Yalnız tekrarı güvenli istekler:** `GET`, `PUT`, `DELETE` ve
|
|
276
|
+
`Idempotency-Key` taşıyan `POST`. İlk istek hâlâ işlenirken gelen
|
|
277
|
+
`409 IDEMPOTENCY_IN_PROGRESS` de beklenip yeniden denenir. Diğer `POST` ve
|
|
278
|
+
`PATCH` istekleri hiç tekrar edilmez.
|
|
279
|
+
- **Süre:** her deneme `timeoutMs` (varsayılan 60 sn) içinde bitmelidir.
|
|
280
|
+
|
|
281
|
+
## Kullanımdan kalkma
|
|
282
|
+
|
|
283
|
+
Kalkacak bir uç nokta en az 180 gün önceden duyurulur. O süre boyunca her
|
|
284
|
+
yanıtı `Deprecation`, `Sunset` ve `Link` başlıklarını taşır.
|
|
285
|
+
|
|
286
|
+
- **Uyarı.** Kütüphane her işlem için bir kez `process.emitWarning` ile bir
|
|
287
|
+
`DeprecationWarning` (kodu `REWLOY_DEPRECATED`) yayar. Uyarı işlemi, `Sunset`
|
|
288
|
+
tarihini ve değişiklik günlüğündeki kaydı söyler.
|
|
289
|
+
- **Tipler.** O metot `@deprecated` olarak işaretlenir; editörünüz üstünü
|
|
290
|
+
çizer.
|
|
291
|
+
- **Yönetmek.** Uyarıları kendi kayıtlarınıza almak için
|
|
292
|
+
`process.on('warning', …)`; kapatmak için `node --no-deprecation`.
|
|
293
|
+
|
|
294
|
+
## Yanıtın tamamı ve test modu
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
const yanit = await rewloy.request('sendCampaign', {
|
|
298
|
+
body: { body: 'Bu hafta kahveler 2 damga!' },
|
|
299
|
+
idempotencyKey: 'kampanya-2026-10-03',
|
|
300
|
+
});
|
|
301
|
+
yanit.status; // 201
|
|
302
|
+
yanit.replayed; // true: aynı anahtarın ilk yanıtı yeniden döndü (Idempotent-Replayed)
|
|
303
|
+
yanit.requestId; // x-request-id
|
|
304
|
+
yanit.mode; // Rewloy-Mode
|
|
305
|
+
yanit.data; // kampanya
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`request(işlem, argüman)` her işlemi çağırır ve yanıtın tamamını döndürür:
|
|
309
|
+
`data`, sayfalı listede `meta`, `status`, `headers`, `requestId`, `mode` ve
|
|
310
|
+
`replayed`.
|
|
311
|
+
|
|
312
|
+
`mode`, yanıtın `Rewloy-Mode` başlığıdır. Platformda test modu hazırlanıyor:
|
|
313
|
+
gerçek mesaj göndermeyen, gerçek kart vermeyen test anahtarları. Geldiğinde
|
|
314
|
+
test yanıtları bunu bu başlıkla söyleyecek. Başlık yoksa `null`. Canlı akışta
|
|
315
|
+
aynı bilgi `akis.mode`dadır.
|
|
316
|
+
|
|
317
|
+
İşlem tablosu da dışa açıktır: `OPERATIONS.passAction` →
|
|
318
|
+
`{ method, path, auth, merchant, idempotency, paged, stream, deprecated, … }`.
|
|
319
|
+
|
|
320
|
+
## Geliştirme
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
npm install
|
|
324
|
+
npm run generate # canlı belgeden: openapi/openapi.json ve src/generated/
|
|
325
|
+
npm run generate -- --file openapi/openapi.json # kayıtlı belgeden
|
|
326
|
+
npm run typecheck && npm run build && npm test
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
- `src/generated/` elle düzenlenmez; üreteç `scripts/generator.ts`'tir.
|
|
330
|
+
- Testler ağa çıkmaz: yerel bir sahte API ile çalışır. TypeScript'i doğrudan
|
|
331
|
+
çalıştırdıkları için Node 22.18 ya da üstünü ister.
|
|
332
|
+
- CI her gün canlı belgeyi okur ve bir değişiklik varsa bir pull request açar.
|
|
333
|
+
- Kararlar: [docs/DECISIONS.md](docs/DECISIONS.md).
|
|
334
|
+
|
|
335
|
+
## Belgeler
|
|
336
|
+
|
|
337
|
+
| | |
|
|
338
|
+
|---|---|
|
|
339
|
+
| Başlarken | https://rewloy.com/gelistiriciler |
|
|
340
|
+
| API referansı | https://rewloy.com/gelistiriciler/api |
|
|
341
|
+
| OpenAPI 3.1 | https://app.rewloy.com/v1/openapi.json |
|
|
342
|
+
| Hata kodları | https://rewloy.com/gelistiriciler/hatalar |
|
|
343
|
+
| API'nin değişiklik günlüğü | https://rewloy.com/gelistiriciler/degisiklikler |
|
|
344
|
+
| Bu kütüphanenin değişiklikleri | [CHANGELOG.md](CHANGELOG.md) |
|
|
345
|
+
|
|
346
|
+
**Sürümler:**
|
|
347
|
+
- Kütüphane anlamsal sürümleme ([SemVer](https://semver.org)) kullanır. 1.0'a
|
|
348
|
+
kadar arayüzü değişebilir.
|
|
349
|
+
- API'ye alan eklemek geriye uyumludur; kütüphanenin tipleri her gün
|
|
350
|
+
güncellenir.
|
|
351
|
+
- Kalkacak bir uç nokta en az 180 gün önce duyurulur ve bu süre boyunca
|
|
352
|
+
`Deprecation` ve `Sunset` başlıklarını taşır.
|
|
353
|
+
|
|
354
|
+
## Güvenlik
|
|
355
|
+
|
|
356
|
+
Bir güvenlik açığı bulursanız [SECURITY.md](SECURITY.md) dosyasındaki yoldan
|
|
357
|
+
özel olarak bildirin. Lütfen herkese açık issue açmayın.
|
|
358
|
+
|
|
359
|
+
## Lisans
|
|
360
|
+
|
|
361
|
+
[MIT](LICENSE)
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## English
|
|
366
|
+
|
|
367
|
+
**The official Node.js and TypeScript library for the Rewloy API.**
|
|
368
|
+
|
|
369
|
+
> **Status: preview (0.x), published on npm. The API is stable; the
|
|
370
|
+
> library's interface may change until 1.0.**
|
|
371
|
+
|
|
372
|
+
The documentation of the API itself is in Turkish (links above). In short:
|
|
373
|
+
|
|
374
|
+
- Every operation of the API is a method named by its `operationId`, typed
|
|
375
|
+
from the OpenAPI document, which CI reads daily and regenerates from.
|
|
376
|
+
- No dependencies: Node 22 or later, built-in `fetch` and `node:crypto`.
|
|
377
|
+
- Safe retries, `Idempotency-Key` handling, pagination, server-sent events,
|
|
378
|
+
webhook signature verification and deprecation warnings.
|
|
379
|
+
|
|
380
|
+
### Install
|
|
381
|
+
|
|
382
|
+
Node 22 or later:
|
|
383
|
+
|
|
384
|
+
```sh
|
|
385
|
+
npm install @rewloy/node
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Use
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import { Rewloy } from '@rewloy/node';
|
|
392
|
+
|
|
393
|
+
const rewloy = new Rewloy({ apiKey: process.env.REWLOY_API_KEY! }); // or { staffSession, merchant } or { holderSession }
|
|
394
|
+
|
|
395
|
+
const { serial } = await rewloy.issuePass({ body: { programId, email, kvkkConsent: true } });
|
|
396
|
+
const result = await rewloy.passAction({
|
|
397
|
+
params: { serial },
|
|
398
|
+
body: { action: 'earn-stamps', locationId },
|
|
399
|
+
idempotencyKey: `receipt-${receiptNo}`, // generated when omitted, reused across retries
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
- **Arguments.** Each method takes one object: `params`, `query` and `body` as
|
|
404
|
+
the operation needs, plus `merchant`, `idempotencyKey`, `signal`, `timeoutMs`
|
|
405
|
+
and `maxRetries`.
|
|
406
|
+
- **Results.** It resolves to the answer's `data`: `{ data, meta }` for paged
|
|
407
|
+
lists, `undefined` for 204, a `Blob` for files.
|
|
408
|
+
- **The whole answer.** `rewloy.request(id, args)` returns `status`,
|
|
409
|
+
`headers`, `requestId`, `mode` (the `Rewloy-Mode` header, for the coming
|
|
410
|
+
test mode) and `replayed` (`Idempotent-Replayed`).
|
|
411
|
+
- **Pagination.** `rewloy.paginate('listCustomers', args)` iterates the items
|
|
412
|
+
of every page.
|
|
413
|
+
- **Streams.** `rewloy.liveFeed({ signal })` (or `rewloy.stream('liveFeed',
|
|
414
|
+
args)`) iterates server-sent events (`event`, `data`, `id`). It reconnects
|
|
415
|
+
with `Last-Event-ID` unless `reconnect: false`.
|
|
416
|
+
|
|
417
|
+
### Webhooks
|
|
418
|
+
|
|
419
|
+
Verify the **raw** body (for example `express.raw({ type: 'application/json' })`)
|
|
420
|
+
with the secret shown when the webhook was created:
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
const event = verifyWebhook({ payload: req.body, header: req.get('Rewloy-Signature'), secret });
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
- **Check.** `Rewloy-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256(secret,
|
|
427
|
+
"<t>.<raw body>")>` is compared in constant time, and `t` must be within
|
|
428
|
+
300 seconds.
|
|
429
|
+
- **Refusal.** On failure it throws `WebhookSignatureError`: answer 400.
|
|
430
|
+
- **Headers.** `Rewloy-Event` is the event type. `Rewloy-Delivery` is the
|
|
431
|
+
same on every retry of a delivery: deduplicate on it. Delivery is at least
|
|
432
|
+
once.
|
|
433
|
+
|
|
434
|
+
### Errors, retries, deprecations
|
|
435
|
+
|
|
436
|
+
- **Errors.** Failures throw `RewloyError` with `status`, `code` (the API's
|
|
437
|
+
stable code), `title`, `detail`, `details`, `requestId` and `body`.
|
|
438
|
+
Subclasses: `RateLimitError` (`retryAfter`), `RewloyConnectionError` and
|
|
439
|
+
`RewloyTimeoutError`.
|
|
440
|
+
- **What is retried.** Network errors, timeouts, 429, 502–504 and
|
|
441
|
+
Cloudflare's 520–524, up to `maxRetries` (default 2), with exponential
|
|
442
|
+
backoff and jitter, honouring `Retry-After`.
|
|
443
|
+
- **Only when safe.** Only GET, PUT, DELETE, and POST with an
|
|
444
|
+
`Idempotency-Key`, are retried.
|
|
445
|
+
- **Deprecations.** A deprecated operation's answers carry `Deprecation`,
|
|
446
|
+
`Sunset` and `Link`. The client emits one `DeprecationWarning`
|
|
447
|
+
(`REWLOY_DEPRECATED`) per operation, and the generated method is marked
|
|
448
|
+
`@deprecated`.
|
|
449
|
+
|
|
450
|
+
### Security and licence
|
|
451
|
+
|
|
452
|
+
Report vulnerabilities privately, as [SECURITY.md](SECURITY.md) says.
|
|
453
|
+
[MIT](LICENSE) licensed.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client: credentials, the request (headers, retries, timeouts, errors,
|
|
3
|
+
* deprecation notices), pagination and streams. The operations themselves
|
|
4
|
+
* come from the generated RewloyMethods, one method per operationId.
|
|
5
|
+
*/
|
|
6
|
+
import { RewloyMethods } from './generated/methods.js';
|
|
7
|
+
import type { OperationId, Operations, PagedOperationId, StreamOperationId } from './generated/types.js';
|
|
8
|
+
import { EventStream } from './sse.js';
|
|
9
|
+
import type { ApiResponse, AuthKind } from './types.js';
|
|
10
|
+
export declare const DEFAULT_BASE_URL = "https://app.rewloy.com";
|
|
11
|
+
type Credential = Exclude<AuthKind, 'public'>;
|
|
12
|
+
type Sleep = (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
13
|
+
interface CommonOptions {
|
|
14
|
+
/** The API's origin, without `/v1`. Default `https://app.rewloy.com`. */
|
|
15
|
+
baseUrl?: string | undefined;
|
|
16
|
+
/** Time allowed for one attempt, in milliseconds; `0` or `Infinity` for none. Default 60000. */
|
|
17
|
+
timeoutMs?: number | undefined;
|
|
18
|
+
/** Retries after a failed attempt, when retrying is safe. Default 2. */
|
|
19
|
+
maxRetries?: number | undefined;
|
|
20
|
+
/** A `fetch` to use instead of the global one (tests, proxies, instrumentation). */
|
|
21
|
+
fetch?: typeof fetch | undefined;
|
|
22
|
+
/** Added to the `User-Agent` this client sends, e.g. `"KasaPOS/4.2"`. */
|
|
23
|
+
userAgent?: string | undefined;
|
|
24
|
+
/** Replaces the wait between retries (tests, custom schedulers). */
|
|
25
|
+
sleep?: Sleep | undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* How to build a client: with one credential, or none for the endpoints that
|
|
29
|
+
* need none (sign-in, joining a programme…).
|
|
30
|
+
*/
|
|
31
|
+
export type RewloyOptions = CommonOptions & ({
|
|
32
|
+
/** An API key, `rwk_…`: a till, a shop, your own system. */
|
|
33
|
+
apiKey: string;
|
|
34
|
+
staffSession?: undefined;
|
|
35
|
+
holderSession?: undefined;
|
|
36
|
+
merchant?: undefined;
|
|
37
|
+
} | {
|
|
38
|
+
/** A staff session, `rws_…` (`login`): a person's business app. */
|
|
39
|
+
staffSession: string;
|
|
40
|
+
/** The business this session acts for (`Rewloy-Merchant`), when the person has seats in several. */
|
|
41
|
+
merchant?: string | undefined;
|
|
42
|
+
apiKey?: undefined;
|
|
43
|
+
holderSession?: undefined;
|
|
44
|
+
} | {
|
|
45
|
+
/** A card holder's session, `rwh_…` (`holderSession`): a Rewloy Cüzdan app. */
|
|
46
|
+
holderSession: string;
|
|
47
|
+
apiKey?: undefined;
|
|
48
|
+
staffSession?: undefined;
|
|
49
|
+
merchant?: undefined;
|
|
50
|
+
} | {
|
|
51
|
+
apiKey?: undefined;
|
|
52
|
+
staffSession?: undefined;
|
|
53
|
+
holderSession?: undefined;
|
|
54
|
+
merchant?: undefined;
|
|
55
|
+
});
|
|
56
|
+
/** The argument of an operation: optional when nothing in it is required. */
|
|
57
|
+
type ArgsParam<A> = object extends A ? [args?: A] : [args: A];
|
|
58
|
+
type ItemOf<K extends PagedOperationId> = Operations[K]['data'] extends (infer T)[] ? T : never;
|
|
59
|
+
/** `Retry-After` in milliseconds: delta-seconds or an HTTP date. */
|
|
60
|
+
export declare function parseRetryAfter(value: string | null, now?: number): number | null;
|
|
61
|
+
/** Exponential backoff with jitter for the retry after attempt `attempt` (0-based). */
|
|
62
|
+
export declare function backoff(attempt: number, random?: () => number): number;
|
|
63
|
+
/**
|
|
64
|
+
* A client of the Rewloy API (`https://app.rewloy.com/v1`).
|
|
65
|
+
*
|
|
66
|
+
* ```ts
|
|
67
|
+
* const rewloy = new Rewloy({ apiKey: process.env.REWLOY_API_KEY! });
|
|
68
|
+
* const card = await rewloy.getPass({ params: { serial: 'ABCD-EFGH-JKLM' } });
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* Every operation of the API is a method named by its operationId; each takes
|
|
72
|
+
* one argument with `params`, `query` and `body` as the operation needs, and
|
|
73
|
+
* the options of {@link RequestOptions}.
|
|
74
|
+
*/
|
|
75
|
+
export declare class Rewloy extends RewloyMethods {
|
|
76
|
+
#private;
|
|
77
|
+
readonly baseUrl: string;
|
|
78
|
+
readonly timeoutMs: number;
|
|
79
|
+
readonly maxRetries: number;
|
|
80
|
+
/** The kind of credential this client sends, or `null` for none. */
|
|
81
|
+
readonly credential: Credential | null;
|
|
82
|
+
/** The default `Rewloy-Merchant` of a staff session. */
|
|
83
|
+
readonly merchant: string | null;
|
|
84
|
+
constructor(options?: RewloyOptions);
|
|
85
|
+
/**
|
|
86
|
+
* Calls an operation and returns the whole answer: `data`, `meta` on paged
|
|
87
|
+
* lists, the status, headers, `requestId`, `mode` and `replayed`.
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* const res = await rewloy.request('sendCampaign', { body: { body: 'Bu hafta kahveler 2 damga!' } });
|
|
91
|
+
* res.status; res.replayed; res.data.id;
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
request<K extends Exclude<OperationId, StreamOperationId>>(id: K, ...args: ArgsParam<Operations[K]['args']>): Promise<ApiResponse<Operations[K]['data']>>;
|
|
95
|
+
/**
|
|
96
|
+
* Walks a paged list item by item, asking for the next page (`page`) while
|
|
97
|
+
* `meta` says there is one. `query.page` sets where to start and
|
|
98
|
+
* `query.limit` the page size.
|
|
99
|
+
*
|
|
100
|
+
* ```ts
|
|
101
|
+
* for await (const customer of rewloy.paginate('listCustomers', { query: { consent: 'yes' } })) { … }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
paginate<K extends PagedOperationId>(id: K, ...args: ArgsParam<Operations[K]['args']>): AsyncIterableIterator<ItemOf<K>>;
|
|
105
|
+
/**
|
|
106
|
+
* Opens a server-sent event stream (`liveFeed`, `holderCardEvents`) and
|
|
107
|
+
* iterates its events. It reconnects by itself unless `reconnect: false`;
|
|
108
|
+
* stop it with `signal`, `break` or `close()`.
|
|
109
|
+
*
|
|
110
|
+
* ```ts
|
|
111
|
+
* for await (const ev of rewloy.stream('liveFeed', { signal })) {
|
|
112
|
+
* if (ev.event === 'event') console.log(JSON.parse(ev.data));
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
stream<K extends StreamOperationId>(id: K, ...args: ArgsParam<Operations[K]['args']>): EventStream;
|
|
117
|
+
protected _call<K extends Exclude<OperationId, StreamOperationId>>(id: K, args: Operations[K]['args'] | undefined): Promise<Operations[K]['result']>;
|
|
118
|
+
protected _open<K extends StreamOperationId>(id: K, args: Operations[K]['args'] | undefined): EventStream;
|
|
119
|
+
}
|
|
120
|
+
export {};
|