Skip to content

PRE-3670: Add authorization, capture and cancellation operation - #35

Merged
adumont-payplug merged 2 commits into
developfrom
feature/PRE-3670
Sep 29, 2026
Merged

adumont-payplug merged 2 commits into
developfrom
feature/PRE-3670

Conversation

@ilajili

@ilajili ilajili commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Description

Expose l'autorisation, la capture et l'annulation dans UPC, pour compléter le cycle de vie du paiement déjà couvert (paiement, remboursement, 3DS).

Ajouts principaux

  • CommonFieldsDto : authorizationType (PRE_AUTHORIZATION/FINAL_AUTHORIZATION) et partialAuthorization, tous deux uniquement pertinents avec capture: false — nestés respectivement sous operation.authorizationType et en top-level par BuildsCommonPayloadBody.
  • UnifiedApiPaymentService::capturePayment() et ::cancelPayment() — capture (immédiate ou différée, totale ou partielle, répétable) et annulation (totale par défaut, partielle si un montant est fourni), sur le même modèle que createRefund() déjà existant.
  • PaymentOutput étendu (maxCaptureDate, remainingCapturableAmount) ; nouveaux CaptureOutput/CancellationOutput exposant le montant restant capturable/annulable après chaque opération.
  • 10 nouvelles exceptions métier normalisant les réponses de l'API (autorisation expirée, déjà capturé/annulé, montant dépassé, conflit 409, annulation partielle non activée au contrat) au lieu d'un ApiException générique.
  • AuthorizationType (DataValues/), même pattern que PaymentOutcome.

