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.

github.com/wajubhq/wajub-go

Stable · GA

Go modules

Version
v1.2.1
Runtime
Go 1.22+

Couvre

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

Installation

Ajouter le module
go get github.com/wajubhq/wajub-go

Le chemin d'import est github.com/wajubhq/wajub-go et le package s'appelle 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.

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
HTTPClientVotre propre *http.Client, pour un proxy, le traçage ou les tests
MaxNetworkRetriesLe nombre de nouvelles tentatives après un échec, sous forme de *int. nil conserve la valeur par défaut, 0 désactive les nouvelles tentatives
TimeoutUne time.Duration. Ce champ est ignoré si HTTPClient est défini : configurez alors le délai sur votre propre client

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.

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, 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.WebhooksConstructEvent
  • La vérification de signature de webhooks s'exécute en local, sans appel HTTP. Toutes les autres ressources appellent l'API REST.
  • links, invoices, tax et shield ne fonctionnent qu'en live. Une clé sandbox reçoit 403 This feature is only available in live mode. sur chacune de leurs méthodes.
  • refunds et transfers ne proposent que la création, la lecture et la liste.

Parcourir une liste par pages

Une page ou toutes les pages
page, err := Client.Payments.List(ctx, map[string]any{
	"status":   "succeeded",
	"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

Les requêtes en échec sont retentées automatiquement, et une nouvelle tentative ne crée jamais rien en double. Si la même commande peut être soumise à nouveau, passez votre propre clé.

Un numéro de commande constitue la meilleure clé
payment, err := Client.Payments.Create(ctx, params, &wajub.RequestOptions{
	IdempotencyKey: "order-" + orderID,
})

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 reçoit l'identifiant du compte connecté. 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)
}

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. errors.Is atteint la cause, un délai dépassé par exemple
*WebhookSignatureVerificationErrorUn webhook dont la signature n'a pas été vérifiée

Toutes contiennent Message, Code, HTTPStatus, Errors et Raw.

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 Mobile Money. 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 ?