tupay 0.5.2

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 (116) hide show
  1. package/README.md +358 -0
  2. package/dist/cjs/client.d.ts +48 -0
  3. package/dist/cjs/client.d.ts.map +1 -0
  4. package/dist/cjs/client.js +165 -0
  5. package/dist/cjs/client.js.map +1 -0
  6. package/dist/cjs/errors.d.ts +66 -0
  7. package/dist/cjs/errors.d.ts.map +1 -0
  8. package/dist/cjs/errors.js +105 -0
  9. package/dist/cjs/errors.js.map +1 -0
  10. package/dist/cjs/index.d.ts +86 -0
  11. package/dist/cjs/index.d.ts.map +1 -0
  12. package/dist/cjs/index.js +110 -0
  13. package/dist/cjs/index.js.map +1 -0
  14. package/dist/cjs/liste.d.ts +54 -0
  15. package/dist/cjs/liste.d.ts.map +1 -0
  16. package/dist/cjs/liste.js +36 -0
  17. package/dist/cjs/liste.js.map +1 -0
  18. package/dist/cjs/package.json +3 -0
  19. package/dist/cjs/resources/events.d.ts +64 -0
  20. package/dist/cjs/resources/events.d.ts.map +1 -0
  21. package/dist/cjs/resources/events.js +104 -0
  22. package/dist/cjs/resources/events.js.map +1 -0
  23. package/dist/cjs/resources/merchants.d.ts +19 -0
  24. package/dist/cjs/resources/merchants.d.ts.map +1 -0
  25. package/dist/cjs/resources/merchants.js +29 -0
  26. package/dist/cjs/resources/merchants.js.map +1 -0
  27. package/dist/cjs/resources/payment-intents.d.ts +29 -0
  28. package/dist/cjs/resources/payment-intents.d.ts.map +1 -0
  29. package/dist/cjs/resources/payment-intents.js +22 -0
  30. package/dist/cjs/resources/payment-intents.js.map +1 -0
  31. package/dist/cjs/resources/payment-links.d.ts +38 -0
  32. package/dist/cjs/resources/payment-links.d.ts.map +1 -0
  33. package/dist/cjs/resources/payment-links.js +56 -0
  34. package/dist/cjs/resources/payment-links.js.map +1 -0
  35. package/dist/cjs/resources/refunds.d.ts +27 -0
  36. package/dist/cjs/resources/refunds.d.ts.map +1 -0
  37. package/dist/cjs/resources/refunds.js +33 -0
  38. package/dist/cjs/resources/refunds.js.map +1 -0
  39. package/dist/cjs/resources/test-helpers.d.ts +42 -0
  40. package/dist/cjs/resources/test-helpers.d.ts.map +1 -0
  41. package/dist/cjs/resources/test-helpers.js +50 -0
  42. package/dist/cjs/resources/test-helpers.js.map +1 -0
  43. package/dist/cjs/resources/transactions.d.ts +29 -0
  44. package/dist/cjs/resources/transactions.d.ts.map +1 -0
  45. package/dist/cjs/resources/transactions.js +65 -0
  46. package/dist/cjs/resources/transactions.js.map +1 -0
  47. package/dist/cjs/resources/webhook-endpoints.d.ts +43 -0
  48. package/dist/cjs/resources/webhook-endpoints.d.ts.map +1 -0
  49. package/dist/cjs/resources/webhook-endpoints.js +71 -0
  50. package/dist/cjs/resources/webhook-endpoints.js.map +1 -0
  51. package/dist/cjs/types.d.ts +166 -0
  52. package/dist/cjs/types.d.ts.map +1 -0
  53. package/dist/cjs/types.js +10 -0
  54. package/dist/cjs/types.js.map +1 -0
  55. package/dist/cjs/webhooks.d.ts +31 -0
  56. package/dist/cjs/webhooks.d.ts.map +1 -0
  57. package/dist/cjs/webhooks.js +81 -0
  58. package/dist/cjs/webhooks.js.map +1 -0
  59. package/dist/esm/client.d.ts +48 -0
  60. package/dist/esm/client.d.ts.map +1 -0
  61. package/dist/esm/client.js +161 -0
  62. package/dist/esm/client.js.map +1 -0
  63. package/dist/esm/errors.d.ts +66 -0
  64. package/dist/esm/errors.d.ts.map +1 -0
  65. package/dist/esm/errors.js +90 -0
  66. package/dist/esm/errors.js.map +1 -0
  67. package/dist/esm/index.d.ts +86 -0
  68. package/dist/esm/index.d.ts.map +1 -0
  69. package/dist/esm/index.js +90 -0
  70. package/dist/esm/index.js.map +1 -0
  71. package/dist/esm/liste.d.ts +54 -0
  72. package/dist/esm/liste.d.ts.map +1 -0
  73. package/dist/esm/liste.js +32 -0
  74. package/dist/esm/liste.js.map +1 -0
  75. package/dist/esm/package.json +3 -0
  76. package/dist/esm/resources/events.d.ts +64 -0
  77. package/dist/esm/resources/events.d.ts.map +1 -0
  78. package/dist/esm/resources/events.js +100 -0
  79. package/dist/esm/resources/events.js.map +1 -0
  80. package/dist/esm/resources/merchants.d.ts +19 -0
  81. package/dist/esm/resources/merchants.d.ts.map +1 -0
  82. package/dist/esm/resources/merchants.js +25 -0
  83. package/dist/esm/resources/merchants.js.map +1 -0
  84. package/dist/esm/resources/payment-intents.d.ts +29 -0
  85. package/dist/esm/resources/payment-intents.d.ts.map +1 -0
  86. package/dist/esm/resources/payment-intents.js +18 -0
  87. package/dist/esm/resources/payment-intents.js.map +1 -0
  88. package/dist/esm/resources/payment-links.d.ts +38 -0
  89. package/dist/esm/resources/payment-links.d.ts.map +1 -0
  90. package/dist/esm/resources/payment-links.js +52 -0
  91. package/dist/esm/resources/payment-links.js.map +1 -0
  92. package/dist/esm/resources/refunds.d.ts +27 -0
  93. package/dist/esm/resources/refunds.d.ts.map +1 -0
  94. package/dist/esm/resources/refunds.js +29 -0
  95. package/dist/esm/resources/refunds.js.map +1 -0
  96. package/dist/esm/resources/test-helpers.d.ts +42 -0
  97. package/dist/esm/resources/test-helpers.d.ts.map +1 -0
  98. package/dist/esm/resources/test-helpers.js +46 -0
  99. package/dist/esm/resources/test-helpers.js.map +1 -0
  100. package/dist/esm/resources/transactions.d.ts +29 -0
  101. package/dist/esm/resources/transactions.d.ts.map +1 -0
  102. package/dist/esm/resources/transactions.js +61 -0
  103. package/dist/esm/resources/transactions.js.map +1 -0
  104. package/dist/esm/resources/webhook-endpoints.d.ts +43 -0
  105. package/dist/esm/resources/webhook-endpoints.d.ts.map +1 -0
  106. package/dist/esm/resources/webhook-endpoints.js +67 -0
  107. package/dist/esm/resources/webhook-endpoints.js.map +1 -0
  108. package/dist/esm/types.d.ts +166 -0
  109. package/dist/esm/types.d.ts.map +1 -0
  110. package/dist/esm/types.js +9 -0
  111. package/dist/esm/types.js.map +1 -0
  112. package/dist/esm/webhooks.d.ts +31 -0
  113. package/dist/esm/webhooks.d.ts.map +1 -0
  114. package/dist/esm/webhooks.js +77 -0
  115. package/dist/esm/webhooks.js.map +1 -0
  116. package/package.json +53 -0
