@rewloy/node 0.1.0 → 0.2.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.
- package/CHANGELOG.md +69 -0
- package/README.md +178 -22
- package/dist/client.d.ts +11 -0
- package/dist/client.js +32 -8
- package/dist/generated/methods.d.ts +352 -25
- package/dist/generated/methods.js +389 -25
- package/dist/generated/operations.js +58 -14
- package/dist/generated/types.d.ts +2103 -110
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,75 @@ https://rewloy.com/gelistiriciler/degisiklikler
|
|
|
5
5
|
|
|
6
6
|
This library's releases. The API's own changes are listed at the link above.
|
|
7
7
|
|
|
8
|
+
## 0.2.1 (2026-10-05)
|
|
9
|
+
|
|
10
|
+
Dışarıdan geliştiricilerin bulduğu üç sorun düzeltildi.
|
|
11
|
+
|
|
12
|
+
Three problems found by outside developers, fixed.
|
|
13
|
+
|
|
14
|
+
- **`Idempotency-Key` is checked before sending.** A key with non-ASCII
|
|
15
|
+
characters (`fiş-0042`) made `fetch` throw a bare `TypeError` about the header
|
|
16
|
+
value. Now the client refuses any key that is not printable ASCII
|
|
17
|
+
(0x21–0x7E), 8–64 characters, with a clear `TypeError` ("Idempotency-Key
|
|
18
|
+
yalnız ASCII karakterler içerebilir …") and sends nothing. The API will also
|
|
19
|
+
answer `400 VALIDATION` for such a key in its next release.
|
|
20
|
+
- **`baseUrl` takes the address with or without `/v1`.** The documentation and
|
|
21
|
+
the OpenAPI document show `https://app.rewloy.com/v1`; the client wanted the
|
|
22
|
+
origin only. Now both work; a trailing `/v1` or `/v1/` and trailing slashes
|
|
23
|
+
are stripped (`rewloy.baseUrl` is the origin).
|
|
24
|
+
- **`idempotencyKey` is required where the API requires it.** For `recordSale`,
|
|
25
|
+
`passAction`, `sendCampaign` and `refundShopRedemption` the OpenAPI document
|
|
26
|
+
marks the header required, but the client made up a random UUID when it was
|
|
27
|
+
missing, which does not survive a restart of your app. `idempotencyKey` is now
|
|
28
|
+
a required argument of those methods (a type error in TypeScript, a
|
|
29
|
+
`TypeError` before sending in JavaScript). Where the header is optional
|
|
30
|
+
(`issuePass`, …) a UUID is still generated and reused on every retry.
|
|
31
|
+
**Breaking for callers that relied on the generated key** (a small break,
|
|
32
|
+
taken in a patch release because the old behaviour could write a sale twice).
|
|
33
|
+
|
|
34
|
+
## 0.2.0 (2026-10-05)
|
|
35
|
+
|
|
36
|
+
Rewloy API 1.0.5'e göre yeniden üretildi: 255 işlem (0.1.0'da 237). Kasa için
|
|
37
|
+
`recordSale` ve `reverseSale`; README'de yeni bir kasa örneği, test modu ve
|
|
38
|
+
`baseUrl`.
|
|
39
|
+
|
|
40
|
+
Regenerated from Rewloy API 1.0.5: 255 operations (237 in 0.1.0).
|
|
41
|
+
|
|
42
|
+
- **New operations (18).**
|
|
43
|
+
- *Till:* `recordSale` (`POST /v1/passes/{serial}/sale`: write a completed
|
|
44
|
+
sale to a card; the card type decides what is written) and `reverseSale`
|
|
45
|
+
(`POST /v1/passes/{serial}/sale/reverse`: take a refunded sale back).
|
|
46
|
+
- *Checkout codes and shop connections:* `quoteCheckoutCode`,
|
|
47
|
+
`holdCheckoutCode`, `captureCheckoutOrder`, `releaseCheckoutOrder`,
|
|
48
|
+
`refundCheckoutOrder`, `listOrderRedemptions`, `listShopRedemptions`,
|
|
49
|
+
`releaseShopRedemption`, `refundShopRedemption`, `setShopSettings`,
|
|
50
|
+
`setShopCeiling`, `setShopPluginAbilities`, and for the card holder
|
|
51
|
+
`holderCheckoutCodes`, `mintHolderCheckoutCode`, `cancelHolderCheckoutCode`.
|
|
52
|
+
- `getMeta` (`GET /v1/meta`): the API's version.
|
|
53
|
+
- **`getPass`** now also returns `programName`, `currency`, `stamps`
|
|
54
|
+
(`count`, `max`), `points`, `money` (`amountMinor`, `currency`), `customer`
|
|
55
|
+
(with `customers.read`), `actions` and `sale`.
|
|
56
|
+
- **Webhooks.** `webhooks.manage` API keys manage webhooks (`createWebhook`,
|
|
57
|
+
`listWebhooks`, `getWebhook`, `setWebhookStatus`, `testWebhook`,
|
|
58
|
+
`listWebhookDeliveries`, `webhookEvents`); a webhook reports `createdByKey`.
|
|
59
|
+
- **Other fields.** `issuePass` returns `created`; business lists and `me`
|
|
60
|
+
carry `currency`; programs carry `sale`; batches `onlineValue`; shops
|
|
61
|
+
`accepts`, `settings`, `shopName`, `unbacked` and the plugin key's
|
|
62
|
+
`abilities`.
|
|
63
|
+
- **Generator.** A doc comment holding an unbalanced bracket no longer
|
|
64
|
+
breaks an object union such as the answer of `me`.
|
|
65
|
+
- **README.**
|
|
66
|
+
- A till example with `recordSale`, the structured fields of `getPass` and
|
|
67
|
+
a refund with `reverseSale`.
|
|
68
|
+
- `Idempotency-Key`: a key is unique for good per credential. The
|
|
69
|
+
receipt number alone is not a key (fiscal receipt numbers restart after
|
|
70
|
+
the Z report): use register + Z number + receipt number, or a UUID
|
|
71
|
+
stored with the sale. The receipt number goes in `reference`.
|
|
72
|
+
- Test mode exists: `rwk_test_` keys and a test business. The "being
|
|
73
|
+
prepared" wording is gone.
|
|
74
|
+
- How to set a custom base URL (staging), and a link to the developer
|
|
75
|
+
docs, https://rewloy.com/gelistiriciler.
|
|
76
|
+
|
|
8
77
|
## 0.1.0 (2026-10-04)
|
|
9
78
|
|
|
10
79
|
|
package/README.md
CHANGED
|
@@ -13,15 +13,16 @@ kartı, kupon ve indirimdir:
|
|
|
13
13
|
|
|
14
14
|
Kasada QR okutulur; bakiye, ödül ve kampanyalar kartın kendisinde güncellenir.
|
|
15
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
|
|
16
|
+
ile de yapılabilir; bu kütüphane onu Node.js'ten kullanır. Geliştirici
|
|
17
|
+
belgeleri: **https://rewloy.com/gelistiriciler**.
|
|
17
18
|
|
|
18
19
|
- **Tam tipli.** API'nin her işlemi, `operationId` adıyla bir metottur.
|
|
19
20
|
Parametreler, gövdeler ve yanıtlar OpenAPI belgesinden
|
|
20
21
|
([`openapi.json`](https://app.rewloy.com/v1/openapi.json)) üretilen
|
|
21
22
|
tiplerle gelir. CI belgeyi her gün okur ve değişince yeniden üretir.
|
|
22
23
|
- **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
|
|
24
|
-
ve kampanyada `Idempotency-Key`.
|
|
24
|
+
- **Güvenli tekrar.** Geçici hatalarda ölçülü yeniden deneme; satışta, kasa
|
|
25
|
+
işleminde ve kampanyada `Idempotency-Key`.
|
|
25
26
|
- **Ötesi:** sayfalama, canlı akış (SSE), webhook imzası doğrulama,
|
|
26
27
|
kullanımdan kalkma uyarıları.
|
|
27
28
|
|
|
@@ -51,7 +52,7 @@ işlemin gerektirdikleriyle:
|
|
|
51
52
|
- `query`: sorgu parametreleri;
|
|
52
53
|
- `body`: JSON gövde;
|
|
53
54
|
- `merchant`: `Rewloy-Merchant` başlığı;
|
|
54
|
-
- `idempotencyKey`: `Idempotency-Key` başlığı (kasa işlemi ve
|
|
55
|
+
- `idempotencyKey`: `Idempotency-Key` başlığı (satış, kasa işlemi, kampanya ve mağaza iadesinde zorunlu);
|
|
55
56
|
- `signal`, `timeoutMs`, `maxRetries`.
|
|
56
57
|
|
|
57
58
|
Metot yanıttaki `data`yı döndürür. Sayfalı listelerde `{ data, meta }`,
|
|
@@ -85,12 +86,27 @@ Bir işlem istemcinin kimlik türünü kabul etmiyor ama kimliksiz de çalışı
|
|
|
85
86
|
bir kimliği reddeder (`CREDENTIAL_NOT_ALLOWED`).
|
|
86
87
|
|
|
87
88
|
Diğer seçenekler:
|
|
88
|
-
- `baseUrl` (varsayılan `https://app.rewloy.com`);
|
|
89
|
+
- `baseUrl` (varsayılan `https://app.rewloy.com`; sonuna `/v1` eklemeniz ya da eklememeniz fark etmez: `https://app.rewloy.com/v1` de olur, kütüphane `/v1`i kendisi ekler);
|
|
89
90
|
- `timeoutMs` (60000);
|
|
90
91
|
- `maxRetries` (2);
|
|
91
92
|
- `fetch`: kendi `fetch`iniz;
|
|
92
93
|
- `userAgent`: gönderilen `User-Agent`a eklenir, örneğin `"KasaPOS/4.2"`.
|
|
93
94
|
|
|
95
|
+
### Başka bir adres (staging)
|
|
96
|
+
|
|
97
|
+
API'nin başka bir kopyasına (kendi staging ortamınız ya da bir vekil sunucu)
|
|
98
|
+
`baseUrl` ile bağlanılır:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const rewloy = new Rewloy({
|
|
102
|
+
apiKey: process.env.REWLOY_API_KEY!,
|
|
103
|
+
baseUrl: 'https://rewloy-staging.ornek.com', // sonuna /v1 yazsanız da olur
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Gerçek müşterilere dokunmadan denemek için adres değiştirmeniz gerekmez:
|
|
108
|
+
[test modu](#test-modu) aynı adreste, ayrı bir test ortamıyla çalışır.
|
|
109
|
+
|
|
94
110
|
## Kart vermek ve kasada işlem
|
|
95
111
|
|
|
96
112
|
```ts
|
|
@@ -101,16 +117,87 @@ const { serial, cardUrl } = await rewloy.issuePass({
|
|
|
101
117
|
const sonuc = await rewloy.passAction({
|
|
102
118
|
params: { serial },
|
|
103
119
|
body: { action: 'earn-stamps', locationId, count: 1 },
|
|
104
|
-
idempotencyKey: `fis
|
|
120
|
+
idempotencyKey: `kasa3-z0187-fis${fisNo}`, // aşağıya bakın
|
|
121
|
+
});
|
|
122
|
+
if (sonuc.duplicate) console.log('Bu işlem zaten yazılmış');
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Satış: `recordSale`
|
|
126
|
+
|
|
127
|
+
Kasa ya da kendi yazılımınız için en kolay yol `recordSale`dir: "bu satış
|
|
128
|
+
oldu, sen yaz". Ödenen toplamı (kartın para biriminde, kuruş) gönderirsiniz;
|
|
129
|
+
ne yazılacağına kartın türü ve programın kendi kuralı karar verir. Kartın
|
|
130
|
+
türünü bilmeniz gerekmez.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const kart = await rewloy.getPass({ params: { serial } });
|
|
134
|
+
// Kartın türüne özgü alanlar; `balance` yerine bunları okuyun.
|
|
135
|
+
if (kart.stamps) console.log(`${kart.stamps.count} / ${kart.stamps.max} damga`);
|
|
136
|
+
if (kart.points !== undefined) console.log(`${kart.points} puan`);
|
|
137
|
+
if (kart.money) console.log(`${kart.money.amountMinor / 100} ${kart.money.currency}`);
|
|
138
|
+
console.log(kart.programName, kart.customer?.name); // customer: yalnız customers.read yetkisiyle
|
|
139
|
+
|
|
140
|
+
// Fiş numarası anahtar olamaz: kasa + Z no + fiş no, ya da satışla saklanan bir UUID.
|
|
141
|
+
const anahtar = `kasa3-z0187-fis${fisNo}`;
|
|
142
|
+
const satis = await rewloy.recordSale({
|
|
143
|
+
params: { serial },
|
|
144
|
+
body: {
|
|
145
|
+
locationId,
|
|
146
|
+
amountMinor: 4550, // 45,50: kartın para biriminde (`kart.currency`), kuruş
|
|
147
|
+
currency: kart.currency, // isteğe bağlı güvence: uyuşmazsa 422 CURRENCY_MISMATCH
|
|
148
|
+
reference: `fis-${fisNo}`, // fiş numarası buraya yazılır
|
|
149
|
+
},
|
|
150
|
+
idempotencyKey: anahtar,
|
|
151
|
+
});
|
|
152
|
+
if (satis.applied === 'none') console.log('Yazılan bir şey yok:', satis.reason);
|
|
153
|
+
else console.log(`${satis.credited} ${satis.applied} yazıldı, bakiye ${satis.balance}`);
|
|
154
|
+
if (satis.rewardReady) console.log('Ödül hazır');
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`GET /v1/passes/{serial}` ayrıca `actions` (kartın aldığı kasa işlemleri ve
|
|
158
|
+
şimdi yapılıp yapılamayacakları) ve `sale` (bir satışın bu kartta ne
|
|
159
|
+
yazacağı) alanlarını verir.
|
|
160
|
+
|
|
161
|
+
**İade.** `reverseSale` bir satışın karta yazdığını geri alır; satışı
|
|
162
|
+
yazarken gönderdiğiniz anahtarla (`saleKey`) ya da `reference`la bulur:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const geri = await rewloy.reverseSale({
|
|
166
|
+
params: { serial },
|
|
167
|
+
body: { saleKey: anahtar, locationId },
|
|
105
168
|
});
|
|
106
|
-
|
|
169
|
+
console.log(geri.reversed, geri.applied, geri.balance, geri.duplicate);
|
|
107
170
|
```
|
|
108
171
|
|
|
109
|
-
|
|
172
|
+
Bir satış bir kez geri alınır (tekrar `duplicate: true` döner). Kazanılan
|
|
173
|
+
kullanılmışsa (ödüle ya da harcamaya gitmişse) `409 SALE_ALREADY_SPENT` gelir ve
|
|
174
|
+
hiçbir şey yazılmaz.
|
|
175
|
+
|
|
176
|
+
### `Idempotency-Key`
|
|
177
|
+
|
|
178
|
+
`recordSale`, `passAction`, `sendCampaign` ve `refundShopRedemption` bir
|
|
179
|
+
`Idempotency-Key` **ister**: API'nin tanımında (OpenAPI) bu başlık bu işlemlerde
|
|
180
|
+
zorunludur, bu yüzden `idempotencyKey` bu metotlarda zorunlu bir argümandır.
|
|
181
|
+
Verilmezse kütüphane istek göndermeden `TypeError` fırlatır; **sizin yerinize
|
|
182
|
+
anahtar üretmez**. Üretilmiş rastgele bir anahtar yalnızca tek çağrının yeniden
|
|
183
|
+
denemelerini korurdu: uygulama çöküp yeniden başlarsa yeni bir anahtar çıkar ve
|
|
184
|
+
satış ikinci kez yazılabilirdi. Anahtarı kendiniz üretip satışla birlikte
|
|
185
|
+
saklayın. Anahtar 8–64 karakterlik görünür ASCII olmalıdır (0x21–0x7E: harf,
|
|
186
|
+
rakam ve noktalama; boşluk, Türkçe harf ya da `fiş` gibi ASCII dışı karakter
|
|
187
|
+
olmaz); aksi halde kütüphane yine istek göndermeden `TypeError` fırlatır.
|
|
188
|
+
Başlığın isteğe bağlı olduğu işlemlerde (örneğin `issuePass`) anahtar verilmezse
|
|
110
189
|
kütüphane bir UUID üretir ve aynı çağrının her denemesinde aynısını gönderir.
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
190
|
+
|
|
191
|
+
- **Anahtar bir kimlik için kalıcı olarak tekildir** (8–64 karakter; defterden
|
|
192
|
+
hiç silinmez). Aynı anahtarla aynı isteğin tekrarı ikinci kez yazmaz ve
|
|
193
|
+
ilk sonucu `duplicate: true` ile döndürür. Aynı anahtar başka bir gövdeyle
|
|
194
|
+
`422 IDEMPOTENCY_KEY_REUSED` alır.
|
|
195
|
+
- **Fiş numarası tek başına anahtar olamaz:** yazarkasa fiş numaraları Z
|
|
196
|
+
raporundan sonra yeniden başlar. Kasa + Z no + fiş no birleşimi
|
|
197
|
+
(`kasa3-z0187-fis0042`) ya da satışla birlikte saklanıp tekrarda yeniden
|
|
198
|
+
gönderilen bir UUID kullanın.
|
|
199
|
+
- **Fiş numarası `reference` alanına** yazılır; müşterinin geçmişinde ve işlem
|
|
200
|
+
dökümünde görünür.
|
|
114
201
|
|
|
115
202
|
## Sayfalama
|
|
116
203
|
|
|
@@ -164,6 +251,22 @@ gösterilen sırla (`whsec_…`) doğrular:
|
|
|
164
251
|
- `t` şimdiden 300 saniyeden (`toleranceSeconds`) uzaksa reddeder;
|
|
165
252
|
- gövdeyi ayrıştırılmış olarak döndürür.
|
|
166
253
|
|
|
254
|
+
Webhook'u panelden ya da API'den ekleyebilirsiniz. `webhooks.manage` yetkili
|
|
255
|
+
bir API anahtarı `createWebhook`, `listWebhooks`, `getWebhook`,
|
|
256
|
+
`setWebhookStatus`, `testWebhook` ve `listWebhookDeliveries`yi çağırabilir;
|
|
257
|
+
`webhookEvents` abone olunabilecek olayları söyler. Sır (`secret`) yalnız
|
|
258
|
+
`createWebhook` yanıtında gelir, saklayın:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const { webhook, secret } = await rewloy.createWebhook({
|
|
262
|
+
body: { url: 'https://ornek.com/rewloy/webhook', events: ['pass.activity', 'pass.voided'] },
|
|
263
|
+
});
|
|
264
|
+
await rewloy.testWebhook({ params: { id: webhook.id } }); // webhook.test olayı gönderir
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Adres herkese açık bir `https` adresi olmalıdır (test ortamında da);
|
|
268
|
+
yerelde bir tünel kullanın.
|
|
269
|
+
|
|
167
270
|
Tutmazsa `WebhookSignatureError` atar: 400 ile yanıtlayın ve hiçbir işlem
|
|
168
271
|
yapmayın. Gövde mutlaka ham olmalıdır. JSON olarak ayrıştırılıp yeniden yazılan
|
|
169
272
|
bir gövde imzayı tutturmaz.
|
|
@@ -238,7 +341,7 @@ try {
|
|
|
238
341
|
await rewloy.passAction({
|
|
239
342
|
params: { serial },
|
|
240
343
|
body: { action: 'spend', locationId, amountMinor: 5000 },
|
|
241
|
-
idempotencyKey: `fis
|
|
344
|
+
idempotencyKey: `kasa3-z0187-fis${fisNo}`,
|
|
242
345
|
});
|
|
243
346
|
} catch (err) {
|
|
244
347
|
if (err instanceof RateLimitError) console.log(`${err.retryAfter} saniye sonra yeniden deneyin`);
|
|
@@ -309,14 +412,35 @@ yanit.data; // kampanya
|
|
|
309
412
|
`data`, sayfalı listede `meta`, `status`, `headers`, `requestId`, `mode` ve
|
|
310
413
|
`replayed`.
|
|
311
414
|
|
|
312
|
-
`mode`, yanıtın `Rewloy-Mode` başlığıdır
|
|
313
|
-
|
|
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.
|
|
415
|
+
`mode`, yanıtın `Rewloy-Mode` başlığıdır: `live` ya da `test`. Başlık yoksa
|
|
416
|
+
`null`. Canlı akışta aynı bilgi `akis.mode`dadır.
|
|
316
417
|
|
|
317
418
|
İşlem tablosu da dışa açıktır: `OPERATIONS.passAction` →
|
|
318
419
|
`{ method, path, auth, merchant, idempotency, paged, stream, deprecated, … }`.
|
|
319
420
|
|
|
421
|
+
## Test modu
|
|
422
|
+
|
|
423
|
+
Gerçek müşterilere dokunmadan denemek için işletmenizin bir **test ortamı**
|
|
424
|
+
vardır: ona bağlı ayrı bir işletme (adı "· Test" ile biter); kendi
|
|
425
|
+
programları, müşterileri, kartları, anahtarları ve webhook'ları. Panel →
|
|
426
|
+
Geliştirici → "Test ortamını aç" ya da `POST /v1/test/environment`. Orada
|
|
427
|
+
oluşturulan anahtar `rwk_test_` ile başlar ve aynı adreste, aynı yollarla
|
|
428
|
+
çalışır:
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
const rewloy = new Rewloy({ apiKey: process.env.REWLOY_TEST_KEY! }); // rwk_test_…
|
|
432
|
+
const yanit = await rewloy.request('getPass', { params: { serial } });
|
|
433
|
+
yanit.mode; // 'test'
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- Test ortamı hiçbir şey göndermez (e-posta, bildirim, SMS); kartlar
|
|
437
|
+
cüzdanlara eklenmez. Gönderilmeyenler `GET /v1/test/messages` ile okunur.
|
|
438
|
+
- Webhook'lar teslim edilir ve `Rewloy-Test: 1` başlığıyla `"test": true`
|
|
439
|
+
taşır.
|
|
440
|
+
- Gerçek müşteri verisini test ortamına girmeyin.
|
|
441
|
+
|
|
442
|
+
Ayrıntı: https://rewloy.com/gelistiriciler#test-ortamı
|
|
443
|
+
|
|
320
444
|
## Geliştirme
|
|
321
445
|
|
|
322
446
|
```sh
|
|
@@ -369,7 +493,8 @@ Bir güvenlik açığı bulursanız [SECURITY.md](SECURITY.md) dosyasındaki yol
|
|
|
369
493
|
> **Status: preview (0.x), published on npm. The API is stable; the
|
|
370
494
|
> library's interface may change until 1.0.**
|
|
371
495
|
|
|
372
|
-
The documentation of the API itself is in Turkish
|
|
496
|
+
The documentation of the API itself is in Turkish. Developer docs:
|
|
497
|
+
**https://rewloy.com/gelistiriciler**. In short:
|
|
373
498
|
|
|
374
499
|
- Every operation of the API is a method named by its `operationId`, typed
|
|
375
500
|
from the OpenAPI document, which CI reads daily and regenerates from.
|
|
@@ -393,21 +518,46 @@ import { Rewloy } from '@rewloy/node';
|
|
|
393
518
|
const rewloy = new Rewloy({ apiKey: process.env.REWLOY_API_KEY! }); // or { staffSession, merchant } or { holderSession }
|
|
394
519
|
|
|
395
520
|
const { serial } = await rewloy.issuePass({ body: { programId, email, kvkkConsent: true } });
|
|
396
|
-
const
|
|
521
|
+
const sale = await rewloy.recordSale({
|
|
397
522
|
params: { serial },
|
|
398
|
-
body: {
|
|
399
|
-
idempotencyKey: `
|
|
523
|
+
body: { locationId, amountMinor: 4550, reference: `receipt-${receiptNo}` }, // amount in the card's currency, minor units
|
|
524
|
+
idempotencyKey: `till3-z0187-r${receiptNo}`,
|
|
400
525
|
});
|
|
401
526
|
```
|
|
402
527
|
|
|
528
|
+
- **Till.** `recordSale` writes a completed sale to a card (the card type and
|
|
529
|
+
the programme's own rule decide what is written); `getPass` returns the
|
|
530
|
+
card's structured fields (`programName`, `currency`, `stamps`, `points`,
|
|
531
|
+
`money`, `customer`); `reverseSale` takes a refunded sale back:
|
|
532
|
+
`rewloy.reverseSale({ params: { serial }, body: { saleKey: key } })`.
|
|
533
|
+
- **Idempotency keys.** `recordSale`, `passAction`, `sendCampaign` and
|
|
534
|
+
`refundShopRedemption` need an `Idempotency-Key`: the API's OpenAPI document
|
|
535
|
+
marks the header required for them, so `idempotencyKey` is a required
|
|
536
|
+
argument and the client throws a `TypeError` before sending if it is missing.
|
|
537
|
+
It never makes one up for you (a generated key would not survive a restart of
|
|
538
|
+
your app). The key must be 8–64 printable ASCII characters (0x21–0x7E); a
|
|
539
|
+
non-ASCII key such as `fiş-0042` is refused client-side, with a `TypeError`,
|
|
540
|
+
before anything is sent. Where the header is optional (for example
|
|
541
|
+
`issuePass`) the client still generates a UUID and reuses it on every retry
|
|
542
|
+
of the call. A key is unique **for good per credential**: do not use the
|
|
543
|
+
receipt number alone (fiscal receipt numbers restart after the Z report) but
|
|
544
|
+
register + Z number + receipt number, or a UUID stored with the sale. The
|
|
545
|
+
receipt number goes in `reference`.
|
|
546
|
+
- **Base URL.** `new Rewloy({ apiKey, baseUrl: 'https://staging.example.com' })`
|
|
547
|
+
or `baseUrl: 'https://staging.example.com/v1'`: with or without a trailing
|
|
548
|
+
`/v1` (and trailing slashes), the client appends `/v1/...` itself. Default
|
|
549
|
+
`https://app.rewloy.com`.
|
|
550
|
+
- **Test mode.** Open the test environment (panel → Developer, or
|
|
551
|
+
`POST /v1/test/environment`) and use its `rwk_test_` key at the same address:
|
|
552
|
+
a separate test business that sends nothing and never reaches real
|
|
553
|
+
customers. Webhooks are delivered with `Rewloy-Test: 1`.
|
|
403
554
|
- **Arguments.** Each method takes one object: `params`, `query` and `body` as
|
|
404
555
|
the operation needs, plus `merchant`, `idempotencyKey`, `signal`, `timeoutMs`
|
|
405
556
|
and `maxRetries`.
|
|
406
557
|
- **Results.** It resolves to the answer's `data`: `{ data, meta }` for paged
|
|
407
558
|
lists, `undefined` for 204, a `Blob` for files.
|
|
408
559
|
- **The whole answer.** `rewloy.request(id, args)` returns `status`,
|
|
409
|
-
`headers`, `requestId`, `mode` (the `Rewloy-Mode` header
|
|
410
|
-
test mode) and `replayed` (`Idempotent-Replayed`).
|
|
560
|
+
`headers`, `requestId`, `mode` (the `Rewloy-Mode` header: `live` or `test`) and `replayed` (`Idempotent-Replayed`).
|
|
411
561
|
- **Pagination.** `rewloy.paginate('listCustomers', args)` iterates the items
|
|
412
562
|
of every page.
|
|
413
563
|
- **Streams.** `rewloy.liveFeed({ signal })` (or `rewloy.stream('liveFeed',
|
|
@@ -451,3 +601,9 @@ const event = verifyWebhook({ payload: req.body, header: req.get('Rewloy-Signatu
|
|
|
451
601
|
|
|
452
602
|
Report vulnerabilities privately, as [SECURITY.md](SECURITY.md) says.
|
|
453
603
|
[MIT](LICENSE) licensed.
|
|
604
|
+
|
|
605
|
+
## Yeni sürüm yayımlamak / Releasing
|
|
606
|
+
|
|
607
|
+
`package.json`'daki sürümü ve CHANGELOG'u güncelleyin, commit'leyin, `v<sürüm>` etiketini gönderin (`git tag v0.2.0 && git push origin v0.2.0`). `release.yml` npm'e güvenilir yayıncı (trusted publishing) yoluyla, jetonsuz ve kaynak kanıtıyla (provenance) yayımlar.
|
|
608
|
+
|
|
609
|
+
Bump the version in `package.json` and the changelog, commit, and push a `v<version>` tag. `release.yml` publishes to npm through trusted publishing: no token, with provenance.
|
package/dist/client.d.ts
CHANGED
|
@@ -60,6 +60,17 @@ type ItemOf<K extends PagedOperationId> = Operations[K]['data'] extends (infer T
|
|
|
60
60
|
export declare function parseRetryAfter(value: string | null, now?: number): number | null;
|
|
61
61
|
/** Exponential backoff with jitter for the retry after attempt `attempt` (0-based). */
|
|
62
62
|
export declare function backoff(attempt: number, random?: () => number): number;
|
|
63
|
+
/**
|
|
64
|
+
* The base URL without trailing slashes and without a trailing `/v1`: the
|
|
65
|
+
* paths of the operations carry `/v1` themselves, and the documentation shows
|
|
66
|
+
* the address both ways (`https://app.rewloy.com` and `https://app.rewloy.com/v1`).
|
|
67
|
+
*/
|
|
68
|
+
export declare function normalizeBaseUrl(url: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* An `Idempotency-Key` is 8–64 printable ASCII characters (0x21–0x7E): an HTTP
|
|
71
|
+
* header value cannot carry anything else, and `fetch` would throw a bare TypeError.
|
|
72
|
+
*/
|
|
73
|
+
export declare function checkIdempotencyKey(key: unknown): string;
|
|
63
74
|
/**
|
|
64
75
|
* A client of the Rewloy API (`https://app.rewloy.com/v1`).
|
|
65
76
|
*
|
package/dist/client.js
CHANGED
|
@@ -56,6 +56,24 @@ export function backoff(attempt, random = Math.random) {
|
|
|
56
56
|
const cap = Math.min(BACKOFF_MAX_MS, BACKOFF_BASE_MS * 2 ** attempt);
|
|
57
57
|
return Math.round(cap / 2 + random() * (cap / 2));
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* The base URL without trailing slashes and without a trailing `/v1`: the
|
|
61
|
+
* paths of the operations carry `/v1` themselves, and the documentation shows
|
|
62
|
+
* the address both ways (`https://app.rewloy.com` and `https://app.rewloy.com/v1`).
|
|
63
|
+
*/
|
|
64
|
+
export function normalizeBaseUrl(url) {
|
|
65
|
+
return url.replace(/\/+$/, '').replace(/\/v1$/, '').replace(/\/+$/, '');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* An `Idempotency-Key` is 8–64 printable ASCII characters (0x21–0x7E): an HTTP
|
|
69
|
+
* header value cannot carry anything else, and `fetch` would throw a bare TypeError.
|
|
70
|
+
*/
|
|
71
|
+
export function checkIdempotencyKey(key) {
|
|
72
|
+
if (typeof key !== 'string' || !/^[\x21-\x7e]{8,64}$/.test(key)) {
|
|
73
|
+
throw new TypeError('Rewloy: Idempotency-Key yalnız ASCII karakterler içerebilir (görünür karakterler, 8–64) / the Idempotency-Key must be printable ASCII (0x21–0x7E), 8–64 characters');
|
|
74
|
+
}
|
|
75
|
+
return key;
|
|
76
|
+
}
|
|
59
77
|
/** The URL a `Link` header gives for `rel="deprecation"` (else its first). */
|
|
60
78
|
function deprecationLink(link) {
|
|
61
79
|
if (!link)
|
|
@@ -135,7 +153,7 @@ export class Rewloy extends RewloyMethods {
|
|
|
135
153
|
if (options.merchant !== undefined && which !== 'staffSession')
|
|
136
154
|
throw new TypeError('Rewloy: `merchant` goes with a staffSession');
|
|
137
155
|
this.merchant = options.merchant ?? null;
|
|
138
|
-
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL)
|
|
156
|
+
this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
|
|
139
157
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
140
158
|
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
141
159
|
const f = options.fetch ?? globalThis.fetch;
|
|
@@ -251,7 +269,7 @@ export class Rewloy extends RewloyMethods {
|
|
|
251
269
|
const qs = query.toString();
|
|
252
270
|
return `${this.baseUrl}${path}${qs ? `?${qs}` : ''}`;
|
|
253
271
|
}
|
|
254
|
-
#headers(op, a, lastEventId) {
|
|
272
|
+
#headers(id, op, a, lastEventId) {
|
|
255
273
|
const h = new Headers();
|
|
256
274
|
h.set('accept', op.stream ? 'text/event-stream' : op.response === 'json' || op.response === 'raw-json' ? 'application/json' : '*/*');
|
|
257
275
|
if (this.#userAgent)
|
|
@@ -264,15 +282,21 @@ export class Rewloy extends RewloyMethods {
|
|
|
264
282
|
const merchant = a.merchant ?? (this.credential === 'staff' ? this.merchant : null);
|
|
265
283
|
if (op.merchant && merchant)
|
|
266
284
|
h.set('rewloy-merchant', merchant);
|
|
267
|
-
if (op.idempotency)
|
|
268
|
-
|
|
285
|
+
if (op.idempotency) {
|
|
286
|
+
if (a.idempotencyKey === undefined && op.idempotency === 'required') {
|
|
287
|
+
throw new TypeError(`Rewloy: ${id} needs idempotencyKey: Idempotency-Key gerekli, kütüphane uydurmaz (8–64 ASCII karakter) / the Idempotency-Key is required and is never generated for you (8–64 printable ASCII characters)`);
|
|
288
|
+
}
|
|
289
|
+
h.set('idempotency-key', a.idempotencyKey === undefined ? randomUUID() : checkIdempotencyKey(a.idempotencyKey));
|
|
290
|
+
}
|
|
269
291
|
if (op.body)
|
|
270
292
|
h.set('content-type', 'application/json');
|
|
271
293
|
if (lastEventId)
|
|
272
294
|
h.set('last-event-id', lastEventId);
|
|
273
|
-
for (const [k, v] of Object.entries(a.headers ?? {}))
|
|
274
|
-
if (v
|
|
275
|
-
|
|
295
|
+
for (const [k, v] of Object.entries(a.headers ?? {})) {
|
|
296
|
+
if (v === undefined || v === null)
|
|
297
|
+
continue;
|
|
298
|
+
h.set(k, k.toLowerCase() === 'idempotency-key' ? checkIdempotencyKey(v) : String(v));
|
|
299
|
+
}
|
|
276
300
|
return h;
|
|
277
301
|
}
|
|
278
302
|
#notice(id, op, headers) {
|
|
@@ -290,7 +314,7 @@ export class Rewloy extends RewloyMethods {
|
|
|
290
314
|
async #exchange(id, op, args, stream) {
|
|
291
315
|
const a = args ?? {};
|
|
292
316
|
const url = this.#url(id, op, a);
|
|
293
|
-
const headers = this.#headers(op, a, stream?.lastEventId);
|
|
317
|
+
const headers = this.#headers(id, op, a, stream?.lastEventId);
|
|
294
318
|
const body = op.body ? JSON.stringify(a.body ?? {}) : undefined;
|
|
295
319
|
const retryable = IDEMPOTENT_METHODS.has(op.method) || headers.has('idempotency-key');
|
|
296
320
|
const maxRetries = Math.max(0, a.maxRetries ?? this.maxRetries);
|