@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 +57 -0
- package/README.md +366 -0
- package/dist/index.cjs +1395 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2559 -0
- package/dist/index.d.ts +2559 -0
- package/dist/index.js +1379 -0
- package/dist/index.js.map +1 -0
- package/package.json +82 -0
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
|
+
[](https://www.npmjs.com/package/@sanghosdk/js)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](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)
|