Aller au contenu

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 · GA

Go modules

Version
v1.1.1
Runtime
Go 1.22+, aucune dépendance externe

Couvre

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

Installation

Le chemin du module et le nom du package sont différents
go get github.com/wajubhq/wajub-go

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.

wajubclient/client.go
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
}
ChampRô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 *http.Client, pour un transport personnalisé ou le traçage
MaxNetworkRetriesUn *int. La valeur par défaut est 2 si elle est nulle, et 0 désactive les nouvelles tentatives
TimeoutUne 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.

Créer un paiement
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.

Lire au-delà des six champs de la structure
reference, _ := payment.Raw["reference"].(string)
createdAt, _ := payment.Raw["created_at"].(string)

Toutes les ressources du client

ServiceMéthodes
client.GlobalPing, Channels, Countries, Currencies
client.PaymentsCreate, Initialize, Retrieve, List, Cancel, Process, ProcessSplit, ListRefunds
client.CustomersCreate, Retrieve, Update, Delete, List, Block, Unblock, Activate, Deactivate, ListTaxIds, CreateTaxId, DeleteTaxId
client.RefundsCreate, Retrieve, List
client.TransfersCreate, Retrieve, List
client.BeneficiariesCreate, Retrieve, Update, Delete, List
client.LinksCreate, Retrieve, Update, Delete, List
client.InvoicesCreate, Retrieve, Update, Delete, List, Send, MarkPaid, Cancel
client.AccountsCreate, Retrieve, Update, Delete, List, RegenerateToken
client.WebhookEndpointsCreate, Retrieve, Update, Delete, List, RotateSecret
client.BalanceRetrieve
client.EventsList, Retrieve, Resend
client.DisputesList, Retrieve, SubmitEvidence, Accept, Close, SendMessage
client.IdentityResolve, Validate
client.TaxGetSettings, UpdateSettings, Rates, Calculate, Reports, ListCodes, RetrieveCode, ListRegistrations, CreateRegistration, RetrieveRegistration, UpdateRegistration, DeleteRegistration, Jurisdictions, Thresholds, ThresholdAlerts
client.ShieldGetSettings, UpdateSettings, Stats, ListBlocklist, AddToBlocklist, RemoveFromBlocklist
client.ListenConfig, Auth
client.WebhooksConstructEvent
  • webhooks signature verification runs locally, no HTTP call. All other resources 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
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.

Un numéro de commande constitue la meilleure clé
payment, err := Client.Payments.Create(ctx, params, &wajub.RequestOptions{
	IdempotencyKey: "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 par requête

Pour désactiver les nouvelles tentatives, faites pointer MaxNetworkRetries vers zéro.

Aucune nouvelle tentative
zero := 0
c, err := wajub.New(wajub.Config{MaxNetworkRetries: &zero})

Agir pour un compte connecté

Un appel pour un vendeur
_, 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é.

Un handler net/http
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.

Traiter d'abord l'erreur précise
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)
}
TypeCas de retour
*AuthenticationError401
*PermissionError403
*NotFoundError404
*InvalidRequestError400 et 422
*RateLimitError429, avec RetryAfter en secondes
*WajubErrorTout autre statut. Cette structure est aussi intégrée à toutes les erreurs ci-dessus
*APIConnectionErrorAucune réponse. Elle implémente Unwrap
*WebhookSignatureVerificationErrorUn 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

Requête push Mobile Money
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

Un transfert vers un numéro de téléphone
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

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 ?