Go
Le module wajub-go, ses ressources et le contexte accepté par chaque appel.
wajub-go 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 uniquement
la bibliothèque standard et n'ajoute aucune dépendance dans go.mod.
github.com/wajubhq/wajub-go
Stable · GAGo modules
- Version
- v1.1.1
- Runtime
- Go 1.22+, aucune dépendance externe
Couvre
- Paiements
- Facturation
- Transferts
- Sync
- Shield
- Taxes
Installation
go get github.com/wajubhq/wajub-goLe chemin d'import est wajubhq, le package est wajub
L'organisation est wajubhq. Ainsi, github.com/wajub/wajub-go ne peut pas être résolu et go get
échoue avec une erreur 404 du proxy. Dans votre fichier, l'identifiant du package est simplement
wajub. L'import n'a donc pas besoin d'alias.
Créer le client
New renvoie une erreur et utilise les variables d'environnement en repli. Dans le cas courant,
vous pouvez donc passer une Config vide.
package wajubclient
import (
"log"
"github.com/wajubhq/wajub-go"
)
// Client is built once at start-up and shared. It is safe for concurrent use.
var Client *wajub.Client
func init() {
// Reads WAJUB_API_KEY and WAJUB_WEBHOOK_SECRET when Config leaves them empty.
c, err := wajub.New(wajub.Config{})
if err != nil {
log.Fatalf("wajub: %v", err)
}
Client = c
}| Champ | Rôle |
|---|---|
APIKey | Votre clé sk. ou sk_test.. Utilise WAJUB_API_KEY en repli |
WebhookSecret | Le secret whsec_. Utilise WAJUB_WEBHOOK_SECRET en repli |
IdempotencyKeyPrefix | Préfixe de la clé générée. Valeur par défaut : "wajub" |
HTTPClient | Votre propre *http.Client, pour un transport personnalisé ou le traçage |
MaxNetworkRetries | Un *int. La valeur par défaut est 2 si elle est nulle, et 0 désactive les nouvelles tentatives |
Timeout | Une time.Duration. La valeur par défaut est de 30 secondes. Ce champ est ignoré si HTTPClient est défini |
New renvoie wajub: api key is required lorsque ni le champ ni la variable ne contient de clé.
Une mauvaise configuration échoue donc au démarrage.
Timeout est ignoré si vous fournissez votre propre client
Timeout configure le *http.Client par défaut construit par le SDK. Si vous définissez
HTTPClient, ce champ est entièrement ignoré. Configurez donc le délai d'expiration sur le client fourni.
Le premier appel
Chaque méthode accepte d'abord un context.Context. Passez le contexte de la requête. Ainsi,
une API lente est annulée avec la requête au lieu de bloquer la goroutine.
payment, err := Client.Payments.Create(ctx, map[string]any{
"amount": 25000,
"currency": "XAF",
"email": "amina@example.com",
"description": "Order 4172",
"reference": "order-4172",
"callback": "https://shop.example.com/complete",
}, nil)
if err != nil {
return fmt.Errorf("create payment: %w", err)
}
http.Redirect(w, r, payment.AuthorizationURL, http.StatusSeeOther)Les paramètres sont transmis sous forme de map[string]any avec les noms de champs exacts de l'API.
Le dernier argument est *RequestOptions, et sa valeur habituelle est nil.
Payment est une structure limitée, Raw contient le reste
Payment décode six champs : ID, Status, Amount, Currency, AuthorizationURL et
AuthorizationToken. Tout le reste de la réponse de l'API, y compris reference et created_at,
se trouve dans payment.Raw sous forme de map[string]any. Utilisez Raw au lieu de supposer qu'un champ existe.
reference, _ := payment.Raw["reference"].(string)
createdAt, _ := payment.Raw["created_at"].(string)Amount est un int64 alors que l'API envoie un nombre décimal
Les montants sont transmis dans l'unité principale. Ainsi, 12,50 GHS arrive sous la forme 12.5
et devient 12 dans Amount. Le champ de la structure convient au XAF, au XOF, au NGN et à toutes
les devises sans décimales, mais il perd de la précision pour les autres. Pour une devise décimale,
lisez payment.Raw["amount"].(float64).
Toutes les ressources du client
| Service | Mé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 |
webhookssignature verification runs locally, no HTTP call. All other resources call the merchant REST API.links,invoices,taxandshieldare live only. A sandbox key gets403 This feature is only available in live mode.on every one of their methods.refundsandtransfersare create, retrieve and list only. The shared CRUD base also exposesupdateanddeleteon them, but the API serves no such route.
Parcourir une liste par pages
page, err := Client.Payments.List(ctx, map[string]any{
"status": "success",
"per_page": 50,
})
if err != nil {
return err
}
for _, payment := range page.Data {
log.Println(payment["id"], payment["amount"])
}
if page.HasMore {
page, err = page.GetNextPage(ctx)
}
// Or collect every page at once.
all, err := page.AutoPagingIter(ctx)page.Data est un []map[string]any. Les lignes se lisent donc par clé. AutoPagingIter 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.
payment, err := Client.Payments.Create(ctx, params, &wajub.RequestOptions{
IdempotencyKey: "order-" + orderID,
})| Élément | Valeur |
|---|---|
| Statuts concernés | 429, 500, 502, 503, 504 et toute erreur réseau |
| Tentatives | 3 au total, l'appel initial et deux nouvelles tentatives |
| Attente progressive | Exponentielle avec une part d'aléa (jitter), en respectant Retry-After pour une erreur 429 |
| Délai d'expiration | 30 secondes par requête |
Pour désactiver les nouvelles tentatives, faites pointer MaxNetworkRetries vers zéro.
zero := 0
c, err := wajub.New(wajub.Config{MaxNetworkRetries: &zero})Agir pour un compte connecté
_, err := Client.Payments.Create(ctx, params, &wajub.RequestOptions{
Sync: seller.WajubAccountID,
})Sync 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é.
func WajubWebhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
event, err := Client.Webhooks.ConstructEvent(
body,
r.Header.Get("X-Wajub-Signature"),
r.Header.Get("X-Wajub-Timestamp"),
0, // 0 means the default 300 second window
)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
if event["event"] == "payment.succeeded" {
go fulfil(event["data"])
}
w.WriteHeader(http.StatusOK)
}Une tolérance nulle applique la valeur par défaut
Passer 0 comme quatrième argument applique la fenêtre de 300 secondes au lieu de désactiver
la protection contre les rejeux. Il est volontairement impossible de désactiver cette vérification.
Le nom de l'événement se trouve dans event, pas dans type. Le résultat est un map[string]any,
vous devez donc le lire par clé. Consultez Vérification de signature.
Erreurs
Les erreurs sont des types pointeurs qui enveloppent une WajubError commune. Utilisez donc
errors.As pour les examiner.
payment, err := Client.Payments.Create(ctx, params, nil)
if err != nil {
var invalid *wajub.InvalidRequestError
if errors.As(err, &invalid) {
// invalid.Errors is map[string]string keyed by field name
return c.JSON(http.StatusUnprocessableEntity, invalid.Errors)
}
var limited *wajub.RateLimitError
if errors.As(err, &limited) {
w.Header().Set("Retry-After", strconv.Itoa(limited.RetryAfter))
w.WriteHeader(http.StatusServiceUnavailable)
return nil
}
var conn *wajub.APIConnectionError
if errors.As(err, &conn) && errors.Is(conn, context.DeadlineExceeded) {
return ErrPaymentTimedOut
}
return fmt.Errorf("wajub: %w", err)
}| Type | Cas de retour |
|---|---|
*AuthenticationError | 401 |
*PermissionError | 403 |
*NotFoundError | 404 |
*InvalidRequestError | 400 et 422 |
*RateLimitError | 429, avec RetryAfter en secondes |
*WajubError | Tout autre statut. Cette structure est aussi intégrée à toutes les erreurs ci-dessus |
*APIConnectionError | Aucune réponse. Elle implémente Unwrap |
*WebhookSignatureVerificationError | Un webhook dont la signature n'a pas été vérifiée |
Toutes contiennent Message, Code, HTTPStatus, Errors et Raw.
APIConnectionError donne accès à la cause réelle
Cette erreur conserve l'erreur de transport dans Err et implémente Unwrap. Ainsi,
errors.Is(err, context.DeadlineExceeded) distingue un délai d'expiration d'une connexion refusée
sans comparer de chaînes de caractères.
Débiter sans page hébergée
payment, err := Client.Payments.Create(ctx, map[string]any{
"amount": 25000,
"currency": "XAF",
"phone": "+237670000000",
}, nil)
if err != nil {
return err
}
_, err = Client.Payments.Process(ctx, payment.ID, map[string]any{
"channel": "cm.mtn",
"phone": "+237670000000",
}, nil)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
transfer, err := Client.Transfers.Create(ctx, map[string]any{
"amount": 100000,
"currency": "XAF",
"beneficiary": map[string]any{
"name": "Amina Diallo",
"channel": "cm.mtn",
"phone": "+237670000000",
},
"reference": "payout-892",
}, &wajub.RequestOptions{IdempotencyKey: "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
wajub listen --forward-to localhost:8080/webhooks/wajub
wajub trigger payment.succeededPour en savoir plus, consultez la CLI.
Pages associées
- Démarrage rapide des SDKsLe même premier appel dans cinq langages, présentés côte à côte.
- WebhooksTous les événements et leurs garanties de livraison.
- Conventions de nommage des SDKsPourquoi Go expose du CamelCase sur un protocole en snake_case.
- IdempotenceCe que protège une clé et pendant combien de temps.
- Gestion des erreursLes échecs à retenter et ceux à présenter à l'utilisateur.
- Vue d'ensemble de la référence APILes endpoints utilisés par chaque méthode ci-dessus.