Ce qui n'est délibérément pas couvert par ce PR

  • Idempotence : ce PR normalise le signal de l'API ("déjà capturé/annulé", conflit 409) en exceptions dédiées, mais ne garantit pas à lui seul l'absence de double capture sur une requête rejouée — cela dépend de IPaymentRepository/ILock (pas encore implémentés dans UPC), à charge du plugin CMS consommateur.
  • Contrats d'interface formels (Contracts/) pour capture/annulation : ces deux méthodes suivent le modèle à paramètres scalaires déjà établi par createRefund(), plutôt que d'introduire de nouvelles interfaces versionnées.
  • Changelog / bump de version : ce dépôt n'a pas de CHANGELOG.md ; le versioning se fait par tag Git au moment du cut d'une branche release/*, pas par PR de feature.

Related Issue

Ticket: PRE-3670

Type of Change

  • ✨ New feature

✅ Quality Checklist

Local Environment & Hooks

  • Local Git hooks (CaptainHook) are installed and executed cleanly (make install).
  • Commit messages strictly follow the (PRE|SMP)-XXXX: description pattern.
  • Branch name follows (feature|fix|hotfix|refactor)/(PRE|SMP)-XXXX... or (release|patch)/x.y.z.

Testing & Code Quality

  • Coding style rules have been applied locally (make cs-fix).
  • Static analysis passes with no new regressions (make stan — PHPStan level 8).
  • I have added/updated PHPUnit tests if applicable (make test).
  • No PHP syntax newer than 7.1 introduced in src/ or tests/ (no typed properties, arrow
    functions, constructor property promotion, match, enum).

CI/CD Deployment Context

  • The CI pipeline passes fully on GitHub, including the compatibility matrix
    (PHP 7.1 / 7.4 / 8.0 / 8.1 / 8.2) and the quality job.

Notes for Reviewer

Revue complète postée en tant que PR review avec commentaires en ligne. Résumé :

  • Architecture, emplacement des fichiers, compatibilité PHP 7.1 et couverture de tests solides ; vérifié indépendamment que make stan/make cs-lint/make test (440/440) passent tous sans régression sur les flux paiement/remboursement/3DS existants.
  • Deux points importants à traiter avant/après merge :
    1. UnifiedApiPaymentService::throwForMessageKeyword() — le test de mot-clé "already" + "captur" peut matcher par erreur à l'intérieur du mot "capturable" (pas seulement "captured"), risquant une exception mal classifiée sur un message du type "already voided and therefore not capturable".
    2. remainingCapturable/CancellableAmount de CaptureOutput/CancellationOutput suppose que le champ "amount" de l'API est cumulatif à travers des captures partielles successives — hypothèse non encore vérifiée sur une vraie réponse de succès (seul le chemin de rejet est testé en intégration).
  • L'AC7 (idempotence) est délibérément une solution partielle ici — voir "Ce qui n'est délibérément pas couvert" ci-dessus.

Mise à jour (490cd03)

Suite à la revue de @Jhoarrau, plusieurs corrections ont été apportées :

  • Point 1 résolu : l'ordre de classification dans assertOperationSuccess()/throwForMessageKeyword() a été revu — le mot-clé "duplicate" et l'execCode "4XXX" (refus émetteur) sont désormais vérifiés avant tout autre mot-clé générique (évitant qu'un refus émetteur contenant "expired"/"exceed" soit mal classifié), et "already captured"/"already cancelled" matchent une phrase contiguë (regex) plutôt que deux sous-chaînes indépendamment co-occurrentes.
  • currency est désormais validé localement (InvalidCaptureRequestException/InvalidCancellationRequestException) dès qu'un amount partiel est fourni à capturePayment()/cancelPayment(), au lieu de remonter un 400 opaque de l'API.
  • authorizationType/partialAuthorization sont désormais rejetés par CommonFieldsDtoValidator quand capture vaut true.
  • Les docblocks @throws de capturePayment()/cancelPayment() couvrent maintenant les exceptions "croisées" (ex. cancelPayment() peut lever PaymentAlreadyCapturedException).
  • Point 2 toujours ouvert : l'hypothèse sur remainingCapturable/CancellableAmount (cumulatif, et cohérence sous autorisation partielle / captures antérieures) reste non vérifiée contre une vraie réponse de succès — documentée explicitement dans le code et CLAUDE.md plutôt que corrigée. Idem pour l'ambiguïté de PartialCancellationNotAllowedException (contrat vs. montant invalide) et le cas non observé des execCodes "en attente" (0002/0003) sur une réponse 2xx.

Toutes les discussions ouvertes par @Jhoarrau ont reçu une réponse indiquant leur résolution (ou, pour les deux points ci-dessus, pourquoi ils restent documentés plutôt que corrigés).

@ilajili ilajili self-assigned this Sep 23, 2026
@ilajili
ilajili force-pushed the feature/PRE-3670 branch 4 times, most recently from 75cbc25 to 745483b Compare September 25, 2026 08:10

@adumont-payplug adumont-payplug left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code review — capture/cancel/authorization lifecycle

Solid, well-tested work overall: capturePayment()/cancelPayment() genuinely reuse createRefund()'s pattern, file placement (AuthorizationType in DataValues/, CaptureOutput/CancellationOutput in Output/, 10 new exception marker classes) all match this repo's established conventions, and PHP 7.1 compatibility is intact throughout. Verified independently: make stan (level 8) clean, make cs-lint clean, make test 440/440 passing — no regression on the existing payment/refund/3DS flows.

Left 3 inline comments on specific findings (1 Important correctness risk in the error-classification keyword matching, 1 Important unverified assumption behind remainingCapturableAmount, 1 documentation-scope note on AC7/idempotency). Nothing blocking at the Critical level.

Acceptance criteria (PRE-3670), quick summary: 6/10 fully met (AC2, AC4, AC6, AC8, AC9, plus AC3 with a caveat noted inline), AC1/AC5/AC7 partially met (each is a reasonable, documented interpretation rather than a gap in effort — see inline comments), AC10 (changelog/version) is out of scope for this PR per the repo's existing tag-based release flow (no CHANGELOG.md exists in this repo).

Ready to merge with the two Important fixes below addressed (or at least explicitly acknowledged/tracked).

Comment thread src/Services/UnifiedApiPaymentService.php Outdated
Comment thread src/Output/CaptureOutput.php
Comment thread CLAUDE.md

@jhoaraupp jhoaraupp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review — PRE-3670 capture / annulation / autorisation

Bonne base : capturePayment()/cancelPayment() reprennent bien le modèle de createRefund(), les fichiers sont au bon endroit (DataValues/, Output/, exceptions marqueurs), la compatibilité PHP 7.1 est respectée et les tests sont nombreux. La correction du premier retour (not capturable/not voidable testés avant already) est bien en place et testée.

Il me reste surtout deux sujets, détaillés en commentaires :

  1. Classification des erreurs (1 HIGH, 2 MEDIUM) : les mots-clés génériques (expired, exceed, already+captur) passent avant le test execCode émetteur et matchent des tournures imprévues. Refus émetteur et cas ambigus sont donc mal classés (vérifié en rejouant la logique en local).
  2. Montants restants (2 MEDIUM) : remainingCapturableAmount ignore l'autorisation partielle et remainingCancellableAmount ignore les captures déjà faites. Ce sont des problèmes de formule, distincts du point sur le caractère cumulatif de amount.

Plus quelques points LOW/NIT (currency non validé quand un montant est fourni, execCodes en attente, @throws incomplets, champs d'autorisation acceptés avec capture=true).

Je recommande de traiter le HIGH et le point currency avant merge. Les autres peuvent faire l'objet d'un ticket de suivi.

Comment thread src/Services/UnifiedApiPaymentService.php
Comment thread src/Services/UnifiedApiPaymentService.php Outdated
Comment thread src/Services/UnifiedApiPaymentService.php
Comment thread src/Services/UnifiedApiPaymentService.php
Comment thread src/Output/CaptureOutput.php
Comment thread src/Output/CancellationOutput.php
Comment thread src/Services/UnifiedApiPaymentService.php
Comment thread src/Services/UnifiedApiPaymentService.php
Comment thread src/Validators/CommonFieldsDtoValidator.php
Comment thread src/Services/UnifiedApiPaymentService.php
@adumont-payplug
adumont-payplug merged commit cf1df65 into develop Sep 29, 2026
14 checks passed
@adumont-payplug
adumont-payplug deleted the feature/PRE-3670 branch September 29, 2026 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants