Aller au contenu

Java

L'artefact wajub-java de com.wajub, ses services et ses exceptions non vérifiées.

wajub-java constitue la partie serveur d'une intégration Wajub. Il conserve votre clé secrète, crée des paiements, lit leur statut réel et vérifie les signatures des webhooks. Il utilise OkHttp pour le transport et Jackson pour le JSON, rien d'autre.

com.wajub:wajub-java

Stable · GA

Maven Central

Version
1.1.1
Runtime
Java 17+, OkHttp 4.12, Jackson 2.18

Couvre

  • Paiements
  • Facturation
  • Transferts
  • Sync
  • Shield
  • Taxes

Installation

<dependency>
  <groupId>com.wajub</groupId>
  <artifactId>wajub-java</artifactId>
  <version>1.1.1</version>
</dependency>

L'artefact est compilé avec --release 17. Java 17 est donc la version minimale, et toute version plus récente peut l'exécuter. Il installe transitivement OkHttp 4.12.0 et jackson-databind 2.18.2. Si votre application fixe déjà leur version, les règles habituelles de gestion des dépendances s'appliquent.

Créer le client

Wajub.create est le point d'entrée. Il utilise les variables d'environnement en repli. La forme courte suffit donc dans la plupart des applications.

Deux façons de le construire
import com.wajub.Wajub;

// Reads WAJUB_API_KEY and WAJUB_WEBHOOK_SECRET when the field is blank.
Wajub wajub = Wajub.create(Wajub.Config.builder().build());

// Or spell everything out.
Wajub wajub = Wajub.create(
    Wajub.Config.builder()
        .apiKey(System.getenv("WAJUB_API_KEY"))
        .webhookSecret(System.getenv("WAJUB_WEBHOOK_SECRET"))
        .build());

// Or pass just the key.
Wajub wajub = Wajub.create(System.getenv("WAJUB_API_KEY"));
Méthode du builderRôle
apiKeyVotre clé sk. ou sk_test.. Utilise WAJUB_API_KEY en repli
webhookSecretLe secret whsec_. Utilise WAJUB_WEBHOOK_SECRET en repli
idempotencyKeyPrefixPréfixe de la clé générée. Valeur par défaut : "wajub"
httpClientVotre propre OkHttpClient, pour un intercepteur, un proxy ou le traçage

Une clé vide déclenche IllegalArgumentException: wajub: api key is required lors de la construction. Une mauvaise configuration échoue donc au démarrage, pas lors du premier paiement.

Sous forme de bean Spring
@Configuration
public class WajubConfiguration {

    @Bean
    Wajub wajub(@Value("${wajub.api-key}") String apiKey,
                @Value("${wajub.webhook-secret}") String webhookSecret) {
        return Wajub.create(
            Wajub.Config.builder()
                .apiKey(apiKey)
                .webhookSecret(webhookSecret)
                .build());
    }
}

Le client est sûr pour les accès concurrents et contient un pool de connexions OkHttp. Un singleton pour toute l'application est donc le bon choix.

Le premier appel

Créer un paiement
import com.wajub.Payment;

Payment payment = wajub.payments().create(
    Map.of(
        "amount", 25000,
        "currency", "XAF",
        "email", "amina@example.com",
        "description", "Order 4172",
        "reference", "order-4172",
        "callback", "https://shop.example.com/complete"),
    null);

return "redirect:" + payment.getAuthorizationUrl();

Les ressources sont des méthodes, pas des champs. Utilisez donc wajub.payments() et non wajub.payments. Les paramètres sont transmis dans un Map<String, Object> avec les noms de champs exacts de l'API.

Tout ce qui dépasse les six champs décodés
String reference = (String) payment.getRaw().get("reference");
Number amount = (Number) payment.getRaw().get("amount");

Les montants utilisent l'unité principale

25000 avec XAF représente vingt-cinq mille francs, pas deux cent cinquante.

Tous les services du client

