Payment Service
Service de paiements : QR codes, paiements programmés, intégration cartes et reçus.
Overview
Le Payment Service gère tous les types de paiements supportés par YaniPay, incluant les paiements par QR code, les paiements récurrents et l'intégration avec le système de cartes.
Service de Paiement Unifié
QR Pay
Paiement instantané
Scheduled
Paiements récurrents
Cards
Paiements carte
Receipts
Reçus et exports
Pourquoi un Service de Paiement Unifié ?
YaniPay centralise tous les modes de paiement pour offrir une expérience fluide. Plus besoin de jongler entre plusieurs apps ou de mémoriser différents processus.
Avantages du Payment Service
- QR Codes Sécurisés : Chaque QR code est signé cryptographiquement et expire après 15 minutes
- Paiements Récurrents : Programmez vos virements avec alertes proactives en cas de solde insuffisant
- Cartes Virtuelles : Générez des cartes virtuelles à usage unique pour les achats en ligne sensibles
- Reçus Automatiques : Chaque transaction génère un reçu PDF conforme aux normes fiscales
QR Codes Dynamiques vs Statiques
Cas d'Utilisation
Découvrez comment différents profils utilisent le Payment Service au quotidien.
Marie
Propriétaire de café
Accepte un paiement QR
Marie affiche un QR code sur sa caisse pour recevoir le paiement d'un client. La transaction est confirmée en moins de 3 secondes avec notification sonore.
Antoine
Père de famille
Programme un virement mensuel
Antoine configure un paiement récurrent de 50€ vers le compte de son fils étudiant. Le service l'alerte si le solde est insuffisant avant l'exécution.
Julie
Acheteuse en ligne
Paie avec sa carte virtuelle
Julie utilise sa carte YaniPay virtuelle pour un achat en ligne. Elle reçoit instantanément une notification push avec les détails du marchand.
Thomas
Comptable indépendant
Exporte ses reçus
Thomas exporte tous ses reçus du mois en CSV pour sa comptabilité. Le Receipt Service génère automatiquement un fichier conforme aux normes fiscales.
Flux de Paiement QR
Architecture du système de paiement par QR code, de la génération à la confirmation.
Vue d'ensemble
Séquence détaillée
Voici le flux complet d'un paiement QR marchand, de la génération du code à la confirmation des deux parties.
Sécurité des QR Codes
QR Payments
Génération et validation de QR codes pour paiements P2P et marchands.
import { apiClient } from './api/client';
// QR Code types
export interface YaniPayQRCode {
type: 'payment' | 'receive' | 'merchant';
version: number;
data: PaymentQRData | ReceiveQRData | MerchantQRData;
signature: string;
expiresAt: string;
}
export interface PaymentQRData {
recipientId: string;
recipientName: string;
amount?: number;
currency: string;
reference?: string;
}
export interface ReceiveQRData {
userId: string;
userName: string;
amount?: number;
currency: string;
}
export interface MerchantQRData {
merchantId: string;
merchantName: string;
orderId: string;
amount: number;
currency: string;
items?: Array<{
name: string;
quantity: number;
price: number;
}>;
}
export const qrPaymentService = {
// Generate QR code for receiving
async generateReceiveQR(data: {
amount?: number;
currency?: string;
note?: string;
expiresIn?: number; // minutes
}): Promise<{
qrData: string;
qrImageUrl: string;
expiresAt: string;
}> {
const { data: result } = await apiClient.post('/payments/qr/generate', {
...data,
type: 'receive',
});
return result;
},
// Validate scanned QR code
async validateQR(qrData: string): Promise<{
valid: boolean;
type: 'payment' | 'receive' | 'merchant';
data: PaymentQRData | ReceiveQRData | MerchantQRData;
expired: boolean;
}> {
const { data } = await apiClient.post('/payments/qr/validate', { qrData });
return data;
},
// Process QR payment
async processQRPayment(data: {
qrData: string;
amount?: number; // Override if QR doesn't include amount
pin?: string;
}): Promise<{
transactionId: string;
status: 'COMPLETED' | 'PENDING';
amount: number;
currency: string;
recipient: { id: string; name: string };
}> {
const { data: result } = await apiClient.post('/payments/qr/process', data);
return result;
},
// Generate merchant payment QR
async generateMerchantQR(data: {
orderId: string;
amount: number;
currency: string;
items?: Array<{ name: string; quantity: number; price: number }>;
expiresIn?: number;
}): Promise<{
qrData: string;
qrImageUrl: string;
paymentUrl: string;
expiresAt: string;
}> {
const { data: result } = await apiClient.post('/payments/merchant/qr', data);
return result;
},
};Scheduled Payments
Gestion des paiements programmés et récurrents.
export type PaymentFrequency =
| 'once'
| 'daily'
| 'weekly'
| 'biweekly'
| 'monthly'
| 'quarterly'
| 'yearly';
export interface ScheduledPayment {
id: string;
recipientId: string;
recipientName: string;
recipientAvatar?: string;
amount: number;
currency: string;
frequency: PaymentFrequency;
nextExecutionDate: string;
lastExecutionDate?: string;
startDate: string;
endDate?: string;
note?: string;
status: 'ACTIVE' | 'PAUSED' | 'COMPLETED' | 'CANCELLED';
executionCount: number;
totalExecutions?: number;
createdAt: string;
}
export interface CreateScheduledPaymentDTO {
recipientId: string;
amount: number;
currency: string;
frequency: PaymentFrequency;
startDate: string;
endDate?: string;
totalExecutions?: number;
note?: string;
}
export const scheduledPaymentService = {
// Get all scheduled payments
async getScheduledPayments(): Promise<ScheduledPayment[]> {
const { data } = await apiClient.get<ScheduledPayment[]>('/payments/scheduled');
return data;
},
// Get single scheduled payment
async getScheduledPayment(id: string): Promise<ScheduledPayment> {
const { data } = await apiClient.get<ScheduledPayment>(`/payments/scheduled/${id}`);
return data;
},
// Create scheduled payment
async createScheduledPayment(
dto: CreateScheduledPaymentDTO
): Promise<ScheduledPayment> {
const { data } = await apiClient.post<ScheduledPayment>(
'/payments/scheduled',
dto
);
return data;
},
// Update scheduled payment
async updateScheduledPayment(
id: string,
updates: Partial<CreateScheduledPaymentDTO>
): Promise<ScheduledPayment> {
const { data } = await apiClient.patch<ScheduledPayment>(
`/payments/scheduled/${id}`,
updates
);
return data;
},
// Pause scheduled payment
async pauseScheduledPayment(id: string): Promise<ScheduledPayment> {
const { data } = await apiClient.post<ScheduledPayment>(
`/payments/scheduled/${id}/pause`
);
return data;
},
// Resume scheduled payment
async resumeScheduledPayment(id: string): Promise<ScheduledPayment> {
const { data } = await apiClient.post<ScheduledPayment>(
`/payments/scheduled/${id}/resume`
);
return data;
},
// Cancel scheduled payment
async cancelScheduledPayment(id: string): Promise<void> {
await apiClient.delete(`/payments/scheduled/${id}`);
},
// Get execution history
async getExecutionHistory(id: string): Promise<Array<{
id: string;
executedAt: string;
amount: number;
status: 'SUCCESS' | 'FAILED';
transactionId?: string;
failureReason?: string;
}>> {
const { data } = await apiClient.get(`/payments/scheduled/${id}/history`);
return data;
},
// Get upcoming payments
async getUpcomingPayments(days = 30): Promise<Array<{
scheduledPaymentId: string;
recipientName: string;
amount: number;
currency: string;
scheduledDate: string;
}>> {
const { data } = await apiClient.get('/payments/scheduled/upcoming', {
params: { days },
});
return data;
},
};Card Payments
Service de gestion des cartes et des paiements par carte.
export interface Card {
id: string;
type: 'virtual' | 'physical';
tier: 'standard' | 'premium' | 'metal';
status: 'ACTIVE' | 'FROZEN' | 'BLOCKED' | 'EXPIRED';
last4: string;
expiryMonth: number;
expiryYear: number;
holderName: string;
frozen: boolean;
addedToWallet: boolean;
dailyLimit: number;
monthlyLimit: number;
dailySpent: number;
monthlySpent: number;
balance: number;
createdAt: string;
}
export interface CardSettings {
contactlessEnabled: boolean;
onlinePaymentsEnabled: boolean;
internationalEnabled: boolean;
atmWithdrawalsEnabled: boolean;
dailyLimit: number;
monthlyLimit: number;
atmDailyLimit: number;
}
export interface CardTransaction {
id: string;
cardId: string;
amount: number;
currency: string;
merchant: {
name: string;
category: string;
city?: string;
country?: string;
logo?: string;
};
type: 'PURCHASE' | 'ATM' | 'REFUND' | 'FEE';
status: 'PENDING' | 'COMPLETED' | 'DECLINED' | 'REFUNDED';
declineReason?: string;
createdAt: string;
}
export const cardPaymentService = {
// Get all cards
async getCards(): Promise<Card[]> {
const { data } = await apiClient.get<Card[]>('/cards');
return data;
},
// Get single card
async getCard(id: string): Promise<Card> {
const { data } = await apiClient.get<Card>(`/cards/${id}`);
return data;
},
// Get card sensitive data (requires auth)
async getSensitiveData(id: string): Promise<{
number: string;
expiry: string;
cvv: string;
}> {
const { data } = await apiClient.get(`/cards/${id}/sensitive`);
return data;
},
// Order new card
async orderCard(data: {
type: 'virtual' | 'physical';
tier: 'standard' | 'premium' | 'metal';
shippingAddress?: {
street: string;
city: string;
postalCode: string;
country: string;
};
}): Promise<Card> {
const response = await apiClient.post<Card>('/cards/order', data);
return response.data;
},
// Freeze/unfreeze card
async toggleFreeze(id: string): Promise<Card> {
const { data } = await apiClient.post<Card>(`/cards/${id}/toggle-freeze`);
return data;
},
// Update card settings
async updateSettings(id: string, settings: Partial<CardSettings>): Promise<Card> {
const { data } = await apiClient.patch<Card>(`/cards/${id}/settings`, settings);
return data;
},
// Get card transactions
async getCardTransactions(
cardId: string,
page = 1,
limit = 20
): Promise<{
data: CardTransaction[];
pagination: { total: number; page: number; hasMore: boolean };
}> {
const { data } = await apiClient.get(`/cards/${cardId}/transactions`, {
params: { page, limit },
});
return data;
},
// Report card lost/stolen
async reportLostStolen(
id: string,
reason: 'lost' | 'stolen'
): Promise<{
blockedAt: string;
replacementOrdered: boolean;
newCardId?: string;
}> {
const { data } = await apiClient.post(`/cards/${id}/report`, { reason });
return data;
},
// Get Apple Wallet provisioning data
async getAppleWalletProvisioning(id: string): Promise<{
primaryAccountIdentifier: string;
activationData: string;
encryptedPassData: string;
}> {
const { data } = await apiClient.get(`/cards/${id}/apple-wallet`);
return data;
},
// Activate physical card
async activateCard(id: string, lastDigits: string): Promise<Card> {
const { data } = await apiClient.post<Card>(`/cards/${id}/activate`, {
lastDigits,
});
return data;
},
};Receipts
Génération et export de reçus de paiement.
export interface Receipt {
id: string;
transactionId: string;
type: 'payment' | 'transfer' | 'card_payment' | 'withdrawal';
amount: number;
currency: string;
fee?: number;
date: string;
reference: string;
sender: {
name: string;
account?: string;
};
recipient: {
name: string;
account?: string;
};
merchant?: {
name: string;
address?: string;
category: string;
};
status: 'COMPLETED' | 'PENDING' | 'REFUNDED';
pdfUrl?: string;
}
export const receiptService = {
// Get receipt for transaction
async getReceipt(transactionId: string): Promise<Receipt> {
const { data } = await apiClient.get<Receipt>(`/receipts/${transactionId}`);
return data;
},
// Generate PDF receipt
async generatePDF(transactionId: string): Promise<{
url: string;
expiresAt: string;
}> {
const { data } = await apiClient.post(`/receipts/${transactionId}/pdf`);
return data;
},
// Send receipt by email
async sendByEmail(transactionId: string, email?: string): Promise<void> {
await apiClient.post(`/receipts/${transactionId}/email`, { email });
},
// Get all receipts
async getReceipts(params?: {
startDate?: string;
endDate?: string;
type?: string;
page?: number;
limit?: number;
}): Promise<{
data: Receipt[];
pagination: { total: number; page: number; hasMore: boolean };
}> {
const { data } = await apiClient.get('/receipts', { params });
return data;
},
// Export receipts (for accounting)
async exportReceipts(params: {
startDate: string;
endDate: string;
format: 'csv' | 'pdf' | 'xlsx';
}): Promise<Blob> {
const { data } = await apiClient.get('/receipts/export', {
params,
responseType: 'blob',
});
return data;
},
};React Query Hooks
Hooks pour les opérations de paiement.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { qrPaymentService } from '@/services/paymentService';
import { scheduledPaymentService, ScheduledPayment, CreateScheduledPaymentDTO } from '@/services/scheduledPaymentService';
import { cardPaymentService, Card, CardSettings } from '@/services/cardPaymentService';
// Query keys
export const paymentKeys = {
all: ['payments'] as const,
scheduled: () => [...paymentKeys.all, 'scheduled'] as const,
upcoming: (days: number) => [...paymentKeys.all, 'upcoming', days] as const,
cards: () => [...paymentKeys.all, 'cards'] as const,
card: (id: string) => [...paymentKeys.all, 'card', id] as const,
cardTransactions: (id: string) => [...paymentKeys.all, 'card', id, 'transactions'] as const,
};
// Scheduled payment hooks
export function useScheduledPayments() {
return useQuery({
queryKey: paymentKeys.scheduled(),
queryFn: scheduledPaymentService.getScheduledPayments,
});
}
export function useUpcomingPayments(days = 30) {
return useQuery({
queryKey: paymentKeys.upcoming(days),
queryFn: () => scheduledPaymentService.getUpcomingPayments(days),
});
}
export function useCreateScheduledPayment() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: CreateScheduledPaymentDTO) =>
scheduledPaymentService.createScheduledPayment(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: paymentKeys.scheduled() });
queryClient.invalidateQueries({ queryKey: paymentKeys.upcoming(30) });
},
});
}
export function useCancelScheduledPayment() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: scheduledPaymentService.cancelScheduledPayment,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: paymentKeys.scheduled() });
},
});
}
// Card hooks
export function useCards() {
return useQuery({
queryKey: paymentKeys.cards(),
queryFn: cardPaymentService.getCards,
});
}
export function useCard(id: string) {
return useQuery({
queryKey: paymentKeys.card(id),
queryFn: () => cardPaymentService.getCard(id),
enabled: !!id,
});
}
export function useToggleCardFreeze() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: cardPaymentService.toggleFreeze,
onSuccess: (updatedCard) => {
queryClient.setQueryData(paymentKeys.card(updatedCard.id), updatedCard);
queryClient.invalidateQueries({ queryKey: paymentKeys.cards() });
},
});
}
export function useUpdateCardSettings() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, settings }: { id: string; settings: Partial<CardSettings> }) =>
cardPaymentService.updateSettings(id, settings),
onSuccess: (updatedCard) => {
queryClient.setQueryData(paymentKeys.card(updatedCard.id), updatedCard);
},
});
}
// QR Payment hooks
export function useProcessQRPayment() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: qrPaymentService.processQRPayment,
onSuccess: () => {
// Invalidate balance and transactions
queryClient.invalidateQueries({ queryKey: ['wallet', 'balance'] });
queryClient.invalidateQueries({ queryKey: ['wallet', 'transactions'] });
},
});
}
export function useGenerateReceiveQR() {
return useMutation({
mutationFn: qrPaymentService.generateReceiveQR,
});
}Intégration Commerçant
Payment Channels
Le Payment Service supporte 5 canaux de paiement distincts, chacun adapté à un contexte d'usage spécifique.
// Tous les canaux de paiement supportés par YaniPay
type PaymentChannel =
| 'TAP_TO_PAY' // Paiement sans contact via puce NFC
| 'ONLINE' // Intégration checkout web (Stripe Elements)
| 'IN_APP' // Flux d'achat in-app (Apple Pay / Google Pay)
| 'QR_CODE' // Scan du QR code marchand
| 'NFC'; // Near Field Communication direct (P2P)Tap to Pay
TAP_TO_PAY
Paiement sans contact via la puce NFC du smartphone. Compatible avec les terminaux EMV standard.
Online
ONLINE
Intégration checkout web via Stripe Elements ou l'API REST. Supporte 3DS2 et SCA.
In-App Purchase
IN_APP
Flux natif Apple Pay / Google Pay avec authentification biométrique intégrée.
QR Code
QR_CODE
Scan du QR code marchand. Montant encodé ou saisi par le payeur. Expire après 15 min.
NFC Direct
NFC
Communication Near Field Communication peer-to-peer entre deux appareils YaniPay.
Choix du canal
channel lors de la création d'un paiement. Le backend enregistre ce canal pour la réconciliation et le calcul des points de fidélité (certains programmes offrent un multiplicateur sur les paiements IN_APP).QR Payment Flow
Flux complet d'un paiement QR marchand, de la génération du code à l'affichage du reçu avec points de fidélité.
Le marchand génère le QR
Contient : merchantId, amount, currency. Signé cryptographiquement, valide 15 min.
POST /payments/qr/generate → { qrData, qrImageUrl, expiresAt }L'utilisateur scanne le QR
L'app décode et valide le contenu via paymentService.decodeQR(data).
POST /payments/qr/validate → { valid, merchantName, amount }Écran de revue
Affiche : nom du marchand, montant, points de fidélité estimés. L'utilisateur vérifie avant de confirmer.
// UI: <PaymentReviewScreen merchant={...} loyaltyPreview={...} />L'utilisateur confirme
Authentification biométrique puis appel au service de paiement.
paymentService.createPayment({ channel: 'QR_CODE', qrData, amount })Le backend traite
Charge Stripe + attribution automatique des points de fidélité si programme actif.
// Stripe charge + awardLoyalty() → loyaltyCard.balance += pointsÉcran de reçu
Transaction ID, timestamp, points gagnés. PDF disponible immédiatement.
// { transactionId, timestamp, loyaltyEarned, pdfUrl }Offline resilience
Paiements Programmés — API Simplifiée
Créez un paiement récurrent en quelques lignes. Supporte les fréquences hebdomadaire, mensuelle et annuelle.
import { scheduledPaymentService } from '@/services/scheduledPaymentService';
// Créer un paiement récurrent mensuel
const subscription = await scheduledPaymentService.createScheduledPayment({
recipientId: 'merchant_123',
amount: 29.99,
currency: 'EUR',
frequency: 'monthly', // 'weekly' | 'monthly' | 'yearly'
startDate: '2026-04-01',
description: 'Abonnement salle de sport',
});
console.log(subscription.id); // "sched_xxx"
console.log(subscription.nextExecutionDate); // "2026-04-01"
console.log(subscription.status); // "ACTIVE"
// Mettre en pause
await scheduledPaymentService.pauseScheduledPayment(subscription.id);
// Reprendre
await scheduledPaymentService.resumeScheduledPayment(subscription.id);
// Annuler définitivement
await scheduledPaymentService.cancelScheduledPayment(subscription.id);Vérification de solde
Refund Handling
YaniPay supporte les remboursements totaux et partiels. Le cycle de vie suit un flux d'états strict pour garantir la traçabilité.
import { paymentService } from '@/services/paymentService';
// Demander un remboursement (partiel ou total)
const refund = await paymentService.requestRefund({
transactionId: 'tx_abc123',
reason: 'Product not received',
amount: 49.99, // Remboursement partiel — omettre pour remboursement total
});
// Cycle de vie du remboursement
// REQUESTED → PROCESSING → APPROVED | REJECTED
console.log(refund.status); // "REQUESTED"
console.log(refund.refundId); // "ref_xyz789"
console.log(refund.estimatedArrival); // "2026-04-05" (3-5 jours ouvrés)
// Suivre l'état du remboursement
const status = await paymentService.getRefundStatus(refund.refundId);
if (status.status === 'APPROVED') {
console.log(`Remboursé : ${status.amount} ${status.currency}`);
}
if (status.status === 'REJECTED') {
console.log(`Refus : ${status.rejectionReason}`);
}| Statut | Description | Delai estimé |
|---|---|---|
| REQUESTED | Demande reçue, en attente de validation | Instantané |
| PROCESSING | Validation en cours, remboursement initié côté Stripe | < 1 h |
| APPROVED | Remboursement approuvé et crédité | 3-5 jours ouvrés |
| REJECTED | Demande refusée (raison fournie) | Notification immédiate |
Points de fidélité et remboursements
Loyalty Integration
Chaque paiement complété déclenche automatiquement la détection du programme de fidélité du marchand et l'attribution de points.
1. Paiement complété
Transaction validée côté Stripe
2. Détection programme
payeeId → proId → LoyaltyProgram (active + published)
3. Calcul des points
amount × tier multiplier (Bronze 1x, Silver 1.5x, Gold 2x)
4. Mise à jour carte
loyaltyCard.balance += points (via awardLoyalty())
5. Toast utilisateur
"Vous avez gagné 48 points !" — affiché 2 s après confirmation
// Le service côté backend est automatique — aucun code supplémentaire requis.
// Côté React Native, écoutez l'événement loyalty:awarded pour afficher le toast.
import { useEffect } from 'react';
import { paymentEventEmitter } from '@/services/paymentService';
function PaymentConfirmationScreen() {
useEffect(() => {
const sub = paymentEventEmitter.on('loyalty:awarded', ({ points, programName }) => {
Toast.show({
type: 'success',
text1: `Vous avez gagné ${points} points !`,
text2: programName,
visibilityTime: 3000,
});
});
return () => sub.remove();
}, []);
// ...
}Canaux éligibles aux points
TAP_TO_PAY, ONLINE, IN_APP et QR_CODE. Les erreurs de fidélité ne bloquent pas le paiement (caught & logged silencieusement).API Endpoints
Tous les endpoints du Payment Service. L'authentification Bearer JWT est requise sur l'ensemble des routes.
| Methode | Endpoint | Description |
|---|---|---|
| POST | /payments | Créer un paiement |
| GET | /payments/:id | Détails d'un paiement |
| POST | /payments/qr | Paiement par QR code |
| POST | /payments/request | Demander un paiement (P2P) |
| POST | /payments/scheduled | Créer un paiement programmé |
| POST | /payments/refund | Demander un remboursement |
| GET | /payments/history | Historique des paiements |
Rate Limiting
POST /payments est soumis à un rate limit de 100 req / 15 min par IP (apiRateLimit). En cas de dépassement, la réponse HTTP 429 est retournée avec un header Retry-After.Error Handling
Erreurs courantes renvoyées par le Payment Service. Chaque erreur inclut un code machine-readable et un message localisé.
import { paymentService, PaymentError } from '@/services/paymentService';
try {
await paymentService.createPayment({
amount: 150,
currency: 'EUR',
channel: 'QR_CODE',
recipientId: 'merchant_123',
});
} catch (err) {
if (err instanceof PaymentError) {
switch (err.code) {
case 'INSUFFICIENT_FUNDS':
// Afficher bottom sheet : "Solde insuffisant — Recharger ?"
navigation.navigate('TopUp', { amount: err.details.shortfall });
break;
case 'CARD_DECLINED':
// Afficher message d'erreur avec raison du refus
showToast({ type: 'error', message: `Carte refusée : ${err.details.declineReason}` });
break;
case 'PAYMENT_LIMIT_EXCEEDED':
// Informer l'utilisateur du plafond atteint
showToast({ type: 'warning', message: `Plafond journalier atteint : ${err.details.limit} EUR` });
break;
case 'KYC_REQUIRED':
// Rediriger vers le parcours KYC
navigation.navigate('KYCOnboarding', { reason: 'payment_limit' });
break;
default:
showToast({ type: 'error', message: err.message });
}
}
}| Code erreur | HTTP | Cause | Action recommandée |
|---|---|---|---|
| INSUFFICIENT_FUNDS | 402 | Solde insuffisant | Proposer une recharge |
| CARD_DECLINED | 402 | Carte refusée par l'émetteur | Afficher raison du refus |
| PAYMENT_LIMIT_EXCEEDED | 403 | Plafond journalier/mensuel atteint | Informer du plafond |
| KYC_REQUIRED | 403 | KYC non validé pour ce montant | Rediriger vers KYC |
| QR_EXPIRED | 400 | QR code expiré (> 15 min) | Demander un nouveau QR |
| DUPLICATE_PAYMENT | 409 | Paiement déjà traité | Afficher la transaction existante |
| RECIPIENT_NOT_FOUND | 404 | Bénéficiaire introuvable | Vérifier l'identifiant |
Ne jamais logger les données de carte
cardNumber, cvv et pin avant tout envoi vers vos outils de monitoring.Références
Ressources officielles pour approfondir les concepts du Payment Service.