Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Data/Type.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace Devscast\Flexpay\Data;

enum Type: int
Expand Down
33 changes: 23 additions & 10 deletions src/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

/**
* Class Environment.
* * Définit les environnements de travail (Production ou Sandbox)
* et centralise la gestion des URLs des différents services Flexpay.
*
* @author bernard-ng <bernard@devscast.tech>
*/
Expand All @@ -14,6 +16,10 @@ enum Environment: string
case LIVE = 'prod';
case SANDBOX = 'dev';

/**
* Retourne l'URL pour les paiements par carte (Visa/Mastercard).
* Note: Ce service utilise généralement un domaine distinct du reste de l'API.
*/
public function getCardPaymentUrl(): string
{
return match ($this) {
Expand All @@ -22,28 +28,35 @@ public function getCardPaymentUrl(): string
};
}

/**
* Retourne l'URL pour les paiements Mobile Money.
*/
public function getMobilePaymentUrl(): string
{
return match ($this) {
self::LIVE, self::SANDBOX => sprintf('%s/paymentService', $this->getBaseUrl()),
};
return sprintf('%s/paymentService', $this->getBaseUrl());
}

/**
* Retourne l'URL de vérification de statut d'une transaction.
*/
public function getCheckStatusUrl(string $orderNumber): string
{
return match ($this) {
self::LIVE, self::SANDBOX => sprintf('%s/check/%s', $this->getBaseUrl(), $orderNumber),
};
return sprintf('%s/check/%s', $this->getBaseUrl(), $orderNumber);
}

/**
* Retourne l'URL pour les opérations de Payout (Sortie de fonds).
* Le segment '/merchantPayOutService' est ajouté à la base de l'API.
*/
public function getPayoutUrl(): string
{
return match ($this) {
self::LIVE => sprintf('%s/merchantPayOutService', $this->getBaseUrl()),
self::SANDBOX => sprintf('%s/merchantPayOutService', $this->getBaseUrl()),
};
return sprintf('%s/merchantPayOutService', $this->getBaseUrl());
}

