# Documentation du SDK TresorPay Node

SDK Node.js/TypeScript pour l'API TresorPay (paiements, mobile money, cartes bancaires). Ce document couvre l'intégralité des fonctionnalités exposées par le paquet `tresorpay`.

## Sommaire

- [Installation](#installation)
- [Configuration](#configuration)
- [Environnements](#environnements)
- [Le pattern des ressources](#le-pattern-des-ressources)
- [Ressources disponibles](#ressources-disponibles)
  - [Customer](#customer)
  - [Transaction](#transaction)
  - [Account](#account)
  - [ApiKey](#apikey)
  - [Currency](#currency)
  - [Event](#event)
  - [Log](#log)
  - [PhoneNumber](#phonenumber)
  - [Webhook](#webhook)
- [TresorPayObject](#tresorpayobject)
- [Gestion des erreurs](#gestion-des-erreurs)
- [Client HTTP personnalisé et intercepteurs](#client-http-personnalisé-et-intercepteurs)
- [Tester une intégration](#tester-une-intégration)
- [Ce que le SDK ne fait pas](#ce-que-le-sdk-ne-fait-pas)

## Installation

```bash
npm install tresorpay --save
```

```js
import { TresorPay, Customer } from 'tresorpay';

TresorPay.setApiKey('sk_sandbox_xxx');

let customers = await Customer.all();
```

## Configuration

Toute la configuration globale du SDK passe par la classe statique `TresorPay`.

| Méthode | Rôle |
|---|---|
| `TresorPay.setApiKey(key: string)` | Définit la clé secrète utilisée pour authentifier les requêtes (`Authorization: Bearer <key>`). Réinitialise aussi le token OAuth interne. |
| `TresorPay.getApiKey()` | Retourne la clé actuellement configurée. |
| `TresorPay.setToken(token: string)` | Définit un token d'accès (alternative à la clé API, utilisé si aucune clé API n'est définie). |
| `TresorPay.getToken()` | Retourne le token actuel. |
| `TresorPay.setAccountId(id: string \| number)` | Définit l'identifiant de compte à cibler (utile en contexte multi-comptes) ; envoyé via le header `TresorPay-Account`. |
| `TresorPay.getAccountId()` | Retourne l'identifiant de compte configuré. |
| `TresorPay.setEnvironment(env: string)` | Définit l'environnement cible : `'sandbox'` (par défaut) ou `'production'`/`'live'`. Voir [Environnements](#environnements). |
| `TresorPay.getEnvironment()` | Retourne l'environnement actuel. |
| `TresorPay.setApiBase(url: string)` | Force une URL de base personnalisée, prioritaire sur l'environnement. Utile pour pointer vers un proxy ou un environnement de test local. |
| `TresorPay.getApiBase()` | Retourne l'URL de base personnalisée (ou vide si non définie). |
| `TresorPay.setApiVersion(version: string)` | Définit la version d'API utilisée dans le chemin des requêtes (par défaut `'v1'`). |
| `TresorPay.getApiVersion()` | Retourne la version d'API actuelle. |
| `TresorPay.setVerifySslCerts(value: boolean)` | Indique si la vérification des certificats SSL doit être activée (par défaut `true`). |
| `TresorPay.getVerifySslCerts()` | Retourne l'état actuel de ce réglage. |
| `TresorPay.VERSION` | Constante : version interne du SDK, envoyée dans le header `X-Version`. |

## Environnements

Le SDK cible par défaut le **sandbox**. Deux environnements sont supportés :

```js
TresorPay.setEnvironment('sandbox');    // https://sandbox.tresorpay.bj (par défaut)
TresorPay.setEnvironment('production'); // https://prod.tresorpay.bj
```

- `'sandbox'` et `'test'` pointent vers `https://sandbox.tresorpay.bj`.
- `'production'` et `'live'` pointent vers `https://prod.tresorpay.bj`.
- Toute valeur non reconnue (y compris si `setEnvironment` n'a jamais été appelé) retombe sur le sandbox, par sécurité.
- `TresorPay.setApiBase(url)` prend le pas sur l'environnement si définie — pratique pour rediriger vers un mock local pendant les tests.

Il n'existe pas d'environnement `dev` distinct dans TresorPay SDK.

## Le pattern des ressources

Toutes les ressources métier (`Customer`, `Transaction`, `Account`, etc.) héritent d'une classe `Resource` commune, qui fournit un jeu d'opérations CRUD génériques exposées différemment selon la ressource (voir tableau par ressource ci-dessous) :

- `Resource.all(params?, headers?)` — liste les enregistrements. Retourne un `TresorPayObject` contenant un tableau (`object.customers`, `object.transactions`, etc.) et des métadonnées de pagination (`object.meta`).
- `Resource.retrieve(id, params?, headers?)` — récupère un enregistrement par son identifiant.
- `Resource.create(params, headers?)` — crée un enregistrement.
- `Resource.update(id, params?, headers?)` — met à jour un enregistrement par son identifiant.
- `instance.save(headers?)` — met à jour l'instance courante avec les champs modifiés localement (basé sur `serializeParameters()`).
- `instance.delete(headers?)` — supprime l'instance courante.

Chaque ressource expose ces méthodes avec un typage de retour spécifique (`Promise<Customer>`, `Promise<Transaction>`, etc.), mais le comportement HTTP sous-jacent est identique pour toutes.

Le nom d'URL de chaque ressource est dérivé automatiquement de son nom (mis au pluriel) : `customer` → `/customers`, `api_key` → `/api_keys`, `currency` → `/currencies`, etc.

## Ressources disponibles

### Customer

```js
import { Customer } from 'tresorpay';

await Customer.all();
await Customer.retrieve(id);
await Customer.create({ firstname, lastname, email, phone });
await Customer.update(id, { firstname: 'Nouveau nom' });

let customer = await Customer.retrieve(id);
customer.firstname = 'Autre nom';
await customer.save();
await customer.delete();
```

Champs : `id`, `firstname`, `lastname`, `email`, `phone`, `created_at`, `updated_at`.

### Transaction

Ressource la plus riche du SDK — couvre la création de transactions (encaissement) et l'encaissement mobile money.

```js
import { Transaction } from 'tresorpay';

let transaction = await Transaction.create({
    description: 'Paiement de service en ligne',
    amount: 1000,
    currency: { iso: 'XOF' },
    custom_metadata: { commande_id: '123' }
});

await Transaction.all();
await Transaction.retrieve(id);
await Transaction.update(id, { description: 'Nouvelle description' });
await transaction.save();
await transaction.delete();
```

> Toutes les opérations sur `Transaction` (`create`, `retrieve`, `all`, `update`, `delete`, `save`) passent par l'endpoint dédié `/deposits`, sans préfixe de version (confirmé par du trafic de production : `POST /deposits`, `GET /deposits/{id}`) — contrairement aux autres ressources qui utilisent `/v1/<ressource>`. C'est un détail d'implémentation interne : l'appel côté SDK reste identique, seule l'URL réellement appelée diffère. Les sous-actions `generateToken()` (`/deposits/{id}/token`) et `getFees()` (`/deposits/fees`) suivent la même convention non versionnée ; `sendNowWithToken()` (`/mtn`, `/moov`, etc.) n'a pas été vérifié contre du trafic réel et reste inchangé.

Dès la création, la réponse contient directement `payment_token` et `payment_url` — un lien de paiement immédiatement utilisable, sans appel supplémentaire. Le token est un JWT valide 24h (`exp` = `created_at` + 24h, vérifié sur un exemple réel) :

```js
transaction.payment_token; // JWT du paiement
transaction.payment_url;   // ex. https://pay.tresorpay.bj/<token> — à rediriger le client vers cette URL
```

**Méthodes d'état :**

```js
transaction.wasPaid();              // true si status ∈ ['approved', 'transferred', 'refunded', 'approved_partially_refunded', 'transferred_partially_refunded']
transaction.wasRefunded();          // true si le status contient 'refunded'
transaction.wasPartiallyRefunded(); // true si le status contient 'partially_refunded'
```

**Encaissement mobile money (flux alternatif, sans passer par `payment_url`) :**

```js
// Réutilise transaction.payment_token s'il est présent ; ne génère un nouveau
// token via generateToken() que s'il est absent (ex. token expiré après 24h).
await transaction.sendNow('mtn', { phone_number: '22900000000' });

// Équivalent manuel, si vous gérez le token vous-même :
const tokenObject = await transaction.generateToken();
await transaction.sendNowWithToken('mtn', tokenObject.token, { phone_number: '22900000000' });
```

**Calcul des frais :**

```js
await transaction.getFees(token, 'mtn');
```

Champs observés en production : `id`, `reference`, `description`, `callback_url`, `amount`, `status`, `operation`, `transaction_id`, `customer_id`, `currency_id`, `account_id`, `balance_id`, `mode`, `metadata`, `custom_metadata`, `commission`, `fees`, `fixed_commission`, `amount_transferred`, `amount_debited`, `receipt_url`, `payment_method_id`, `sub_accounts_commissions`, `transaction_key`, `merchant_reference`, `payment_token`, `payment_url`, `flags`, `last_error_code`, `created_at`, `updated_at`, `approved_at`, `canceled_at`, `declined_at`, `refunded_at`, `transferred_at`, `to_be_transferred_at`, `deleted_at`.

### Account

```js
import { Account } from 'tresorpay';

await Account.all();
await Account.retrieve(id);
await Account.create({ name: 'Ma boutique', country: 'BJ' });
await Account.update(id, { name: 'Nouveau nom' });

let account = await Account.retrieve(id);
await account.save();
await account.delete();
```

Champs : `id`, `name`, `timezone`, `country`, `verify`, `created_at`, `updated_at`.

### ApiKey

```js
import { ApiKey } from 'tresorpay';

await ApiKey.all();
await ApiKey.retrieve(id);
```

Ressource simple, sans méthodes spécifiques au-delà des opérations CRUD génériques héritées de `Resource`. Champs : `id`, `public_key`, `private_key`, `created_at`, `updated_at`.

### Currency

```js
import { Currency } from 'tresorpay';

await Currency.all();
await Currency.retrieve(id);
```

Ressource en lecture seule côté SDK (pas de `create`/`update`/`delete` exposés). Champs : `id`, `name`, `iso`, `code`, `prefix`, `suffix`, `div`, `created_at`, `updated_at`.

### Event

```js
import { Event } from 'tresorpay';

await Event.all();
await Event.retrieve(id);
await Event.subscribe({ url: 'https://monsite.com/events' });
```

`subscribe()` inscrit un endpoint pour recevoir un flux d'événements. Champs : `id`, `type`, `entity`, `object_id`, `account_id`, `object`, `created_at`, `updated_at`.

### Log

```js
import { Log } from 'tresorpay';

await Log.all();
await Log.retrieve(id);
await Log.subscribe({ url: 'https://monsite.com/logs' });
```

Journal des requêtes effectuées sur le compte. Champs : `id`, `method`, `url`, `status`, `ip_address`, `version`, `source`, `query`, `body`, `response`, `account_id`, `created_at`, `updated_at`.

### PhoneNumber

```js
import { PhoneNumber } from 'tresorpay';

await PhoneNumber.all();
await PhoneNumber.retrieve(id);
```

Ressource simple, opérations CRUD génériques uniquement. Champs : `id`, `number`, `country`, `created_at`, `updated_at`.

### Webhook

```js
import { Webhook } from 'tresorpay';

await Webhook.create({ url: 'https://monsite.com/webhook' });
await Webhook.all();
await Webhook.retrieve(id);
await Webhook.update(id, { url: 'https://monsite.com/nouveau-webhook' });

let webhook = await Webhook.retrieve(id);
await webhook.save();
await webhook.delete();

// Déclenche un événement de test contre l'endpoint enregistré
await Webhook.stubEvent({ type: 'transaction.created' });
await webhook.sendEvent({ type: 'transaction.created' });
```

**Vérification de signature** (à utiliser côté serveur, quand vous recevez un webhook) :

```js
import { Webhook } from 'tresorpay';

const event = Webhook.constructEvent(
    rawRequestBody,          // corps brut de la requête reçue (string)
    request.headers['tresorpay-signature'],
    'wh_votre_secret_webhook'
);
```

`constructEvent` vérifie la signature HMAC-SHA256 du payload (avec une tolérance de 300 secondes par défaut) et lève une `SignatureVerificationError` si la signature est invalide, absente, ou trop ancienne. En cas de succès, il retourne le payload JSON parsé.

Champs : `id`, `url`, `created_at`, `updated_at`.

## TresorPayObject

Toutes les réponses de l'API sont converties en instances de `TresorPayObject` (ou d'une sous-classe de ressource typée quand la réponse identifie une ressource connue via son champ `klass`). C'est un objet dynamique :

```js
import { TresorPayObject } from 'tresorpay';

let object = new TresorPayObject({ foo: 'value' });
object.foo;                     // 'value'
object.serializeParameters();   // { foo: 'value' } — exclut automatiquement `id` et les méthodes
JSON.stringify(object);         // sérialisation JSON standard
```

Les listes (`Resource.all()`) retournent un `TresorPayObject` racine avec une propriété nommée d'après la ressource au pluriel (ex. `object.customers`, un tableau d'instances) et une propriété `object.meta` (pagination).

## Gestion des erreurs

Trois types d'erreurs peuvent être levées, toutes exportées depuis le SDK :

```js
import { ApiConnectionError, InvalidRequest, SignatureVerificationError } from 'tresorpay';

try {
    await Customer.create({});
} catch (e) {
    if (e instanceof ApiConnectionError) {
        e.httpStatus;     // code HTTP retourné (ou null si erreur réseau)
        e.hasErrors();     // true si la réponse contient un détail d'erreurs de validation
        e.errorMessage;    // message d'erreur renvoyé par l'API
        e.errors;          // détail des erreurs de validation par champ
    }
}
```

- `ApiConnectionError` — toute requête HTTP qui échoue (erreur réseau ou réponse en erreur de l'API).
- `InvalidRequest` — paramètres invalides passés localement (ex. objet attendu, ID manquant pour construire une URL de ressource).
- `SignatureVerificationError` — échec de vérification de signature d'un webhook (`Webhook.constructEvent`).

## Client HTTP personnalisé et intercepteurs

Le SDK utilise `axios` en interne, mais permet d'injecter un client personnalisé ou d'intercepter chaque requête sortante :

```js
import { Requestor } from 'tresorpay';
import axios from 'axios';

// Remplacer le client HTTP interne (ex. pour du proxying, du logging custom, etc.)
Requestor.setHttpClient(axios.create({ timeout: 5000 }));

// Intercepter chaque requête avant envoi
Requestor.addRequestInterceptor({
    callback: (config) => {
        console.log('Requête sortante :', config.method, config.url);
        return config;
    },
    onRejected: (error) => Promise.reject(error)
});
```

## Tester une intégration

Le SDK est conçu pour être facilement mocké avec [`nock`](https://github.com/nock/nock) dans vos propres tests :

```js
import * as nock from 'nock';
import { TresorPay, Customer } from 'tresorpay';

TresorPay.setApiKey('sk_test_123');

nock('https://sandbox.tresorpay.bj')
    .get('/v1/customers')
    .reply(200, {
        'v1/customers': [{ id: 1, klass: 'v1/customer', firstname: 'Ada' }],
        meta: { page: 1 }
    });

const result = await Customer.all();
result.customers[0].firstname; // 'Ada'
```

## Ce que le SDK ne fait pas

TresorPay SDK ne propose **pas** de fonctionnalités de déboursement (versement/retrait de fonds vers un compte externe) : il n'existe pas de ressource `Payout` ni `Balance`. Toutes les autres fonctionnalités décrites ci-dessus (encaissement, gestion clients, transactions, webhooks, etc.) sont disponibles.
