Démarrage rapide
Introduction
L'API Senfenico propose deux méthodes d'intégration pour le traitement des paiements : avec redirection et sans redirection, répondant ainsi aux divers besoins des applications.
Avec Redirection
- Avantage : Parcours utilisateur simplifié 🚀; les utilisateurs sont redirigés vers la page de paiement sécurisée de Senfenico.
- Inconvénient : Perturbation potentielle de l'expérience utilisateur lorsque les utilisateurs quittent l'application native ou le site web.
- Processus :
- Initialiser le paiement : Commencer le processus de paiement en appelant le point de terminaison désigné.
- Rediriger le client : Envoyer les utilisateurs vers l'URL d'autorisation renvoyé par la première requête.
Sans Redirection
- Avantage : Contrôle total de l'expérience utilisateur, toutes les étapes étant gérées au sein de votre application ou site web.
- Inconvénient : Complexité accrue car vous devez gérer toutes les étapes du paiement, y compris la soumission du OTP (One-Time Password) si nécessaire.
- Processus :
- Créer un paiement : Configurer les détails du paiement.
- Action du client : Demander aux utilisateurs d'approuver la transaction sur leur téléphone.
- Soumettre le code OTP : Soumettre le OTP reçu par le client.
Accepter un Paiement avec Redirection
1️⃣ Première Étape : Initialiser le Paiement
curl --location 'https://api.senfenico.com/v1/payment/checkouts/initialize/' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'X-API-KEY: sk_test_...' \
--data '{
"email": "customer@mail.com",
"amount": 600,
"success_url": "https://example.com/sucess",
"cancel_url": "https://example.com/cancel"
}'
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n \"email\": \"customer@mail.com\",\n \"amount\": 600,\n \"success_url\": \"https://example.com/sucess\",\n \"cancel_url\": \"https://example.com/cancel\"\n}");
Request request = new Request.Builder()
.url("https://api.senfenico.com/v1/payment/checkouts/initialize/")
.method("POST", body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.addHeader("X-API-KEY", "sk_test_...")
.build();
Response response = client.newCall(request).execute();
var https = require('follow-redirects').https;
var fs = require('fs');
var options = {
'method': 'POST',
'hostname': 'api.senfenico.com',
'path': '/v1/payment/checkouts/initialize/',
'headers': {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-API-KEY': 'sk_test_...'
},
'maxRedirects': 20
};
var req = https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
console.log(body.toString());
});
res.on("error", function (error) {
console.error(error);
});
});
var postData = JSON.stringify({
"email": "customer@mail.com",
"amount": 600,
"success_url": "https://example.com/sucess",
"cancel_url": "https://example.com/cancel"
});
req.write(postData);
req.end();
Paramètre | Type | Optionnel | Description |
---|---|---|---|
chaîne | Oui | Adresse e-mail du client | |
amount | entier | Non | Montant à facturer |
success_url | chaîne | Non | URL de redirection en cas de succès |
cancel_url | chaîne | Non | URL de redirection si le client annule |
Exemple de reponse
Élément de Réponse | Type | Description |
---|---|---|
référence | chaîne | Référence unique pour le paiement |
url_autorisation | chaîne | URL pour rediriger l'utilisateur afin de procéder au paiement |
2️⃣ Deuxième Étape : Rediriger l'Utilisateur
Dirigez l'utilisateur vers l'authorization_url
pour compléter le processus de paiement.
Accepter un Paiement Sans Redirection
1️⃣ Première Étape : Créer une Charge
curl --location 'https://api.senfenico.com/v1/payment/charges/' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'X-API-KEY: sk_test_...' \
--data '{
"amount": "500",
"currency": "XOF",
"payment_method": "mobile_money",
"payment_method_details": {
"phone": "76xxxxxx",
"provider": "orange_bf"
}
}'
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n \"amount\": \"500\",\n \"currency\": \"XOF\",\n \"payment_method\": \"mobile_money\",\n \"payment_method_details\": {\n \"phone\": \"76xxxxxx\",\n \"provider\": \"orange_bf\"\n }\n}");
Request request = new Request.Builder()
.url("https://api.senfenico.com/v1/payment/charges/")
.method("POST", body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.addHeader("X-API-KEY", "sk_test_...")
.build();
Response response = client.newCall(request).execute();
var https = require('follow-redirects').https;
var fs = require('fs');
var options = {
'method': 'POST',
'hostname': 'api.senfenico.com',
'path': '/v1/payment/charges/',
'headers': {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-API-KEY': 'sk_test_...'
},
'maxRedirects': 20
};
var req = https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
console.log(body.toString());
});
res.on("error", function (error) {
console.error(error);
});
});
var postData = JSON.stringify({
"amount": "500",
"currency": "XOF",
"payment_method": "mobile_money",
"payment_method_details": {
"phone": "76xxxxxx",
"provider": "orange_bf"
}
});
req.write(postData);
req.end();
Parameter | Type | Optional | Description |
---|---|---|---|
amount | int | No | Amount to charge |
currency | string | No | Currency code ("XOF") |
payment_method | string | No | Payment method ("mobile_money") |
payment_method_details | object | No | Details about the payment method |
phone | string | No | your client phone number |
provider | object | No | the client payment platform operator. Available options:
|
Note on Provider Constraints:
- For
orange_bf
: Use an Orange BF phone number (8 digits). Valid prefixes: 04, 05, 06, 07, 54, 55, 56, 57, 64, 65, 66, 67, 74, 75, 76, 77. - For
moov_bf
: Use a Moov BF phone number (8 digits). Valid prefixes: 70, 71, 72, 73, 60, 61, 62, 63, 50, 51, 52, 53, 01, 02, 03. - Pour
coris_bf
: Vous pouvez utiliser n'importe quelle numero de téléphone portable burkinabè - Pour
sank_bf
: Vous pouvez utiliser n'importe quelle numero de téléphone portable burkinabè
Sample Response
{
"status": true,
"message": "Charge attempted",
"data": {
"reference": "ch_1ba1b4d1268b41079ee84c777e999f51",
"status": "send_otp",
"display_text": "Composez *144*4*6*500# pour obtenir un code OTP, puis entrez-le sur notre plateforme pour finaliser la transaction.",
"live_mode": false
}
}
Interprétation des réponses
- Pour
orange_bf
: Le client doit composer le code USSD fourni afin de recevoir un OTP, qu'il devra ensuite saisir sur la plateforme. - Pour
moov_bf
: Le client recevra un OTP (sans action de sa part), qu'il devra ensuite saisir sur la plateforme. - Pour
coris_bf
: Le client recevra un OTP (sans action de sa part), qu'il devra ensuite saisir sur la plateforme. - Pour
sank_bf
: Le client recevra un OTP (sans action de sa part), qu'il devra ensuite saisir sur la plateforme.
2️⃣ Deuxième étape : Action requise de la part du client
Demandez au client de suivre les instructions données pour confirmer le paiement.
3️⃣ Troisième étape : Saisie de l'OTP
Procédez à la saisie de l'OTP pour effectuer la transaction.
curl --location 'https://api.senfenico.com/v1/payment/charges/submit' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'X-API-KEY: sk_test_...' \
--data '{
"otp": "123456",
"charge_reference": "ch_39728149f97a46f1806a694dbb620b23"
}'
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/json");
RequestBody body = RequestBody.create(mediaType, "{\n \"otp\": \"123456\",\n \"charge_reference\": \"ch_39728149f97a46f1806a694dbb620b23\"\n}");
Request request = new Request.Builder()
.url("https://api.senfenico.com/v1/payment/charges/submit")
.method("POST", body)
.addHeader("Content-Type", "application/json")
.addHeader("Accept", "application/json")
.addHeader("X-API-KEY", "sk_test_...")
.build();
Response response = client.newCall(request).execute();
var https = require('follow-redirects').https;
var fs = require('fs');
var options = {
'method': 'POST',
'hostname': 'api.senfenico.com',
'path': '/v1/payment/charges/submit',
'headers': {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-API-KEY': 'sk_test_...'
},
'maxRedirects': 20
};
var req = https.request(options, function (res) {
var chunks = [];
res.on("data", function (chunk) {
chunks.push(chunk);
});
res.on("end", function (chunk) {
var body = Buffer.concat(chunks);
console.log(body.toString());
});
res.on("error", function (error) {
console.error(error);
});
});
var postData = JSON.stringify({
"otp": "123456",
"charge_reference": "ch_39728149f97a46f1806a694dbb620b23"
});
req.write(postData);
req.end();
Parameter | Type | Optional | Description |
---|---|---|---|
otp | string | No | One-Time Password (OTP) |
charge_reference | string | No | Reference of the charge |
Sample Response
{
"status": true,
"message": "charge completed successfully",
"data": {
"reference": "ch_39728149f97a46f1806a694dbb620b23",
"amount": 1000,
"fees": 3.5,
"currency": "XOF",
"transaction_date": "2024-08-18T11:43:47.380565+00:00",
"ip_address": "172.18.0.1",
"status": "success",
"live_mode": false,
"payment_method": "mobile_money",
"provider": "orange_bf",
"cancelled_at": null,
"cancellation_reason": null,
"confirmation_attempts": [
{
"ip_address": "172.18.0.1",
"attempt_date": "2024-08-18T11:44:16.683423+00:00",
"confirmation_status": "success"
}
]
}
}
Élément de Réponse | Type | Description |
---|---|---|
reference | chaîne | Référence de la transaction |
amount | entier | Montant facturé |
fees | flottant | Frais de transaction en pourcentage (ex. : 2 %) |
currency | chaîne | Code de devise ["XOF"] |
status | chaîne | Statut de la transaction |
live_mode | booléen | Indique s'il s'agit d'une transaction en direct |
payment_method | chaîne | Méthode de paiement ["mobile_money"] |
provider | chaîne | Nom du fournisseur ["orange_bf", "moov_bf", "coris_bf" , "sank_bf"] |
cancelled_at | null | Heure d'annulation (si applicable) |
cancellation_reason | null | Raison de l'annulation (si applicable) |
📣 Webhooks
Indépendamment de la méthode d'intégration choisie, il est crucial d'implémenter des webhooks. Cela permet de gérer les événements de paiement ou tout autre événement.
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.http.ResponseEntity;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@RestController
public class WebhookController {
private static final String WEBHOOK_SECRET_KEY = "..."; // Replace with your webhook secret key
@PostMapping("/webhook")
public ResponseEntity<String> handleWebhook(@RequestBody String payload,
@RequestHeader("X-Webhook-Hash") String receivedHash) {
try {
if (!verifyHash(payload, receivedHash)) {
return ResponseEntity.status(400).body("Hash mismatch, data might be tampered");
}
// Process the webhook payload
System.out.println(payload);
return ResponseEntity.ok("Webhook received successfully");
} catch (NoSuchAlgorithmException e) {
return ResponseEntity.status(500).body("Server error: " + e.getMessage());
}
}
private boolean verifyHash(String payload, String receivedHash) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = digest.digest((payload + WEBHOOK_SECRET_KEY).getBytes(StandardCharsets.UTF_8));
String computedHash = Base64.getEncoder().encodeToString(hashBytes);
return computedHash.equals(receivedHash);
}
}
// In an ExpressJS application, handling webhooks is straightforward. Here's a sample implementation:
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const app = express();
app.use(bodyParser.json());
const webhookSecretKey = '...'; // Replace with your webhook secret key
function verifyHash(payload, receivedHash) {
const hash = crypto.createHmac('sha256', webhookSecretKey)
.update(JSON.stringify(payload))
.digest('hex');
return hash === receivedHash;
}
app.post('/webhook', (req, res) => {
const receivedHash = req.headers['x-webhook-hash'];
if (!verifyHash(req.body, receivedHash)) {
return res.status(400).send({'error': 'Hash mismatch, data might be tampered'});
}
// Process the webhook payload
console.log(req.body);
res.status(200).send({'message': 'Webhook received successfully'});
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
<?php
require_once 'senfenico-php/init.php';
$payload = file_get_contents('php://input');
$webhook_hash = $_SERVER['HTTP_X_WEBHOOK_HASH'];
$webhook_key = 'your_secret_webhook_key...';
try{
$webhook = \Senfenico\Webhook::constructEvent($payload, $webhook_hash, $webhook_key);
} catch(\Exception $e) {
// Invalid payload
http_response_code(400);
exit;
}
// Handle the event
switch ($webhook->event) {
case 'checkout.pending':
//handle checkout pending event
break;
case 'checkout.success':
//chandle checkout success event
break;
// ... handle other event types
default:
echo 'Received unknown event type ' . $webhook->event;
}
http_response_code(200);
exit;
$payload = $request->getContent();
$webhook_hash = $request->header('X-Webhook-Hash');
$webhook_key = '9e191505-9637-4c2b-a55e-da813b882732';
try{
$webhook = \Senfenico\Webhook::constructEvent($payload, $webhook_hash, $webhook_key);
} catch(\Exception $e) {
Log::debug('Message: ' .$e->getMessage());
return response()->json(['message' => 'une erreur sest produite'], 400);
}
// Handle the event
switch ($webhook->event) {
case 'checkout.pending':
//handle checkout pending event
break;
case 'checkout.success':
//chandle checkout success event
break;
// ... handle other event types
default:
Log::debug('Received unknown event type ' . $webhook->event);
}
return response()->json(['message' => 'Webhook received'], 200);
webhook_key = 'your_secret_webhook_key...' # replace with your secret key
@app.route('/webhook', methods=['POST'])
def handle_webhook():
# Extract the hash from headers
received_hash = request.headers.get('X-Webhook-Hash')
# Get the payload from the body
payload = request.json
try:
webhook = senfenico.Webhook.construct_event(payload, received_hash, webhook_key)
except ValueError as e:
print('Invalid payload')
return jsonify({'error': str(e)}), 400
if webhook.event == 'checkout.pending':
print('checkout pending')
elif webhook.event == 'checkout.success':
print('checkout completed')
# ... handle other events
else:
print('Unhandled event type {}'.format(webhook.event))
return jsonify({'message': 'Webhook received successfully'}), 200
webhook_key = 'your_webhook_secret_key...' # replace with your secret key
@csrf_exempt
def handle_webhook(request):
# Extract the hash from headers
received_hash = request.headers.get('X-Webhook-Hash')
# Get the payload from the body
payload = request.body
try:
webhook = senfenico.Webhook.construct_event(payload, received_hash, webhook_key)
except ValueError as e:
print('Invalid payload')
return HttpResponse(status=400)
if webhook.event == 'checkout.pending':
print('checkout pending')
elif webhook.event == 'checkout.success':
print('checkout completed')
# ... handle other events
else:
print('Unhandled event type {}'.format(webhook.event))
return HttpResponse(status=200)
L'Importance de Vérifier le Hash Vérifier le hash est essentiel pour garantir l'intégrité et la sécurité des données reçues via les webhooks. Cela contribue à :
- Confirmer l'Authenticité : S'assurer que les données proviennent d'une source fiable, en l'occurrence Senfenico.
- Empêcher les Altérations : Permet de détecter si les données ont été modifiées ou altérées durant leur transmission.
- Renforcer la Sécurité : Ajoute une couche supplémentaire de sécurité en vérifiant les données avec une clé secrète uniquement connue de l'expéditeur et du destinataire.
Intégrer la vérification de hash dans la mise en œuvre de vos webhooks est une pratique recommandée pour assurer une intégration sûre et fiable.