UNPKG

ynkap-payment

Version:

Module de paiement Y-Nkap pour Angular - Intégration simple des paiements mobiles (Orange Money, MTN Mobile Money)

746 lines (599 loc) 20.5 kB
# Y-Nkap Payment Module for Angular [![npm version](https://badge.fury.io/js/ynkap-payment.svg)](https://badge.fury.io/js/ynkap-payment) [![Angular](https://img.shields.io/badge/Angular-16%2B-red.svg)](https://angular.io/) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0%2B-blue.svg)](https://www.typescriptlang.org/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Build Status](https://img.shields.io/badge/Build-Passing-brightgreen.svg)](https://github.com/SIEWE-FORTUNE/projet-npm-ykap) [![Coverage](https://img.shields.io/badge/Coverage-87%25-brightgreen.svg)](../../RAPPORT-DE-TEST.md) Le module Y-Nkap pour Angular offre une **intégration simple et robuste** des paiements mobiles (Orange Money, MTN Mobile Money) dans vos applications Angular. Développé par **Yaba-In**, ce module fournit une solution complète avec gestion avancée des erreurs, retry automatique, interface utilisateur optimisée, et respect des bonnes pratiques Angular. ## **Fonctionnalités principales** - **Intégration en une ligne** : Composant `<ynkap-pay-button>` prêt à l'emploi - **Interface moderne** : Modale responsive avec thèmes light/dark et animations fluides - **Retry intelligent** : Gestion automatique des erreurs avec backoff exponentiel - **Sécurisé** : Authentification par clés API, validation stricte - **Mobile-first** : Optimisé pour Orange Money et MTN Mobile Money - **Formatage FCFA** : Pipe personnalisé pour affichage correct "1 000 FCFA" - **TypeScript** : Support complet avec types stricts et IntelliSense - **Testé** : 87% de couverture de code, 53 tests unitaires - **Architecture modulaire** : Services séparés et fichiers organisés (HTML, CSS, TS) - **Bonnes pratiques** : Respect des conventions Angular et séparation des responsabilités ## **Table des matières** - [Installation](#-installation) - [Configuration](#-configuration) - [Utilisation rapide](#-utilisation-rapide) - [Guide complet](#-guide-complet) - [API Reference](#-api-reference) - [Gestion des erreurs](#-gestion-des-erreurs) - [Exemples avancés](#-exemples-avancés) - [Migration](#-migration) - [Support](#-support) --- ## **Installation** ### Prérequis - Angular 16+ - TypeScript 5.0+ - Node.js 18+ ### Installation via npm ```bash npm install ynkap-payment --save ``` ### Installation via yarn ```bash yarn add ynkap-payment ``` ### Dépendances requises ```bash npm install @angular/animations --save ``` --- ## **Configuration** ### 1. Import du module Importez `YnkapModule` dans votre module principal : ```typescript // app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { YnkapModule } from 'ynkap-payment'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, BrowserAnimationsModule, // Requis pour les animations // Configuration Y-Nkap YnkapModule.forRoot({ apiKey: 'your-api-key', apiSecret: 'your-api-secret', merchantId: 'your-merchant-id', environment: 'sandbox', // 'sandbox' | 'production' webhookUrl: 'https://your-app.com/webhook' // Optionnel }) ], bootstrap: [AppComponent] }) export class AppModule { } ``` ### 2. Configuration des environnements ```typescript // environments/environment.ts export const environment = { production: false, ynkap: { apiKey: 'sandbox-api-key', apiSecret: 'sandbox-api-secret', merchantId: 'sandbox-merchant-id', environment: 'sandbox' as const } }; // environments/environment.prod.ts export const environment = { production: true, ynkap: { apiKey: 'prod-api-key', apiSecret: 'prod-api-secret', merchantId: 'prod-merchant-id', environment: 'production' as const } }; ``` ### 3. Configuration dynamique ```typescript import { ConfigurationService } from '@yaba-in/ynkap-payment'; constructor(private ynkapConfig: ConfigurationService) {} ngOnInit() { // Configuration à l'exécution this.ynkapConfig.setConfig({ apiKey: 'dynamic-api-key', apiSecret: 'dynamic-api-secret', merchantId: 'dynamic-merchant-id', environment: 'production' }); } ``` --- ## **Utilisation rapide** ### Exemple minimal ```html <!-- Utilisation la plus simple --> <ynkap-pay-button [amount]="1000" merchantReference="ORDER-123" description="Achat produit" (paymentSuccess)="onSuccess($event)" (paymentError)="onError($event)"> Payer 1000 XAF </ynkap-pay-button> ``` ```typescript import { Component } from '@angular/core'; import { PaymentResponse, YnkapError } from '@yaba-in/ynkap-payment'; @Component({ selector: 'app-checkout', templateUrl: './checkout.component.html' }) export class CheckoutComponent { onSuccess(response: PaymentResponse): void { console.log(' Paiement réussi:', response); // Rediriger vers page de confirmation } onError(error: YnkapError): void { console.error(' Erreur de paiement:', error); // Afficher message d'erreur } } ``` --- ## **Guide complet** ### 1. Composant Pay Button Le composant principal `<ynkap-pay-button>` gère tout le processus de paiement : ```html <ynkap-pay-button [amount]="orderTotal" [currency]="'XAF'" [merchantReference]="orderId" [description]="orderDescription" [theme]="'light'" [primaryColor]="'#007bff'" [payButtonText]="'Finaliser le paiement'" (paymentSuccess)="handleSuccess($event)" (paymentError)="handleError($event)" (paymentCancel)="handleCancel()" (paymentStart)="handleStart()"> <!-- Contenu personnalisé du bouton --> <i class="fas fa-credit-card"></i> Payer {{ orderTotal | currency:'XAF':'symbol':'1.0-0' }} </ynkap-pay-button> ``` ### 2. Gestion des événements ```typescript export class PaymentComponent { orderTotal = 5000; orderId = 'ORDER-' + Date.now(); orderDescription = 'Commande e-commerce'; handleSuccess(response: PaymentResponse): void { // Paiement réussi console.log('Transaction ID:', response.transaction?.id); console.log('Statut:', response.transaction?.status); // Sauvegarder en base de données this.saveTransaction(response.transaction); // Redirection this.router.navigate(['/success'], { queryParams: { transactionId: response.transaction?.id } }); } handleError(error: YnkapError): void { // Gestion des différents types d'erreurs switch (error.code) { case 'INSUFFICIENT_FUNDS': this.showMessage('Solde insuffisant'); break; case 'NETWORK_ERROR': this.showMessage('Problème de connexion. Veuillez réessayer.'); break; case 'PAYMENT_DECLINED': this.showMessage('Paiement refusé par votre opérateur'); break; default: this.showMessage('Une erreur est survenue: ' + error.userMessage); } } handleCancel(): void { console.log('Paiement annulé par l\'utilisateur'); // Optionnel: analytics, nettoyage, etc. } handleStart(): void { console.log('Processus de paiement démarré'); // Optionnel: analytics, loading state, etc. } } ``` --- ## **API Reference** ### YnkapPayButtonComponent #### Inputs (Propriétés) | Propriété | Type | Requis | Défaut | Description | |-----------|------|--------|--------|-------------| | `amount` | `number` | | - | Montant de la transaction en centimes | | `merchantReference` | `string` | | - | Référence unique de la transaction | | `description` | `string` | | - | Description affichée à l'utilisateur | | `currency` | `string` | | `'XAF'` | Code devise ISO (XAF, EUR, USD) | | `payButtonText` | `string` | | `'Payer {amount}'` | Texte du bouton de paiement | | `theme` | `'light' \| 'dark'` | | `'light'` | Thème de l'interface | | `primaryColor` | `string` | | `'#007bff'` | Couleur principale (hex) | | `disabled` | `boolean` | | `false` | Désactive le bouton | | `autoRetry` | `boolean` | | `true` | Active le retry automatique | | `maxRetries` | `number` | | `3` | Nombre max de tentatives | #### Outputs (Événements) | Événement | Type | Description | |-----------|------|-------------| | `paymentSuccess` | `EventEmitter<PaymentResponse>` | Paiement réussi | | `paymentError` | `EventEmitter<YnkapError>` | Erreur de paiement | | `paymentCancel` | `EventEmitter<void>` | Paiement annulé | | `paymentStart` | `EventEmitter<void>` | Début du processus | | `paymentRetry` | `EventEmitter<number>` | Tentative de retry (numéro) | ### Services disponibles #### ConfigurationService ```typescript import { ConfigurationService } from '@yaba-in/ynkap-payment'; // Injection constructor(private config: ConfigurationService) {} // Méthodes principales setConfig(config: ApiConfig): void getConfig(): ApiConfig | null setEnvironment(env: 'sandbox' | 'production'): void clearConfig(): void // Propriétés get apiUrl(): string get environment(): string get apiKey(): string | null ``` #### PaymentService ```typescript import { PaymentService } from '@yaba-in/ynkap-payment'; // Injection constructor(private payment: PaymentService) {} // Méthodes principales initiatePayment(request: PaymentRequest): Observable<PaymentResponse> retryPayment(transactionId: string): Observable<PaymentResponse> getPaymentStatus(transactionId: string): Observable<PaymentTransaction> // Observables availablePaymentMethods$: Observable<PaymentMethod[]> currentTransaction$: Observable<PaymentTransaction | null> ``` #### RetryService ```typescript import { RetryService } from '@yaba-in/ynkap-payment'; // Injection constructor(private retry: RetryService) {} // Méthodes principales isRetryable(error: YnkapError): boolean getRetryInfo(transactionId: string): string shouldAutoRetry(error: YnkapError): boolean ``` --- ## **Gestion des erreurs** ### Types d'erreurs Le module Y-Nkap utilise un système d'erreurs typées pour une gestion précise : ```typescript interface YnkapError extends Error { code: string; // Code d'erreur technique userMessage?: string; // Message utilisateur localisé retryable?: boolean; // Indique si l'erreur est retryable httpStatus?: number; // Code de statut HTTP transactionId?: string; // ID de transaction si disponible } ``` ### Codes d'erreur courants | Code | Description | Retryable | Action recommandée | |------|-------------|-----------|-------------------| | `NETWORK_ERROR` | Problème de connexion | | Retry automatique | | `SERVER_ERROR` | Erreur serveur (5xx) | | Retry automatique | | `INSUFFICIENT_FUNDS` | Solde insuffisant | | Demander à l'utilisateur de recharger | | `PAYMENT_DECLINED` | Paiement refusé | | Vérifier les informations | | `INVALID_PHONE` | Numéro invalide | | Corriger le numéro | | `AUTH_ERROR` | Erreur d'authentification | | Vérifier les clés API | | `VALIDATION_ERROR` | Erreur de validation | | Corriger les données | ### Gestion avancée des erreurs ```typescript import { YnkapError, ErrorCategory } from '@yaba-in/ynkap-payment'; handleError(error: YnkapError): void { // Log pour debugging console.error('Payment Error:', { code: error.code, message: error.message, transactionId: error.transactionId, retryable: error.retryable }); // Gestion par catégorie switch (error.code) { case ErrorCategory.NETWORK: this.showRetryableError('Problème de connexion'); break; case ErrorCategory.PAYMENT_PROCESSING: this.showPaymentError(error.userMessage || 'Erreur de paiement'); break; case ErrorCategory.VALIDATION: this.showValidationError(error.userMessage || 'Données invalides'); break; default: this.showGenericError(); } } showRetryableError(message: string): void { // Afficher avec option de retry this.snackBar.open(message, 'Réessayer', { duration: 0, action: () => this.retryPayment() }); } ``` ### Retry automatique Le module inclut un système de retry intelligent : ```typescript // Configuration du retry YnkapModule.forRoot({ // ... autres options retryOptions: { maxRetries: 3, baseDelayMs: 1000, maxDelayMs: 30000, nonRetryableErrors: ['INSUFFICIENT_FUNDS', 'PAYMENT_DECLINED'] } }) ``` --- ## **Exemples avancés** ### 1. E-commerce avec panier ```typescript @Component({ selector: 'app-checkout', template: ` <div class="checkout-container"> <div class="order-summary"> <h3>Récapitulatif de commande</h3> <div *ngFor="let item of cartItems" class="cart-item"> {{ item.name }} - {{ item.price | currency:'XAF' }} </div> <div class="total"> Total: {{ orderTotal | currency:'XAF' }} </div> </div> <ynkap-pay-button [amount]="orderTotal" [merchantReference]="orderId" [description]="orderDescription" [theme]="userPreferences.theme" (paymentSuccess)="onPaymentSuccess($event)" (paymentError)="onPaymentError($event)" class="payment-button"> <div class="button-content"> <i class="fas fa-lock"></i> Paiement sécurisé {{ orderTotal | currency:'XAF' }} </div> </ynkap-pay-button> </div> ` }) export class CheckoutComponent { cartItems = [ { name: 'T-shirt Premium', price: 15000 }, { name: 'Jean Slim', price: 25000 } ]; get orderTotal(): number { return this.cartItems.reduce((sum, item) => sum + item.price, 0); } get orderId(): string { return `ORDER-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } get orderDescription(): string { return `Commande de ${this.cartItems.length} article(s)`; } onPaymentSuccess(response: PaymentResponse): void { // Vider le panier this.cartService.clearCart(); // Envoyer confirmation par email this.emailService.sendOrderConfirmation(response.transaction); // Analytics this.analytics.track('purchase', { transaction_id: response.transaction?.id, value: this.orderTotal, currency: 'XAF' }); // Redirection this.router.navigate(['/order-success'], { queryParams: { orderId: response.transaction?.merchantReference } }); } } ``` ### 2. Abonnement récurrent ```typescript @Component({ selector: 'app-subscription', template: ` <div class="subscription-plans"> <div *ngFor="let plan of plans" class="plan-card" [class.selected]="selectedPlan?.id === plan.id" (click)="selectPlan(plan)"> <h3>{{ plan.name }}</h3> <div class="price">{{ plan.price | currency:'XAF' }}/mois</div> <ul> <li *ngFor="let feature of plan.features">{{ feature }}</li> </ul> </div> </div> <ynkap-pay-button *ngIf="selectedPlan" [amount]="selectedPlan.price" [merchantReference]="subscriptionId" [description]="'Abonnement ' + selectedPlan.name" (paymentSuccess)="onSubscriptionSuccess($event)"> S'abonner à {{ selectedPlan.name }} </ynkap-pay-button> ` }) export class SubscriptionComponent { plans = [ { id: 'basic', name: 'Basic', price: 5000, features: ['10 GB stockage', 'Support email'] }, { id: 'premium', name: 'Premium', price: 15000, features: ['100 GB stockage', 'Support prioritaire', 'API access'] } ]; selectedPlan: any = null; get subscriptionId(): string { return `SUB-${this.selectedPlan?.id}-${Date.now()}`; } selectPlan(plan: any): void { this.selectedPlan = plan; } onSubscriptionSuccess(response: PaymentResponse): void { // Activer l'abonnement this.subscriptionService.activateSubscription({ userId: this.currentUser.id, planId: this.selectedPlan.id, transactionId: response.transaction?.id, startDate: new Date(), endDate: this.calculateEndDate() }); // Notification this.notificationService.success( `Abonnement ${this.selectedPlan.name} activé avec succès!` ); } } ``` ### 3. Intégration avec formulaires réactifs ```typescript @Component({ selector: 'app-donation', template: ` <form [formGroup]="donationForm" (ngSubmit)="onSubmit()"> <mat-form-field> <mat-label>Montant de la donation</mat-label> <input matInput type="number" formControlName="amount" placeholder="Montant en XAF"> <mat-error *ngIf="donationForm.get('amount')?.hasError('required')"> Le montant est requis </mat-error> <mat-error *ngIf="donationForm.get('amount')?.hasError('min')"> Montant minimum: 1000 XAF </mat-error> </mat-form-field> <mat-form-field> <mat-label>Message (optionnel)</mat-label> <textarea matInput formControlName="message" placeholder="Votre message de soutien"></textarea> </mat-form-field> <mat-checkbox formControlName="anonymous"> Donation anonyme </mat-checkbox> <ynkap-pay-button [amount]="donationForm.get('amount')?.value || 0" [merchantReference]="donationId" [description]="donationDescription" [disabled]="donationForm.invalid" (paymentSuccess)="onDonationSuccess($event)"> Faire un don de {{ donationForm.get('amount')?.value | currency:'XAF' }} </ynkap-pay-button> </form> ` }) export class DonationComponent { donationForm = this.fb.group({ amount: [null, [Validators.required, Validators.min(1000)]], message: [''], anonymous: [false] }); constructor(private fb: FormBuilder) {} get donationId(): string { return `DONATION-${Date.now()}`; } get donationDescription(): string { const amount = this.donationForm.get('amount')?.value; return `Donation de ${amount} XAF`; } onDonationSuccess(response: PaymentResponse): void { const formValue = this.donationForm.value; // Enregistrer la donation this.donationService.recordDonation({ amount: formValue.amount, message: formValue.message, anonymous: formValue.anonymous, transactionId: response.transaction?.id, donorId: formValue.anonymous ? null : this.currentUser.id }); // Reset du formulaire this.donationForm.reset(); // Message de remerciement this.dialog.open(ThankYouDialogComponent, { data: { amount: formValue.amount } }); } } ``` --- ## **Migration** ### Depuis la version 0.x ```typescript // Avant (v0.x) import { YnkapPaymentModule } from '@ynkap/payment-old'; YnkapPaymentModule.forRoot({ apiKey: 'key', sandbox: true }) // Après (v1.x) import { YnkapModule } from '@yaba-in/ynkap-payment'; YnkapModule.forRoot({ apiKey: 'key', apiSecret: 'secret', // Nouveau: requis merchantId: 'merchant', // Nouveau: requis environment: 'sandbox' // Changé: 'sandbox' | 'production' }) ``` ### Changements majeurs v1.0 - **Nouveau service RetryService** avec gestion avancée - **Types TypeScript** stricts pour toutes les APIs - **Gestion d'erreurs** améliorée avec codes spécifiques - **Interface utilisateur** redesignée avec thèmes - **Configuration** simplifiée avec validation - **Breaking:** `sandbox: boolean` `environment: 'sandbox' | 'production'` - **Breaking:** Nouveaux champs requis `apiSecret` et `merchantId` --- ## **Support** ### Documentation - [Guide complet](https://docs.ynkap.com/angular) - [Tutoriels vidéo](https://youtube.com/ynkap-tutorials) - [Blog technique](https://blog.yaba-in.com/ynkap) ### Communauté - [Discord](https://discord.gg/ynkap) - [Forum](https://forum.ynkap.com) - [Twitter @YnkapDev](https://twitter.com/ynkapdev) ### Support technique - [Issues GitHub](https://github.com/yaba-in/ynkap-angular/issues) - Email: support@ynkap.com - Téléphone: +237 6XX XXX XXX ### Contribution Nous accueillons les contributions ! Voir [CONTRIBUTING.md](CONTRIBUTING.md) --- ## **Licence** MIT © 2025 Yaba-In. Voir [LICENSE](LICENSE) pour plus de détails. --- ## **Remerciements** - Équipe de développement Yaba-In - Communauté Angular - Contributeurs open source - Opérateurs mobiles partenaires (Orange, MTN) --- **Développé par [Yaba-In](https://yaba-in.com)**