@sanghosdk/js 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ Tous les changements notables sont documentés ici.
4
+
5
+ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/).
6
+ Ce projet respecte le [Semantic Versioning](https://semver.org/lang/fr/).
7
+
8
+ ---
9
+
10
+ ## [Unreleased]
11
+
12
+ ### Added
13
+
14
+ - `sangho.account.retrieve()` — `GET /account`, introspection de l'app liée à la clé secrète.
15
+ - `sangho.sandbox.reset()` — `POST /reset`, purge des données sandbox (clé test uniquement).
16
+ - Les erreurs `SanghoError` (et sous-classes) exposent désormais `.type` (catégorie large, une des 9 valeurs `SanghoErrorType`, ex : `VALIDATION_ERROR`) EN PLUS de `.code` qui devient le code métier précis renvoyé par le backend (ex : `AMOUNT_TOO_SMALL`, `CURRENCY_NOT_IN_PLAN`) — voir `README.md#gestion-des-erreurs`.
17
+ - Les erreurs `429` et `5xx` sont désormais réellement retryées avec backoff exponentiel (respectant `maxRetries`) ; un `429` respecte en priorité le délai `retry_after` renvoyé par le serveur. Les `4xx` restants (400/401/403/404/409/422) ne sont jamais retryés.
18
+
19
+ ### Changed
20
+
21
+ - **Breaking** : `ListResponse<T>` expose désormais `data: T[]` au lieu de `results: T[]`, conformément à la pagination réelle de l'API. Tout code utilisant `.results` sur une liste doit passer à `.data`.
22
+ - **Breaking** : `CurrencyCode` n'est plus restreint à `"XAF" | "XOF"` — le type couvre désormais l'ensemble des codes ISO 4217 actifs standards (`ISO_4217_CURRENCIES` dans `types/common.ts`). Cela reflète uniquement la validité ISO 4217 du code, pas ce qu'un marchand a le droit d'utiliser : chaque plan Sangho entitle un sous-ensemble de devises, et le backend rejette à l'exécution toute devise hors plan (`SanghoError` avec `.code === "CURRENCY_NOT_IN_PLAN"`, ou `"INVALID_CURRENCY"` si le code n'est pas un ISO 4217 reconnu).
23
+ - **Breaking** : `SanghoError.code` ne vaut plus une des 10 catégories basses (`"authentication_error"`, `"api_error"`, ...) — c'est maintenant le code métier précis du backend (`raw.code`), typé `string` (catalogue ouvert, côté backend). La catégorie large vit désormais dans le nouveau champ `.type` (`SanghoErrorType`, 9 valeurs UPPERCASE). Le type `SanghoErrorCode` a été retiré ; utiliser `SanghoErrorType`.
24
+ - **Breaking** : renommage du package `sangho` → `@sanghosdk`.
25
+ - `checkoutSessions.retrieve()` n'exige plus une clé secrète — le backend autorise explicitement la clé publique sur cette action (page de confirmation navigateur).
26
+ - `security.*` appelle désormais les bonnes routes (`/security/me/`, `/security/update_me/`) et existe réellement à l'exécution en tant que `sangho.security` (elle n'existait pas auparavant malgré son typage).
27
+
28
+ ### Deprecated
29
+
30
+ ### Removed
31
+
32
+ - Suppression des fichiers de test `tests/unit/payouts.test.ts` et `tests/unit/sandbox.test.ts` : ils testaient un module « Payouts » qui n'existe ni dans le SDK ni comme endpoint public de l'API.
33
+
34
+ ### Fixed
35
+
36
+ - `SanghoNotFoundError` ne construit plus un message dupliqué/incohérent sur les 404 — elle relaie désormais tel quel le message renvoyé par le backend.
37
+ - `SanghoRateLimitError` lit correctement `retry_after` (au lieu de `retry_later`, qui n'existe pas côté backend).
38
+ - La comparaison du code d'erreur `public_key_not_allowed` est désormais insensible à la casse (le backend envoie parfois `PUBLIC_KEY_NOT_ALLOWED`).
39
+ - Revert d'une régression non publiée qui rendait `success_url` optionnel sur `checkoutSessions.create()` alors que le backend l'exige toujours.
40
+
41
+ ### Security
42
+
43
+ - Retrait des clés API committées en clair et de `NODE_TLS_REJECT_UNAUTHORIZED=0` dans `tests/playground.ts`.
44
+ - `SanghoOptions.baseURL` refuse désormais un protocole non-HTTPS (sauf `localhost`/`127.0.0.1`), pour éviter d'envoyer une clé API en clair par erreur de configuration.
45
+
46
+ ---
47
+
48
+ ## [1.0.0] - 2026-04-01
49
+
50
+ ### Added
51
+
52
+ - Version initiale du SDK
53
+ - Support de toutes les ressources : apps, customers, products, paymentIntents,
54
+ checkoutSessions, invoices, transactions, refunds, subscriptions,
55
+ paymentMethods, webhooks, paymentLinks, addresses, partners
56
+ - Gestion complète des erreurs (auth, validation, rate limit, réseau)
57
+ - Pagination via ListResponse
package/README.md ADDED
@@ -0,0 +1,366 @@
1
+ # @sanghosdk/js — SDK JavaScript / TypeScript officiel
2
+
3
+ SDK officiel de [Sangho](https://sangho.ga), la plateforme de paiement B2B pour l'Afrique francophone.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@sanghosdk/js)](https://www.npmjs.com/package/@sanghosdk/js)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.4-blue)](https://www.typescriptlang.org/)
7
+ [![Docs](https://img.shields.io/badge/docs-docs.sangho.ga-navy)](https://docs.sangho.ga)
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @sanghosdk/js
15
+ # ou
16
+ pnpm add @sanghosdk/js
17
+ # ou
18
+ yarn add @sanghosdk/js
19
+ ```
20
+
21
+ > Ce SDK est un client **serveur** (Node.js ≥ 18). Il n'y a pas de build navigateur/CDN —
22
+ > pour le paiement côté client, redirigez vers l'URL de checkout hébergée retournée par l'API
23
+ > (`session.url` / `intent.url`), comme documenté ci-dessous.
24
+
25
+ ---
26
+
27
+ ## Clés API
28
+
29
+ | Préfixe | Environnement | Usage |
30
+ |---------------|---------------|--------------------------------|
31
+ | `sk_prod_` | Production | Serveur uniquement |
32
+ | `sk_test_` | Sandbox | Serveur uniquement (tests) |
33
+ | `pk_prod_` | Production | Navigateur (checkout public) |
34
+ | `pk_test_` | Sandbox | Navigateur (checkout tests) |
35
+
36
+ > ⚠️ **Ne jamais exposer `sk_prod_` ou `sk_test_` dans du code client (navigateur, app mobile).**
37
+
38
+ ---
39
+
40
+ ## Démarrage rapide
41
+
42
+ ```typescript
43
+ import { Sangho } from "@sanghosdk/js"
44
+
45
+ // Initialisation (côté serveur — clé secrète)
46
+ const sangho = new Sangho("sk_prod_xxxxxxxxxxxxxxxxxxxxxxxxxxxx")
47
+
48
+ // Créer un client
49
+ const customer = await sangho.customers.create({
50
+ email: "jean.ondo@example.ga",
51
+ name: "Jean Ondo",
52
+ phone: "+24107000001",
53
+ currency: "XAF",
54
+ })
55
+
56
+ // Créer un PaymentIntent
57
+ const intent = await sangho.paymentIntents.create({
58
+ amount: 50_000, // 500.00 XAF (en centimes)
59
+ currency: "XAF",
60
+ customer: customer.id,
61
+ description: "Commande #1234",
62
+ })
63
+
64
+ // Confirmer le paiement
65
+ const confirmed = await sangho.paymentIntents.confirm(intent.id, {
66
+ payment_method: "meth_xxx",
67
+ })
68
+
69
+ console.log(confirmed.status) // "succeeded" | "requires_action" | ...
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Modules disponibles
75
+
76
+ ### Customers
77
+
78
+ ```typescript
79
+ // Créer
80
+ const customer = await sangho.customers.create({ email, name, phone, currency })
81
+
82
+ // Récupérer
83
+ const customer = await sangho.customers.retrieve("cust_xxx")
84
+
85
+ // Mettre à jour
86
+ const customer = await sangho.customers.update("cust_xxx", { phone: "+24107000002" })
87
+
88
+ // Supprimer
89
+ await sangho.customers.delete("cust_xxx")
90
+
91
+ // Lister (avec filtres)
92
+ const { data, count } = await sangho.customers.list({
93
+ page: 1,
94
+ page_size: 20,
95
+ status: "active",
96
+ currency: "XAF",
97
+ })
98
+
99
+ // Transactions d'un client
100
+ const txns = await sangho.customers.listTransactions("cust_xxx")
101
+
102
+ // Modes de paiement d'un client
103
+ const methods = await sangho.customers.listPaymentMethods("cust_xxx")
104
+ ```
105
+
106
+ ### Products
107
+
108
+ ```typescript
109
+ const product = await sangho.products.create({
110
+ name: "Abonnement Premium",
111
+ type: "subscription",
112
+ unit_amount: 15_000,
113
+ currency: "XAF",
114
+ })
115
+
116
+ await sangho.products.update(product.id, { name: "Abonnement Premium+" })
117
+
118
+ await sangho.products.delete("prod_xxx") // archive côté backend (soft delete)
119
+ ```
120
+
121
+ ### Payment Intents
122
+
123
+ ```typescript
124
+ // Créer et confirmer en une étape
125
+ const intent = await sangho.paymentIntents.create({
126
+ amount: 10_000,
127
+ currency: "XAF",
128
+ customer: "cust_xxx",
129
+ confirm: true,
130
+ payment_method: "meth_xxx",
131
+ })
132
+
133
+ // Capture manuelle
134
+ await sangho.paymentIntents.capture("pi_xxx", { amount_to_capture: 8_000 })
135
+
136
+ // Annuler
137
+ await sangho.paymentIntents.cancel("pi_xxx", {
138
+ cancellation_reason: "requested_by_customer",
139
+ })
140
+ ```
141
+
142
+ ### Transactions
143
+
144
+ ```typescript
145
+ // Lecture seule
146
+ const txn = await sangho.transactions.retrieve("trans_xxx")
147
+
148
+ const { data } = await sangho.transactions.list({
149
+ status: "succeeded",
150
+ currency: "XAF",
151
+ created_after: "2024-01-01T00:00:00Z",
152
+ min_amount: 1_000,
153
+ })
154
+ ```
155
+
156
+ ### Refunds
157
+
158
+ ```typescript
159
+ // Remboursement partiel
160
+ const refund = await sangho.refunds.create({
161
+ transaction: "trans_xxx",
162
+ amount: 5_000,
163
+ reason: "requested_by_customer",
164
+ })
165
+
166
+ await sangho.refunds.cancel("refd_xxx")
167
+ ```
168
+
169
+ ### Invoices
170
+
171
+ ```typescript
172
+ const invoice = await sangho.invoices.create({
173
+ customer: "cust_xxx",
174
+ currency: "XAF",
175
+ line_items: [
176
+ { description: "Consultation", quantity: 2, unit_amount: 25_000 },
177
+ { description: "Frais de déplacement", quantity: 1, unit_amount: 10_000 },
178
+ ],
179
+ tax_rate: 18, // 18% TVA
180
+ due_date: "2024-12-31",
181
+ })
182
+
183
+ await sangho.invoices.send(invoice.id)
184
+
185
+ // Télécharger le PDF
186
+ const { url } = await sangho.invoices.getPdfUrl(invoice.id)
187
+ ```
188
+
189
+ ### Payment Links
190
+
191
+ ```typescript
192
+ const link = await sangho.paymentLinks.create({
193
+ currency: "XAF",
194
+ line_items: [{ product: "prod_xxx", quantity: 1 }],
195
+ success_url: "https://monsite.com/merci",
196
+ usage_limit: 100,
197
+ })
198
+
199
+ console.log(link.url) // https://checkout.sangho.ga/pay/link_xxx
200
+ ```
201
+
202
+ ### Checkout Sessions
203
+
204
+ ```typescript
205
+ const session = await sangho.checkoutSessions.create({
206
+ mode: "payment",
207
+ currency: "XAF",
208
+ line_items: [{ product: "prod_xxx", quantity: 1 }],
209
+ success_url: "https://monsite.com/success",
210
+ cancel_url: "https://monsite.com/cancel",
211
+ expires_in: 3600, // 1 heure
212
+ })
213
+
214
+ // Rediriger le client vers session.url
215
+ ```
216
+
217
+ ### Subscriptions
218
+
219
+ ```typescript
220
+ const sub = await sangho.subscriptions.create({
221
+ customer: "cust_xxx",
222
+ currency: "XAF",
223
+ unit_amount: 15_000,
224
+ interval: "month",
225
+ trial_period_days: 14,
226
+ })
227
+
228
+ await sangho.subscriptions.pause("sub_xxx")
229
+ await sangho.subscriptions.resume("sub_xxx")
230
+ await sangho.subscriptions.cancel("sub_xxx", { cancel_at_period_end: true })
231
+ ```
232
+
233
+ ### Webhooks
234
+
235
+ ```typescript
236
+ const webhook = await sangho.webhooks.create({
237
+ url: "https://monserveur.com/webhooks/sangho",
238
+ events: [
239
+ "payment_intent.succeeded",
240
+ "payment_intent.payment_failed",
241
+ "customer.created",
242
+ "invoice.paid",
243
+ ],
244
+ })
245
+
246
+ // Régénérer le secret
247
+ const { secret } = await sangho.webhooks.rollSecret(webhook.id)
248
+
249
+ // Voir les livraisons
250
+ const deliveries = await sangho.webhooks.listDeliveries(webhook.id, {
251
+ status: "failed",
252
+ })
253
+
254
+ // Rejouer une livraison
255
+ await sangho.webhooks.retryDelivery(webhook.id, "wdl_xxx")
256
+ ```
257
+
258
+ ### Vérification des signatures webhook
259
+
260
+ ```typescript
261
+ import { Sangho } from "@sanghosdk/js"
262
+
263
+ // Express
264
+ app.post(
265
+ "/webhooks/sangho",
266
+ express.raw({ type: "application/json" }),
267
+ async (req, res) => {
268
+ try {
269
+ const event = await Sangho.constructEvent(
270
+ req.body,
271
+ req.headers["sangho-signature"] as string,
272
+ process.env.SANGHO_WEBHOOK_SECRET!
273
+ )
274
+
275
+ switch (event.type) {
276
+ case "payment_intent.succeeded":
277
+ await handleSuccessfulPayment(event.data)
278
+ break
279
+ case "invoice.paid":
280
+ await markInvoicePaid(event.data)
281
+ break
282
+ }
283
+
284
+ res.json({ received: true })
285
+ } catch (err) {
286
+ res.status(400).send(`Webhook error: ${err.message}`)
287
+ }
288
+ }
289
+ )
290
+ ```
291
+
292
+ ---
293
+
294
+ ## Gestion des erreurs
295
+
296
+ ```typescript
297
+ import {
298
+ Sangho,
299
+ SanghoAuthError,
300
+ SanghoValidationError,
301
+ SanghoNotFoundError,
302
+ SanghoRateLimitError,
303
+ } from "@sanghosdk/js"
304
+
305
+ try {
306
+ const customer = await sangho.customers.create({ email: "invalid" })
307
+ } catch (err) {
308
+ if (err instanceof SanghoValidationError) {
309
+ console.error("Erreurs de validation:", err.fieldErrors)
310
+ // { email: ["Enter a valid email address."] }
311
+ } else if (err instanceof SanghoAuthError) {
312
+ console.error("Clé API invalide ou expirée")
313
+ } else if (err instanceof SanghoNotFoundError) {
314
+ console.error("Ressource introuvable")
315
+ } else if (err instanceof SanghoRateLimitError) {
316
+ console.error(`Limite de taux dépassée. Réessayez dans ${err.retryAfter}s`)
317
+ } else if (err instanceof SanghoError) {
318
+ console.error(err.type); // Catégorie — ex. 'VALIDATION_ERROR'
319
+ console.error(err.code); // Code métier précis — ex. 'AMOUNT_TOO_SMALL'
320
+ console.error(err.statusCode); // Code HTTP
321
+ } else {
322
+ throw err
323
+ }
324
+ }
325
+ ```
326
+
327
+ ---
328
+
329
+ ## Options avancées
330
+
331
+ ```typescript
332
+ const sangho = new Sangho("sk_test_xxx", {
333
+ timeout: 10_000, // Timeout en ms (défaut : 30 000)
334
+ maxRetries: 5, // Nombre de retries auto (défaut : 3)
335
+ baseURL: "https://api.staging.sangho.ga/v1", // URL custom (staging)
336
+ })
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Sécurité
342
+
343
+ - La clé API est transmise uniquement via le header `Authorization: Bearer`
344
+ - Chaque requête POST génère automatiquement une `Idempotency-Key` unique (UUID v4)
345
+ - Les retries auto n'ont lieu que pour les erreurs `429` et `5xx` (jamais `4xx`), en respectant `Retry-After` pour les `429`
346
+ - La vérification de signature webhook utilise HMAC-SHA256 avec protection anti-replay (5 min)
347
+ - Les clés publiques (`pk_`) sont rejetées côté SDK si utilisées pour des opérations réservées aux clés secrètes (`sk_`)
348
+
349
+ ---
350
+
351
+ ## Compatibilité
352
+
353
+ Ce SDK est un client **serveur** — il n'y a pas de build navigateur/UMD ni de CDN.
354
+
355
+ | Environnement | Support |
356
+ |---|---|
357
+ | Node.js ≥ 18 | ✅ natif (ESM + CJS) |
358
+ | Deno | ✅ via `npm:` |
359
+ | Bun | ✅ |
360
+ | TypeScript ≥ 5.0 | ✅ types complets |
361
+
362
+ ---
363
+
364
+ ## Licence
365
+
366
+ MIT © [Sangho](https://sangho.ga)