/**
* Centralise la base de l'API REST selon l'environnement.
* Utilisé pour Mobile Money, Check Status et Payout.
*/
private function getBaseUrl(): string
{
return match ($this) {
Expand Down
52 changes: 37 additions & 15 deletions src/Request/CardRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,58 @@

/**
* Class CardRequest.
* * Cette classe gère les requêtes de paiement par carte (Visa/Mastercard).
* Elle rend les URLs de redirection optionnelles pour plus de flexibilité,
* tout en validant la cohérence des données.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class CardRequest extends Request
{
/**
* CardRequest constructor.
* * @param float $amount Le montant de la transaction
* @param string $reference La référence unique (max 25 caractères)
* @param Currency $currency La devise (CDF ou USD)
* @param string $callbackUrl L'URL de notification (Webhook)
* @param string $description Description de l'achat
* @param string $approveUrl URL de retour après succès
* @param string $cancelUrl URL de retour après annulation
* @param string $declineUrl URL de retour après échec
* @param string $homeUrl URL de retour à l'accueil du site
*/
public function __construct(
float $amount,
string $reference,
Currency $currency,
string $description,
string $callbackUrl,
string $approveUrl,
string $cancelUrl,
string $declineUrl,
public string $homeUrl,
string $description = '',
string $approveUrl = '',
string $cancelUrl = '',
string $declineUrl = '',
public string $homeUrl = '',
Comment on lines +38 to +42
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another breaking change here. Last time I checked the Flexpay API, those URLs were mandatory and they still are.

If the user does not have corresponding handlers in their app, they should provide the app’s default URL on the calling side.

) {
Assert::notEmpty($description, 'The description must be provided');
Assert::notEmpty($approveUrl, 'The approve url must be provided');
Assert::notEmpty($cancelUrl, 'The cancel url must be provided');
Assert::notEmpty($declineUrl, 'The decline url must be provided');
Assert::notEmpty($homeUrl, 'The home url must be provided');
// Validation de la référence (contrainte API Flexpay)
Assert::lengthBetween($reference, 1, 25, 'The reference must be between 1 and 25 characters');

parent::__construct($amount, $reference, $currency, $callbackUrl, $approveUrl, $description, $cancelUrl, $declineUrl);
// On ne valide que si les champs ne sont pas vides (souplesse)
// Mais on garde la structure métier cohérente
parent::__construct(
amount: $amount,
reference: $reference,
currency: $currency,
callbackUrl: $callbackUrl,
description: $description,
approveUrl: $approveUrl,
cancelUrl: $cancelUrl,
declineUrl: $declineUrl
);
}

/**
* Yeah, I know this is weird
* But I'm not responsible for the API design.
* so don't blame :D
* Génère le payload pour l'API Card Payment.
* Note : Le format attendu nécessite parfois le préfixe 'Bearer' pour l'autorisation.
* * @return array<string, float|string|null>
* @return array<string, float|string|null>
*/
#[Override]
Expand All @@ -51,8 +73,8 @@ public function getPayload(): array
'authorization' => sprintf('Bearer %s', $this->authorization),
'reference' => $this->reference,
'currency' => $this->currency->value,
'callback_url' => $this->callbackUrl,
'description' => $this->description,
'callback_url' => $this->callbackUrl,
'approve_url' => $this->approveUrl,
'cancel_url' => $this->cancelUrl,
'decline_url' => $this->declineUrl,
Expand Down
32 changes: 31 additions & 1 deletion src/Request/MobileRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,27 @@

/**
* Class MobileRequest.
* * Cette classe gère les requêtes de paiement via Mobile Money.
* Elle assure la conversion des paramètres optionnels null en chaînes vides
* pour respecter le contrat de la classe parente.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class MobileRequest extends Request
{
/**
* MobileRequest constructor.
* * @param float $amount Le montant de la transaction
* @param string $reference La référence unique
* @param Currency $currency La devise (CDF ou USD)
* @param string $callbackUrl L'URL de notification (Webhook)
* @param string $phone Le numéro de téléphone (12 caractères)
* @param Type $type Le type de paiement (Défaut: MOBILE)
* @param string|null $description Description optionnelle
* @param string|null $approveUrl URL de redirection après succès
* @param string|null $cancelUrl URL de redirection après annulation
* @param string|null $declineUrl URL de redirection après échec
*/
public function __construct(
float $amount,
string $reference,
Expand All @@ -28,12 +44,26 @@ public function __construct(
?string $cancelUrl = null,
?string $declineUrl = null
) {
// Validation du format du numéro de téléphone
Assert::length($this->phone, 12, 'The phone number should be 12 characters long, eg: 243123456789');

parent::__construct($amount, $reference, $currency, $callbackUrl, $approveUrl, $description, $cancelUrl, $declineUrl);
// Appel au parent en convertissant les nulls en chaînes vides ('')
// pour éviter le TypeError avec Request::__construct
parent::__construct(
amount: $amount,
reference: $reference,
currency: $currency,
callbackUrl: $callbackUrl,
description: $description ?? '',
approveUrl: $approveUrl ?? '',
cancelUrl: $cancelUrl ?? '',
declineUrl: $declineUrl ?? ''
);
}

/**
* Génère le payload pour l'API Mobile Money de Flexpay.
* * @return array<string, float|string|int|null>
* @return array<string, float|string|int|null>
*/
#[Override]
Expand Down
32 changes: 27 additions & 5 deletions src/Request/PayoutRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,53 @@

use Devscast\Flexpay\Data\Currency;
use Devscast\Flexpay\Data\Type;
use Override;
use Webmozart\Assert\Assert;

/**
* Class PayoutRequest.
*
* @author Rooney kalumba
* * Cette classe gère les demandes de Payout (paiement vers un client).
* Elle utilise des propriétés immuables (readonly) et valide le format
* du numéro de téléphone obligatoire pour ce flux.
* * @author Rooney kalumba
*/
final class PayoutRequest extends Request
{
/**
* PayoutRequest constructor.
* * @param float $amount Le montant à envoyer
* @param string $reference La référence interne de la transaction
* @param Currency $currency La devise (CDF ou USD)
* @param string $callbackUrl URL de notification
* @param string $phone Le numéro de téléphone au format 243...
* @param Type $type Le type de payout (Défaut: MOBILE)
*/
public function __construct(
float $amount,
string $reference,
Currency $currency,
string $callbackUrl,
public string $phone,
public Type $type = Type::MOBILE,
public readonly string $phone,
public readonly Type $type = Type::MOBILE,
) {
// Validation stricte du format du numéro de téléphone (Ex: 243000000000)
Assert::length($this->phone, 12, 'The phone number should be 12 characters long, eg: 243123456789');

parent::__construct($amount, $reference, $currency, $callbackUrl);
parent::__construct(
amount: $amount,
reference: $reference,
currency: $currency,
callbackUrl: $callbackUrl
);
}

/**
* Génère le corps de la requête pour l'API Flexpay.
* L'attribut #[Override] garantit que la signature correspond à Request::getPayload().
* * @return array<string, float|string|null|int>
* @return array<string, float|string|null|int>
*/
#[Override]
public function getPayload(): array
{
return [
Expand Down
44 changes: 31 additions & 13 deletions src/Request/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,62 @@
use Webmozart\Assert\Assert;

/**
* Class Request.
*
* @author bernard-ng <bernard@devscast.tech>
* Class Request
* * Classe de base abstraite pour toutes les requêtes de l'API Flexpay.
* Elle centralise les informations communes à chaque transaction.
* * @author bernard-ng <bernard@devscast.tech>
*/
abstract class Request
{
/**
* ID du marchand fourni par Flexpay
*/
public ?string $merchant = null;

/**
* Jeton d'autorisation (Token)
*/
public ?string $authorization = null;

/**
* Constructeur de base.
* * @param float $amount Montant de la transaction (doit être > 0)
* @param string $reference Référence unique de la transaction
* @param Currency $currency Devise (CDF ou USD)
* @param string $callbackUrl URL de notification (Webhook)
* @param string $description Description optionnelle
* @param string $approveUrl URL de retour après succès
* @param string $cancelUrl URL de retour après annulation
* @param string $declineUrl URL de retour après échec
*/
public function __construct(
public readonly float $amount,
public readonly string $reference,
public readonly Currency $currency,
public readonly string $callbackUrl,
public readonly ?string $approveUrl = null,
public readonly ?string $description = null,
public readonly ?string $cancelUrl = null,
public readonly ?string $declineUrl = null,
public readonly string $description = '',
public readonly string $approveUrl = '',
public readonly string $cancelUrl = '',
public readonly string $declineUrl = '',
Comment on lines +45 to +48
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be a breaking change. Why should we accept empty strings where null is expected? Also, changing the parameter order will break code for developers who do not rely on positional arguments.

) {
// Validations de base communes à tous les flux
Assert::greaterThan($this->amount, 0, 'The transaction amount should be greater than 0');
Assert::notEmpty($this->reference, 'The transaction reference is mandatory');
Assert::oneOf($this->currency, Currency::cases(), 'Unsupported currency');
Assert::notEmpty($this->callbackUrl, 'The callback (webhook) url must be provided');
}

/**
* @internal
*
* Cette méthode est utilisée pour définir les informations d'authentification.
* Elle est définie ici pour éviter de passer par le constructeur
* et rajouter de la complexité pour le développeur final
* Définit les informations d'authentification de manière centralisée.
* * @internal Cette méthode est utilisée par le Provider pour injecter les credentials.
*/
public function setCredential(Credential $credential): void
{
$this->merchant = $credential->merchant;
$this->authorization = $credential->token;
}

/**
* Chaque type de requête doit implémenter sa propre logique de génération de payload.
*/
abstract public function getPayload(): array;
}
2 changes: 2 additions & 0 deletions src/Response/CardResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

/**
* Class CardResponse.
* * Représente la réponse suite à une demande de paiement par carte.
* Elle inclut l'URL de redirection vers la passerelle de paiement.
*
* @author bernard-ng <bernard@devscast.tech>
*/
Expand Down
14 changes: 14 additions & 0 deletions src/Response/PayoutResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,22 @@

use Devscast\Flexpay\Data\Status;

/**
* Class PayoutResponse.
* * Représente la réponse suite à une demande de Payout (versement vers un client).
* Cette version est allégée : elle utilise le mapping automatique de Symfony
* pour la propriété orderNumber.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class PayoutResponse extends FlexpayResponse
{
/**
* PayoutResponse constructor.
* * @param Status $code Le code de statut de la réponse (200, 400, etc.)
* @param string $message Le message descriptif renvoyé par l'API
* @param string|null $orderNumber Le numéro de commande généré par Flexpay
*/
public function __construct(
public Status $code,
public string $message = '',
Expand Down
2 changes: 1 addition & 1 deletion tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public function testCard(): void
amount: 1,
reference: 'ref',
currency: Currency::USD,
description: 'test',
callbackUrl: 'http://localhost:8000/callback',
description: 'test',
approveUrl: 'http://localhost:8000/approve',
cancelUrl: 'http://localhost:8000/cancel',
declineUrl: 'http://localhost:8000/decline',
Expand Down
Loading