ServiceMéthodes
client.global()ping, channels, countries, currencies
client.payments()create, initialize, retrieve, list, cancel, process, processSplit, listRefunds
client.customers()create, retrieve, update, delete, list, block, unblock, activate, deactivate, listTaxIds, createTaxId, deleteTaxId
client.refunds()create, retrieve, list
client.transfers()create, retrieve, list
client.beneficiaries()create, retrieve, update, delete, list
client.links()create, retrieve, update, delete, list
client.invoices()create, retrieve, update, delete, list, send, markPaid, cancel
client.accounts()create, retrieve, update, delete, list, regenerateToken
client.webhookEndpoints()create, retrieve, update, delete, list, rotateSecret
client.balance()retrieve
client.events()list, retrieve, resend
client.disputes()list, retrieve, submitEvidence, accept, close, sendMessage
client.identity()resolve, validate
client.tax()getSettings, updateSettings, rates, calculate, reports, listCodes, retrieveCode, listRegistrations, createRegistration, retrieveRegistration, updateRegistration, deleteRegistration, jurisdictions, thresholds, thresholdAlerts
client.shield()getSettings, updateSettings, stats, listBlocklist, addToBlocklist, removeFromBlocklist
client.listen()config, auth
client.webhooks()constructEvent
  • webhooks / ConstructEvent runs locally, no HTTP call. All other services call the merchant REST API.
  • links, invoices, tax and shield are live only. A sandbox key gets 403 This feature is only available in live mode. on every one of their methods.
  • refunds and transfers are create, retrieve and list only. The shared CRUD base also exposes update and delete on them, but the API serves no such route.

Parcourir une liste par pages

Une page ou toutes les pages
PagedResult page = wajub.payments().list(Map.of("status", "success", "per_page", 50));

for (Map<String, Object> payment : page.getData()) {
    log.info("{} {}", payment.get("id"), payment.get("amount"));
}

if (page.hasMore()) {
    page = page.getNextPage();
}

// Or collect every page at once.
List<Map<String, Object>> all = page.autoPaging();

Les lignes sont des Map<String, Object>, pas des objets typés. autoPaging() simplifie le parcours, mais consomme plus de mémoire, car il conserve toutes les lignes avant de les renvoyer.

Idempotence et nouvelles tentatives

Chaque requête POST et PUT contient une Idempotency-Key générée. Cette clé sécurise les nouvelles tentatives automatiques.

Enchaîner les RequestOptions
Payment payment = wajub.payments().create(
    params,
    new RequestOptions().setIdempotencyKey("order-" + orderId));
ÉlémentValeur
Statuts concernés429, 500, 502, 503, 504 et toute erreur réseau
Tentatives3 au total, l'appel initial et deux nouvelles tentatives
Attente progressiveExponentielle avec une part d'aléa (jitter), en respectant Retry-After pour une erreur 429
Délai d'expiration30 secondes pour la connexion, la lecture et l'écriture

Le nombre de nouvelles tentatives est fixe. Pour modifier les délais d'expiration, fournissez votre propre OkHttpClient au builder.

Votre propre client OkHttp
OkHttpClient http = new OkHttpClient.Builder()
    .connectTimeout(Duration.ofSeconds(10))
    .readTimeout(Duration.ofSeconds(60))
    .addInterceptor(new TracingInterceptor())
    .build();

Wajub wajub = Wajub.create(Wajub.Config.builder().httpClient(http).build());

Agir pour un compte connecté

Un appel pour un vendeur
wajub.payments().create(
    params,
    new RequestOptions().setSync(seller.getWajubAccountId()));

setSync devient l'en-tête X-Sync. La configuration est présentée dans Sync.

Webhooks

constructEvent vérifie la signature et renvoie l'événement analysé. Il accepte le corps sous forme d'octets, exactement tel qu'il a été livré.