package/README.md ADDED
@@ -0,0 +1,358 @@
1
+ # tupay
2
+
3
+ SDK Node officiel de **Tupay** — encaissement en XPF pour la Polynésie française.
4
+
5
+ Zéro dépendance runtime. TypeScript natif. ESM et CommonJS.
6
+
7
+ ```bash
8
+ npm install tupay
9
+ ```
10
+
11
+ Node 18 ou plus (le SDK s'appuie sur le `fetch` global).
12
+
13
+ > **Historique du nom.** Ce paquet s'est appelé `@tupay/node` avant sa
14
+ > première publication. Le scope `@tupay` n'était pas disponible sur npm,
15
+ > et un scope de repli se serait lu comme une coquille. Le nom publié
16
+ > est donc `tupay`, sans scope.
17
+ >
18
+ > Les versions `0.5.0` et `0.5.1` ont été taguées mais jamais publiées :
19
+ > la première a échoué à la construction, la seconde sur un jeton qui
20
+ > exigeait un code d'authentification qu'aucune chaîne d'intégration ne
21
+ > peut fournir. Ni l'une ni l'autre n'existe sur npm, et c'est
22
+ > volontaire. Republier un numéro déjà tagué serait pire qu'un trou dans
23
+ > la numérotation.
24
+
25
+ ---
26
+
27
+ ## Démarrer
28
+
29
+ ```ts
30
+ import { Tupay } from 'tupay'
31
+
32
+ const tupay = new Tupay(process.env.TUPAY_SECRET_KEY!)
33
+
34
+ const link = await tupay.paymentLinks.create({
35
+ amountXpf: 4500, // entier XPF — pas de centimes
36
+ description: 'Location paddle 2 h',
37
+ returnUrl: 'https://ma-boutique.pf/merci',
38
+ metadata: { commande: 'A-1234' },
39
+ })
40
+
41
+ console.log(link.url) // → https://api.tupay.pf/pay/k3f9zq2xw1
42
+ ```
43
+
44
+ En CommonJS :
45
+
46
+ ```js
47
+ const { Tupay } = require('tupay')
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Les montants sont en XPF entiers
53
+
54
+ Le franc pacifique n'a pas de centimes. `amountXpf: 4500` vaut **4 500 XPF**, pas 45.
55
+
56
+ Ne divisez jamais par 100. `amountEurCents` existe uniquement pour la réconciliation bancaire — il ne sert pas à l'affichage client.
57
+
58
+ ---
59
+
60
+ ## Mode test
61
+
62
+ Le mode découle du **préfixe de la clé**, sans configuration :
63
+
64
+ ```ts
65
+ new Tupay('tpk_test_…').livemode // false — aucun argent ne bouge
66
+ new Tupay('tpk_live_…').livemode // true
67
+ ```
68
+
69
+ Les deux mondes sont étanches : une clé de test ne voit ni ne rembourse une transaction réelle.
70
+
71
+ Le bac à sable ne résout pas les paiements tout seul — **vous** décidez de l'issue, ce qui vous permet de tester vos chemins d'échec :
72
+
73
+ ```ts
74
+ const intent = await tupay.paymentIntents.create({ amountXpf: 4500, merchantId })
75
+
76
+ await tupay.testHelpers.simulatePayment(intent.transactionId, 'succeeded')
77
+ // → votre endpoint de webhook reçoit un payment.succeeded signé
78
+ ```
79
+
80
+ `simulatePayment` lève **localement** si le client a été construit avec une clé live — aucune requête ne part.
81
+
82
+ Pour tester un échec dès la création :
83
+
84
+ ```ts
85
+ import { TestHelpers } from 'tupay'
86
+
87
+ TestHelpers.magicAmounts.cardDeclined // 402 → card_declined
88
+ TestHelpers.magicAmounts.insufficientFunds // 403 → insufficient_funds
89
+ TestHelpers.magicAmounts.rateLimited // 429 → rate_limited
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Webhooks
95
+
96
+ ### Vérifier la signature
97
+
98
+ ```ts
99
+ import express from 'express'
100
+ import { Tupay, TupaySignatureVerificationError } from 'tupay'
101
+
102
+ const tupay = new Tupay(process.env.TUPAY_SECRET_KEY!)
103
+ const app = express()
104
+
105
+ app.post(
106
+ '/tupay/webhook',
107
+ express.raw({ type: 'application/json' }), // ⚠️ corps BRUT, obligatoire
108
+ (req, res) => {
109
+ let event
110
+ try {
111
+ event = tupay.webhooks.constructEvent(
112
+ req.body,
113
+ req.get('tupay-signature'),
114
+ process.env.TUPAY_WEBHOOK_SECRET!,
115
+ )
116
+ } catch (err) {
117
+ if (err instanceof TupaySignatureVerificationError) {
118
+ return res.status(400).send('signature invalide')
119
+ }
120
+ throw err
121
+ }
122
+
123
+ res.sendStatus(200) // acquittez vite (< 10 s)
124
+ void traiter(event) // puis traitez
125
+ },
126
+ )
127
+ ```
128
+
129
+ **Le piège n°1 :** passer un objet déjà parsé puis re-sérialisé. Le HMAC porte sur les octets exacts envoyés ; un espace d'écart le casse. Un test du SDK couvre précisément ce scénario.
130
+
131
+ | Framework | Comment obtenir le corps brut |
132
+ |---|---|
133
+ | Express | `express.raw({ type: 'application/json' })` |
134
+ | Next.js (App Router) | `await request.text()` |
135
+ | Fastify | `config: { rawBody: true }` |
136
+ | Hono | `await c.req.text()` |
137
+
138
+ ### Les quatre règles de réception
139
+
140
+ 1. **Répondez 2xx en moins de 10 s.** Au-delà, la livraison est comptée en échec.
141
+ 2. **Dédupliquez sur `event.id`.** Une livraison peut se répéter — c'est le prix d'une livraison garantie.
142
+ 3. **Ne vous fiez pas à l'ordre.** `event.data.object.updatedAt` tranche.
143
+ 4. **Filtrez sur `event.livemode`** si le même endpoint sert vos deux environnements.
144
+
145
+ ### Déboguer une intégration
146
+
147
+ ```ts
148
+ const { data } = await tupay.webhookEndpoints.listDeliveries(endpointId, {
149
+ status: 'failed',
150
+ })
151
+
152
+ for (const d of data) {
153
+ console.log(d.eventType, d.responseStatus, d.lastError, '→ retry', d.nextRetryAt)
154
+ }
155
+ ```
156
+
157
+ Reprises automatiques : 1 min → 5 min → 30 min → 2 h → 6 h → 24 h, soit 7 tentatives sur ~34 h.
158
+
159
+ ### L'historique des événements
160
+
161
+ Les événements existent indépendamment de leurs livraisons : même sans
162
+ endpoint déclaré, votre activité laisse une trace complète.
163
+
164
+ ```ts
165
+ // Ce qui s'est passé, du plus récent au plus ancien.
166
+ const { data, has_more } = await tupay.events.list({ limit: 20 })
167
+
168
+ // Un événement précis — le geste courant quand un webhook vous
169
+ // intrigue : vous tenez son id, vous voulez revoir l'objet émis.
170
+ const event = await tupay.events.retrieve('evt_9f8c1a2b3d4e5f60')
171
+ ```
172
+
173
+ **Rejouer.** Votre serveur était en panne à 3 h du matin :
174
+
175
+ ```ts
176
+ for await (const event of tupay.events.autoPagingEach({
177
+ type: 'payment.succeeded',
178
+ })) {
179
+ const { deliveries } = await tupay.events.resend(event.id)
180
+ console.log(event.id, '→', deliveries, 'livraison(s)')
181
+ }
182
+ ```
183
+
184
+ Le rejeu **crée** une livraison, il ne modifie pas l'ancienne — celle-ci
185
+ reste une pièce d'audit. Votre gestionnaire recevra donc l'événement une
186
+ fois de plus **avec le même `id`** : c'est exactement pourquoi la règle 2
187
+ ci-dessus existe.
188
+
189
+ La pagination des événements se fait par **curseur**
190
+ (`startingAfter: 'evt_…'`), comme les cinq autres collections. Un
191
+ `offset` glisserait dès qu'un événement naît pendant le parcours, et
192
+ vous en sauteriez sans le voir.
193
+
194
+ `apres` reste accepté comme alias de `startingAfter` : le SDK le
195
+ traduit avant d'émettre. Il est déprécié, supporté au minimum douze
196
+ mois.
197
+
198
+ ---
199
+
200
+ ## Remboursements
201
+
202
+ ```ts
203
+ // Total du reliquat
204
+ await tupay.refunds.create({ transactionId })
205
+
206
+ // Partiel
207
+ await tupay.refunds.create({ transactionId, amountXpf: 1500, reason: 'Article retourné' })
208
+ ```
209
+
210
+ Les remboursements partiels **s'accumulent**. Après 1 500 XPF remboursés sur 4 500 :
211
+
212
+ ```ts
213
+ tx.status // 'succeeded' ← inchangé
214
+ tx.refundedAmountXpf // 1500 ← c'est ce champ qui fait foi
215
+ ```
216
+
217
+ > Ne testez pas `status === 'refunded'` pour savoir si un remboursement a eu lieu. `status` ne bascule qu'au remboursement intégral.
218
+
219
+ ---
220
+
221
+ ## Idempotence et reprises
222
+
223
+ Le SDK génère une clé d'idempotence (UUID v4) sur `paymentIntents.create` et `refunds.create`. Une reprise après timeout ne débitera jamais deux fois.
224
+
225
+ Fournissez la vôtre pour qu'un retry applicatif ultérieur retombe sur le même résultat :
226
+
227
+ ```ts
228
+ await tupay.paymentIntents.create({
229
+ amountXpf: 4500,
230
+ merchantId,
231
+ idempotencyKey: `commande-${orderId}`,
232
+ })
233
+ ```
234
+
235
+ **Politique de reprise :** le SDK retente sur 429, 5xx et pannes réseau, avec backoff exponentiel et gigue (`Retry-After` respecté). Il ne retente **que** les GET et les écritures porteuses d'une clé d'idempotence — `paymentLinks.create` n'est donc jamais rejoué, une reprise créerait un second lien.
236
+
237
+ ```ts
238
+ new Tupay(key, { maxRetries: 0, timeout: 5000 })
239
+ ```
240
+
241
+ ---
242
+
243
+ ## Erreurs
244
+
245
+ Toutes héritent de `TupayError`. Testez `err.code` (code machine stable), pas `err.message` (français, destiné à un humain, peut changer).
246
+
247
+ ```ts
248
+ import { TupayValidationError, TupayRateLimitError, TupayError } from 'tupay'
249
+
250
+ try {
251
+ await tupay.paymentIntents.create({ amountXpf: 50, merchantId })
252
+ } catch (err) {
253
+ if (err instanceof TupayValidationError) console.error(err.code, err.message)
254
+ else if (err instanceof TupayRateLimitError) await attendre()
255
+ else if (err instanceof TupayError) console.error(err.code, err.requestId)
256
+ else throw err
257
+ }
258
+ ```
259
+
260
+ | Classe | HTTP | Quand |
261
+ |---|---|---|
262
+ | `TupayValidationError` | 400 / 422 | Corps invalide |
263
+ | `TupayAuthenticationError` | 401 | Clé absente, invalide ou révoquée |
264
+ | `TupayPermissionError` | 403 | Interdit ici — dont `livemode_mismatch` |
265
+ | `TupayNotFoundError` | 404 | Inexistant ou hors de votre périmètre |
266
+ | `TupayConflictError` | 409 | Déjà remboursé, limite atteinte |
267
+ | `TupayRateLimitError` | 429 | 100 req/min dépassées |
268
+ | `TupayBaasError` | 502 | Le prestataire bancaire a refusé |
269
+ | `TupayApiError` | 5xx | Incident côté Tupay |
270
+ | `TupayConnectionError` | — | Réseau, DNS, TLS ou timeout |
271
+ | `TupaySignatureVerificationError` | — | Webhook non vérifiable |
272
+
273
+ ---
274
+
275
+ ## Pagination
276
+
277
+ Les six collections se paginent **de la même façon** : `limit`,
278
+ `startingAfter`, `endingBefore`.
279
+
280
+ ```ts
281
+ const page = await tupay.transactions.list({ status: 'succeeded', limit: 50 })
282
+ // { data: Transaction[], has_more: boolean, hasMore: boolean }
283
+
284
+ // Page suivante : on repart du dernier élément lu.
285
+ const dernier = page.data[page.data.length - 1]
286
+ const suite = await tupay.transactions.list({
287
+ status: 'succeeded',
288
+ limit: 50,
289
+ startingAfter: dernier.id,
290
+ })
291
+
292
+ // Ou tout parcourir sans tenir de curseur soi-même :
293
+ let total = 0
294
+ for await (const tx of tupay.transactions.autoPagingEach({ status: 'succeeded' })) {
295
+ total += tx.amountXpf
296
+ }
297
+ ```
298
+
299
+ **`has_more` est la clé canonique.** `hasMore` porte la même valeur et
300
+ reste rempli, y compris face à une API qui ne rend encore que l'une des
301
+ deux : le SDK lit `has_more` avec repli sur `hasMore`. `hasMore` est
302
+ déprécié et disparaîtra dans une version datée future ; lisez
303
+ `has_more`.
304
+
305
+ **`offset` est déprécié.** Il reste accepté au minimum douze mois, avec
306
+ exactement le comportement qu'il a toujours eu, et n'est jamais envoyé
307
+ par défaut. Un décalage nomme une position dans un classement qui
308
+ bouge : dès qu'une ligne naît pendant votre parcours, tout ce qui suit
309
+ glisse d'un cran. Le mêler à un curseur dans le même appel est refusé
310
+ par un `400`.
311
+
312
+ **Un curseur qui ne se résout pas rend `400 invalid_cursor`**, jamais
313
+ une page vide. C'est la seule erreur de pagination dont la reprise
314
+ n'est pas de retenter le même appel : il faut recommencer le parcours
315
+ depuis le début.
316
+
317
+ ---
318
+
319
+ ## Référence
320
+
321
+ | Ressource | Méthodes |
322
+ |---|---|
323
+ | `paymentIntents` | `create` |
324
+ | `paymentLinks` | `create`, `retrieve`, `list`, `activate`, `deactivate` |
325
+ | `transactions` | `retrieve`, `list`, `autoPagingEach` |
326
+ | `refunds` | `create`, `list` |
327
+ | `webhookEndpoints` | `create`, `retrieve`, `list`, `update`, `del`, `listDeliveries` |
328
+ | `merchants` | `me` |
329
+ | `events` | `list`, `retrieve`, `resend`, `autoPagingEach` |
330
+ | `testHelpers` | `simulatePayment`, `magicAmounts` |
331
+ | `webhooks` | `constructEvent` |
332
+
333
+ ### Options du client
334
+
335
+ ```ts
336
+ new Tupay(apiKey, {
337
+ baseUrl: 'http://localhost:3000', // pour pointer un environnement local
338
+ timeout: 20_000, // ms, par tentative
339
+ maxRetries: 2, // reprises après la tentative initiale
340
+ headers: { 'x-trace-id': '…' },
341
+ fetch: monFetch, // injectable pour les tests
342
+ })
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Développement
348
+
349
+ ```bash
350
+ npm install
351
+ npm test # 43 tests
352
+ npm run typecheck
353
+ npm run build # dist/esm + dist/cjs
354
+ ```
355
+
356
+ ## Licence
357
+
358
+ MIT
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Cœur HTTP du SDK : authentification, idempotence, reprises, erreurs.
3
+ *
4
+ * Aucune dépendance runtime — `fetch` global (Node 18+) et `node:crypto`.
5
+ */
6
+ export interface TupayOptions {
7
+ /** Défaut : l’hôte de production Tupay. Surchargez pour pointer un environnement local. */
8
+ baseUrl?: string;
9
+ /** Timeout par tentative, en millisecondes. Défaut 20 000. */
10
+ timeout?: number;
11
+ /** Nombre de reprises après la tentative initiale. Défaut 2. */
12
+ maxRetries?: number;
13
+ /** En-têtes ajoutés à chaque requête (traçage, proxy d'entreprise…). */
14
+ headers?: Record<string, string>;
15
+ /**
16
+ * Version d'API à demander, au format `AAAA-MM-JJ`.
17
+ *
18
+ * Facultative : sans elle, la version figée à la création de la clé
19
+ * s'applique, et les formes de réponse restent stables dans le temps.
20
+ * La préciser sert à coder explicitement contre une version donnée —
21
+ * une valeur inconnue est refusée par un `400`, jamais silencieusement
22
+ * ignorée.
23
+ */
24
+ apiVersion?: string;
25
+ /** Injectable pour les tests. Défaut : `globalThis.fetch`. */
26
+ fetch?: typeof globalThis.fetch;
27
+ }
28
+ export interface RequestOptions {
29
+ method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
30
+ path: string;
31
+ query?: Record<string, string | number | boolean | undefined>;
32
+ body?: unknown;
33
+ /**
34
+ * Clé d'idempotence. Sa présence rend la requête rejouable en toute
35
+ * sécurité : c'est elle qui autorise le SDK à retenter un POST.
36
+ */
37
+ idempotencyKey?: string;
38
+ }
39
+ export declare class TupayClient {
40
+ #private;
41
+ /** `true` si la clé est une `tpk_live_`. Déduit du préfixe, sans appel réseau. */
42
+ readonly livemode: boolean;
43
+ constructor(apiKey: string, options?: TupayOptions);
44
+ /** Génère une clé d'idempotence. Exposé pour que l'appelant puisse la réutiliser. */
45
+ static idempotencyKey(): string;
46
+ request<T>(options: RequestOptions): Promise<T>;
47
+ }
48
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,WAAW,YAAY;IAC3B,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;IAC3C,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAA;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAsBD,qBAAa,WAAW;;IAQtB,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;gBAEd,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB;IAkCtD,qFAAqF;IACrF,MAAM,CAAC,cAAc,IAAI,MAAM;IAIzB,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAmHtD"}
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ /**
3
+ * Cœur HTTP du SDK : authentification, idempotence, reprises, erreurs.
4
+ *
5
+ * Aucune dépendance runtime — `fetch` global (Node 18+) et `node:crypto`.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.TupayClient = void 0;
9
+ const node_crypto_1 = require("node:crypto");
10
+ const errors_js_1 = require("./errors.js");
11
+ /**
12
+ * Hôte de production.
13
+ *
14
+ * ⚠️ `api.tupay.pf` ne résout pas — le domaine n'est pas enregistré. Un
15
+ * SDK publié avec cette valeur échouerait en erreur DNS au tout premier
16
+ * appel, ce qui est la pire première impression possible. On pointe donc
17
+ * l'hôte qui répond réellement aujourd'hui.
18
+ *
19
+ * Le jour où `api.tupay.pf` existe, changer cette constante est un
20
+ * changement de comportement pour quiconque s'est reposé sur le défaut :
21
+ * il devra donc sortir en version MAJEURE, avec les deux hôtes servis en
22
+ * parallèle le temps de la transition.
23
+ */
24
+ const DEFAULT_BASE_URL = 'https://tupay.apps.lepetittahitien.dev';
25
+ const DEFAULT_TIMEOUT_MS = 20_000;
26
+ const DEFAULT_MAX_RETRIES = 2;
27
+ /** Statuts qui méritent une reprise : surcharge passagère ou incident serveur. */
28
+ const RETRYABLE_STATUSES = new Set([429, 500, 502, 503, 504]);
29
+ class TupayClient {
30
+ #apiKey;
31
+ #baseUrl;
32
+ #timeout;
33
+ #maxRetries;
34
+ #headers;
35
+ #fetch;
36
+ /** `true` si la clé est une `tpk_live_`. Déduit du préfixe, sans appel réseau. */
37
+ livemode;
38
+ constructor(apiKey, options = {}) {
39
+ if (!apiKey) {
40
+ throw new errors_js_1.TupayError('Clé API manquante. Passez-la au constructeur : new Tupay(process.env.TUPAY_SECRET_KEY).', { code: 'missing_api_key' });
41
+ }
42
+ if (!apiKey.startsWith('tpk_live_') && !apiKey.startsWith('tpk_test_')) {
43
+ throw new errors_js_1.TupayError('Clé API invalide : elle doit commencer par tpk_live_ ou tpk_test_.', { code: 'invalid_api_key' });
44
+ }
45
+ const fetchImpl = options.fetch ?? globalThis.fetch;
46
+ if (typeof fetchImpl !== 'function') {
47
+ throw new errors_js_1.TupayError('fetch global introuvable. Node 18+ est requis, ou passez une implémentation via l’option `fetch`.', { code: 'fetch_unavailable' });
48
+ }
49
+ this.#apiKey = apiKey;
50
+ this.#baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
51
+ this.#timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
52
+ this.#maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
53
+ this.#headers = {
54
+ ...(options.apiVersion ? { 'tupay-version': options.apiVersion } : {}),
55
+ ...(options.headers ?? {}),
56
+ };
57
+ this.#fetch = fetchImpl;
58
+ this.livemode = apiKey.startsWith('tpk_live_');
59
+ }
60
+ /** Génère une clé d'idempotence. Exposé pour que l'appelant puisse la réutiliser. */
61
+ static idempotencyKey() {
62
+ return (0, node_crypto_1.randomUUID)();
63
+ }
64
+ async request(options) {
65
+ const url = this.#buildUrl(options.path, options.query);
66
+ const headers = {
67
+ authorization: `Bearer ${this.#apiKey}`,
68
+ accept: 'application/json',
69
+ 'user-agent': 'tupay-node',
70
+ ...this.#headers,
71
+ };
72
+ if (options.body !== undefined) {
73
+ headers['content-type'] = 'application/json';
74
+ }
75
+ if (options.idempotencyKey) {
76
+ headers['idempotency-key'] = options.idempotencyKey;
77
+ }
78
+ // Une requête n'est rejouable que si elle est sûre à répéter :
79
+ // lecture pure, ou écriture protégée par une clé d'idempotence.
80
+ // Sans ça, une reprise sur timeout créerait un doublon.
81
+ const retryable = options.method === 'GET' || options.idempotencyKey !== undefined;
82
+ const attempts = retryable ? this.#maxRetries + 1 : 1;
83
+ let lastError;
84
+ for (let attempt = 1; attempt <= attempts; attempt++) {
85
+ try {
86
+ const response = await this.#fetch(url, {
87
+ method: options.method,
88
+ headers,
89
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
90
+ signal: AbortSignal.timeout(this.#timeout),
91
+ });
92
+ const requestId = response.headers.get('x-request-id') ?? undefined;
93
+ if (response.ok) {
94
+ if (response.status === 204)
95
+ return undefined;
96
+ const text = await response.text();
97
+ if (!text)
98
+ return undefined;
99
+ return JSON.parse(text);
100
+ }
101
+ const body = await this.#safeJson(response);
102
+ const error = (0, errors_js_1.errorFromResponse)(response.status, body, requestId);
103
+ if (attempt < attempts && RETRYABLE_STATUSES.has(response.status)) {
104
+ lastError = error;
105
+ await this.#sleep(this.#backoffMs(attempt, response));
106
+ continue;
107
+ }
108
+ throw error;
109
+ }
110
+ catch (err) {
111
+ // Erreur API déjà typée : la logique de reprise a été appliquée
112
+ // dans le `try` (on n'arrive ici qu'une fois les essais épuisés,
113
+ // ou sur un statut non rejouable).
114
+ if (err instanceof errors_js_1.TupayError)
115
+ throw err;
116
+ // Panne réseau, DNS, TLS ou timeout — aucune réponse reçue.
117
+ const connectionError = new errors_js_1.TupayConnectionError(err instanceof Error && err.name === 'TimeoutError'
118
+ ? `La requête a dépassé le délai de ${this.#timeout} ms.`
119
+ : `Impossible de joindre l'API Tupay : ${err instanceof Error ? err.message : String(err)}`, err);
120
+ if (attempt >= attempts)
121
+ throw connectionError;
122
+ lastError = connectionError;
123
+ await this.#sleep(this.#backoffMs(attempt));
124
+ }
125
+ }
126
+ throw lastError ?? new errors_js_1.TupayError('Requête échouée.', { code: 'server_error' });
127
+ }
128
+ #buildUrl(path, query) {
129
+ const url = new URL(`${this.#baseUrl}/api/v1${path}`);
130
+ for (const [key, value] of Object.entries(query ?? {})) {
131
+ if (value !== undefined)
132
+ url.searchParams.set(key, String(value));
133
+ }
134
+ return url.toString();
135
+ }
136
+ /**
137
+ * Backoff exponentiel avec gigue, pour ne pas faire repartir tous les
138
+ * clients en même temps après un incident. Respecte `Retry-After`
139
+ * quand le serveur l'envoie.
140
+ */
141
+ #backoffMs(attempt, response) {
142
+ const retryAfter = response?.headers.get('retry-after');
143
+ if (retryAfter) {
144
+ const seconds = Number.parseInt(retryAfter, 10);
145
+ if (Number.isFinite(seconds) && seconds >= 0) {
146
+ return Math.min(seconds * 1000, 20_000);
147
+ }
148
+ }
149
+ const base = Math.min(500 * 2 ** (attempt - 1), 8_000);
150
+ return base + Math.random() * 250;
151
+ }
152
+ async #safeJson(response) {
153
+ try {
154
+ return (await response.json());
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ }
160
+ #sleep(ms) {
161
+ return new Promise((resolve) => setTimeout(resolve, ms));
162
+ }
163
+ }
164
+ exports.TupayClient = TupayClient;
165
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Hiérarchie d'erreurs du SDK.
3
+ *
4
+ * Toutes héritent de `TupayError` : un `catch (err)` unique suffit, et
5
+ * `err instanceof TupayRateLimitError` permet d'affiner si besoin.
6
+ *
7
+ * `code` est le code machine renvoyé par l'API (`validation_error`,
8
+ * `livemode_mismatch`, …). C'est lui qu'il faut tester — le `message`
9
+ * est en français, destiné à un humain, et peut changer sans préavis.
10
+ */
11
+ export interface TupayErrorOptions {
12
+ code: string;
13
+ statusCode?: number;
14
+ requestId?: string;
15
+ cause?: unknown;
16
+ }
17
+ export declare class TupayError extends Error {
18
+ /** Code machine stable renvoyé par l'API. */
19
+ readonly code: string;
20
+ /** Statut HTTP, absent si l'erreur est survenue avant toute réponse. */
21
+ readonly statusCode?: number;
22
+ /** Identifiant de requête, à citer au support. */
23
+ readonly requestId?: string;
24
+ constructor(message: string, options: TupayErrorOptions);
25
+ }
26
+ /** 401 — clé absente, invalide ou révoquée. */
27
+ export declare class TupayAuthenticationError extends TupayError {
28
+ }
29
+ /** 403 — opération interdite dans ce contexte (dont `livemode_mismatch`). */
30
+ export declare class TupayPermissionError extends TupayError {
31
+ }
32
+ /** 404 — ressource inexistante ou hors du périmètre de la clé. */
33
+ export declare class TupayNotFoundError extends TupayError {
34
+ }
35
+ /** 400 / 422 — corps de requête invalide. */
36
+ export declare class TupayValidationError extends TupayError {
37
+ }
38
+ /** 409 — état incompatible (déjà remboursé, limite atteinte). */
39
+ export declare class TupayConflictError extends TupayError {
40
+ }
41
+ /** 429 — 100 req/min par marchand dépassées. */
42
+ export declare class TupayRateLimitError extends TupayError {
43
+ }
44
+ /** 5xx — incident côté Tupay. */
45
+ export declare class TupayApiError extends TupayError {
46
+ }
47
+ /** 502 — le prestataire bancaire a refusé l'opération. */
48
+ export declare class TupayBaasError extends TupayError {
49
+ }
50
+ /** Panne réseau, DNS, TLS ou dépassement du timeout. Aucune réponse reçue. */
51
+ export declare class TupayConnectionError extends TupayError {
52
+ constructor(message: string, cause?: unknown);
53
+ }
54
+ /** La signature d'un webhook n'a pas pu être vérifiée. */
55
+ export declare class TupaySignatureVerificationError extends TupayError {
56
+ constructor(message: string);
57
+ }
58
+ /**
59
+ * Construit l'erreur typée correspondant à une réponse HTTP.
60
+ * Le mapping suit le catalogue documenté dans `docs/api-integration.md`.
61
+ */
62
+ export declare function errorFromResponse(status: number, body: {
63
+ error?: string;
64
+ message?: string;
65
+ } | null, requestId?: string): TupayError;
66
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,UAAW,SAAQ,KAAK;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;gBAEf,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB;CAOxD;AAED,+CAA+C;AAC/C,qBAAa,wBAAyB,SAAQ,UAAU;CAAG;AAE3D,6EAA6E;AAC7E,qBAAa,oBAAqB,SAAQ,UAAU;CAAG;AAEvD,kEAAkE;AAClE,qBAAa,kBAAmB,SAAQ,UAAU;CAAG;AAErD,6CAA6C;AAC7C,qBAAa,oBAAqB,SAAQ,UAAU;CAAG;AAEvD,iEAAiE;AACjE,qBAAa,kBAAmB,SAAQ,UAAU;CAAG;AAErD,gDAAgD;AAChD,qBAAa,mBAAoB,SAAQ,UAAU;CAAG;AAEtD,iCAAiC;AACjC,qBAAa,aAAc,SAAQ,UAAU;CAAG;AAEhD,0DAA0D;AAC1D,qBAAa,cAAe,SAAQ,UAAU;CAAG;AAEjD,8EAA8E;AAC9E,qBAAa,oBAAqB,SAAQ,UAAU;gBACtC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAG7C;AAED,0DAA0D;AAC1D,qBAAa,+BAAgC,SAAQ,UAAU;gBACjD,OAAO,EAAE,MAAM;CAG5B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EACjD,SAAS,CAAC,EAAE,MAAM,GACjB,UAAU,CAwBZ"}