Un contrôleur Spring Boot
@PostMapping(value = "/webhooks/wajub", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> wajubWebhook(
        @RequestBody byte[] body,
        @RequestHeader("X-Wajub-Signature") String signature,
        @RequestHeader("X-Wajub-Timestamp") String timestamp) {

    Map<String, Object> event;
    try {
        event = wajub.webhooks().constructEvent(body, signature, timestamp, null);
    } catch (WebhookSignatureVerificationException e) {
        return ResponseEntity.badRequest().build();
    }

    if ("payment.succeeded".equals(event.get("event"))) {
        fulfilmentQueue.submit(event);
    }

    return ResponseEntity.ok().build();
}

Une tolérance null applique la fenêtre par défaut de 300 secondes, elle ne supprime pas la vérification. Il est volontairement impossible de désactiver la protection contre les rejeux.

Le nom de l'événement se trouve dans event, pas dans type, et le résultat est un Map<String, Object>. Consultez Vérification de signature.

Erreurs

Intercepter d'abord l'erreur précise
import com.wajub.exception.*;

try {
    wajub.payments().create(params, null);
} catch (InvalidRequestException e) {
    // e.getErrors() is {"amount": "The amount must be at least 25."}
    return ResponseEntity.unprocessableEntity().body(e.getErrors());
} catch (RateLimitException e) {
    return ResponseEntity.status(503)
        .header("Retry-After", String.valueOf(e.getRetryAfter()))
        .build();
} catch (WajubException e) {
    log.error("wajub failed code={} status={}", e.getCode(), e.getHttpStatus());
    throw e;
}
Classe dans com.wajub.exceptionCas de déclenchement
AuthenticationException401
PermissionException403
NotFoundException404
InvalidRequestException400 et 422
RateLimitException429, avec getRetryAfter() en secondes
WajubExceptionTout autre statut. C'est aussi la classe parente de toutes les exceptions ci-dessus
ApiConnectionExceptionAucune réponse, à cause du réseau ou d'un délai d'expiration
WebhookSignatureVerificationExceptionUn webhook dont la signature n'a pas été vérifiée

Toutes exposent getMessage(), getCode(), getHttpStatus(), getErrors() et getRaw().

getErrors() renvoie un Map<String, String>, avec un message par champ, alors que l'API envoie un tableau par champ. Seul le premier message de chaque champ est conservé lors du mapping.

Tester sans appeler l'API

L'URL de base est une constante qui ne peut pas être remplacée par une variable d'environnement. Vous ne pouvez donc pas la faire pointer vers un serveur simulé. Le point d'interception est OkHttpClient : un intercepteur qui répond avant le départ de l'appel permet d'utiliser tout le SDK hors ligne.

Un test JUnit sans réseau
Interceptor stub = chain -> new Response.Builder()
    .request(chain.request())
    .protocol(Protocol.HTTP_1_1)
    .code(201)
    .message("Created")
    .body(ResponseBody.create("""
        {"code":201,
         "authorization_url":"https://pay.wajub.com/tok_test",
         "authorization_token":"tok_test",
         "transaction":{"id":"trx_test","status":"pending","amount":25000}}
        """, MediaType.get("application/json")))
    .build();

Wajub wajub = Wajub.create(
    Wajub.Config.builder()
        .apiKey("sk_test.fake")
        .httpClient(new OkHttpClient.Builder().addInterceptor(stub).build())
        .build());

Payment payment = wajub.payments().create(Map.of("amount", 25000, "currency", "XAF"), null);

assertEquals("tok_test", payment.getAuthorizationToken());

Seul le SDK Node.js lit WAJUB_API_URL

@wajub/node permet à cette variable de rediriger l'URL de base, ce qui est pratique avec une stack locale. Java, Python, PHP, Go, Ruby et C# utilisent tous https://api.wajub.com comme constante. Pour les intercepter, injectez un transport comme dans l'exemple ci-dessus.

Débiter sans page hébergée

Requête push Mobile Money
Payment payment = wajub.payments().create(
    Map.of("amount", 25000, "currency", "XAF", "phone", "+237670000000"),
    null);

wajub.payments().process(
    payment.getId(),
    Map.of("channel", "cm.mtn", "phone", "+237670000000"),
    null);

Un canal suit le format country.operator. Ainsi, cm.mtn désigne MTN Mobile Money au Cameroun. La liste complète se trouve dans Moyens de paiement et canaux. Le payeur doit toujours approuver l'opération sur son téléphone. Le résultat arrive donc par webhook.

Effectuer un payout

Un transfert vers un numéro de téléphone
Map<String, Object> transfer = wajub.transfers().create(
    Map.of(
        "amount", 100000,
        "currency", "XAF",
        "beneficiary", Map.of(
            "name", "Amina Diallo",
            "channel", "cm.mtn",
            "phone", "+237670000000"),
        "reference", "payout-892"),
    new RequestOptions().setIdempotencyKey("payout-892"));

beneficiary accepte aussi l'identifiant ben_… d'un bénéficiaire enregistré. Cette forme est préférable dès que vous payez plusieurs fois la même personne.

Développement local

Deux terminaux
wajub listen --forward-to localhost:8080/webhooks/wajub
wajub trigger payment.succeeded

Pour en savoir plus, consultez la CLI.

Que pensez-vous de ce contenu ?