-
Notifications
You must be signed in to change notification settings - Fork 19
Feature/pre 3469 #305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
jhoaraupp
wants to merge
11
commits into
develop
Choose a base branch
from
feature/PRE-3469
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Feature/pre 3469 #305
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
690d9f5
PRE-3469: Validate contracts with Sylius
jhoaraupp b97c69d
PRE-3469: Promote unified-plugin-core to a real dependency
jhoaraupp 29b4b8d
PRE-3469: Remove design spec accidentally committed in b97c69d
jhoaraupp e6ad438
PRE-3469: Promote SyliusOrderStateMutator to PayplugOrderStateMutator
jhoaraupp 463c79c
PRE-3469: Promote SyliusTokenCache to PayplugTokenCache
jhoaraupp 753ded3
PRE-3469: Promote SyliusConfigurationRepository to PayplugConfigurati…
jhoaraupp d770af8
PRE-3469: Wire PayplugOrderStateMutator into NotifyPaymentRequestHandler
jhoaraupp 1a89a34
PRE-3469: Fix phpstan error in NotifyPaymentRequestHandler's mutator …
jhoaraupp ddb7742
PRE-3469: Update VALIDATION.md for the promoted contracts
jhoaraupp f0cd2a8
PRE-3469: Add explicit service alias for IOrderStateMutator
jhoaraupp 3ded48e
PRE-3469: Guard PayplugOrderStateMutator call against multi-payment o…
jhoaraupp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1,555 changes: 1,555 additions & 0 deletions
1,555
docs/superpowers/plans/2026-07-21-pre-3469-upc-contracts-rework.md
Large diffs are not rendered by default.
Oops, something went wrong.
65 changes: 65 additions & 0 deletions
65
docs/superpowers/specs/2026-07-20-pre-3469-upc-contracts-rework-design.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # PRE-3469 rework — real UPC contract wiring | ||
|
|
||
| ## Context | ||
|
|
||
| [PRE-3469](https://payplug-prod.atlassian.net/browse/PRE-3469) originally asked for isolated proof-of-concept skeletons (under `src/Spike/`) validating that 4 Unified Plugin Core (UPC) contracts — `IOrderStateMutator`, `ITokenCache`, `IConfigurationRepository`, `IPaymentRepository` — are satisfiable against real Sylius APIs, before freezing the interfaces. That work landed in commit `690d9f5` and is fully written up in `src/Spike/VALIDATION.md`. | ||
|
|
||
| The ticket's "Résultats attendus" were updated twice on 2026-07-20. The current version requires: | ||
|
|
||
| - Real, production-wired implementations (not isolated `src/Spike/` skeletons) for `PayplugOrderStateMutator`, `PayplugTokenCache`, `PayplugConfigurationRepository`. | ||
| - `IPaymentRepository` explicitly **out of scope** (tied to a Value Object too coupled to the not-yet-built Unified API) — the existing `SyliusPaymentRepository` sketch is sufficient as-is. | ||
| - No regression on existing plugin functionality (PHPUnit + Behat stay green). | ||
| - Any blocking friction documented, or the interfaces adjusted. | ||
|
|
||
| This spec covers reworking the branch to meet the updated scope. | ||
|
|
||
| ## Existing production code map | ||
|
|
||
| Research (see conversation) found the production analogs each contract needs to be validated against: | ||
|
|
||
| | Contract | Production analog | Notes | | ||
| |---|---|---| | ||
| | `IOrderStateMutator` | `PaymentTransitionApplier` (called from `NotifyPaymentRequestHandler`/`StatusPaymentRequestHandler`, the webhook/status-poll path) and `PaymentStateResolver` (called from the CLI reconciliation command `payplug:update-payment-state`) | Two separate, non-unified implementations of the same guarded `can()`/`apply()` idiom already exist in production; this ticket does not unify them. | | ||
| | `ITokenCache` | `PayPlugApiClientFactory::getTokenForGatewayConfig()`'s inline `Symfony\Contracts\Cache\CacheInterface` usage, caching the OAuth JWT access token per gateway factory/live-test scope | Confirmed by the ticket's second update: targets the OAuth JWT cache, not card/token storage (saved cards are a permanent Doctrine entity, `Card.php`, uninvolved with any cache). | | ||
| | `IConfigurationRepository` | Read side: `PayPlugApiClientFactory::getTokenForGatewayConfig()` (`client_id`/`client_secret` scoped by `config['live']`). Write side: `UnifiedAuthenticationController::oauthCallback()` (persists `live_client`/`test_client`, busts the token cache). | No production equivalent exists for `getPublicKeyId()`/`getPublicKeyValue()` — Hosted Fields public-key material isn't implemented anywhere in the plugin today. | | ||
| | `IPaymentRepository` | None — out of scope | `SyliusPaymentRepository` + `PayplugOperation` entity + `Version20260720100000` migration stay as an unwired sketch. | | ||
|
|
||
| ## Design | ||
|
|
||
| ### 1. `PayplugOrderStateMutator` — real production call site | ||
|
|
||
| Keep the existing mapping logic from `src/Spike/SyliusOrderStateMutator.php`: `PaymentOutcome::PAID/AUTHORIZED/CAPTURE_REQUIRED/REFUNDED/FAILED` → `PaymentTransitions::TRANSITION_COMPLETE/AUTHORIZE/PROCESS/REFUND/FAIL`, `THREE_DS_PENDING` as a confirmed no-op, `StateMachineInterface::can()` guard before `apply()`, explicit `EntityManagerInterface::flush()` after. | ||
|
|
||
| Wire it as an **additive** call inside `NotifyPaymentRequestHandler::__invoke()`, immediately after the existing `PaymentTransitionApplier::apply($payment)` call. The handler already has everything needed to derive a `PaymentOutcome` from the same status signal `PaymentTransitionApplier` consumes (`$payment->getDetails()['status']`, populated upstream by `PaymentNotificationHandler::treat()`); translate that into a `PaymentOutcome` value and call `$orderStateMutator->apply($order->getId(), $outcome)`. | ||
|
|
||
| Because `PaymentTransitionApplier::apply()` already applied the transition earlier in the same handler invocation, `PayplugOrderStateMutator`'s own `can()` guard will find the transition no longer applicable and no-op. This proves the mutator works against a real, live webhook event without replacing or risking the existing transition-application path. No change to `PaymentStateResolver` or the CLI reconciliation command. | ||
|
|
||
| ### 2. `PayplugTokenCache` — real integration test, no production call site | ||
|
|
||
| No change to `PayPlugApiClientFactory::getTokenForGatewayConfig()`. Promote the spike's existing unit-mocked test (`tests/PHPUnit/Spike/SyliusTokenCacheTest.php`) with a new real-infrastructure test that exercises `get`/`set`/`delete` against an actual `cache.app`-equivalent PSR-6 pool (filesystem or array adapter, same spirit as `SpikeIntegrationTest`'s real MariaDB), rather than a mocked `CacheItemPoolInterface`. This validates `getItem`/`save`/`deleteItem` "dans le flux réel" per the ticket, without introducing any new code on a live request path that gates authentication for every PayPlug gateway. | ||
|
|
||
| ### 3. `PayplugConfigurationRepository` — real integration test, no production call site | ||
|
|
||
| Same treatment as `PayplugTokenCache`: add a real integration test that constructs `SyliusConfigurationRepository` against an actual `GatewayConfigInterface` on a real `PaymentMethod` fixture persisted via Doctrine (rather than a mock), validating `get`/`set`/`getClientId`/`getClientSecret` against real Sylius config storage. No change to `PayPlugApiClientFactory` or `UnifiedAuthenticationController`. | ||
|
|
||
| `getPublicKeyId()`/`getPublicKeyValue()` read from two new config array keys, `public_key_id` and `public_key_value`, added alongside the existing `live_client`/`test_client` keys, defaulting to an empty string when absent. Nothing in production writes these keys yet — this is implementable end-to-end and ready for whenever Hosted Fields lands, documented as a friction note rather than worked around or blocked on. | ||
|
|
||
| ### 4. `IPaymentRepository` — unchanged | ||
|
|
||
| `SyliusPaymentRepository`, `Entity/PayplugOperation.php`, and the `Version20260720100000` migration stay exactly as they are: an unwired sketch, correctly out of scope per the ticket. | ||
|
|
||
| ### 5. Documentation | ||
|
|
||
| Update `src/Spike/VALIDATION.md` to reflect the new verdict per contract: which ones are now genuinely wired into production vs. validated only through real-infrastructure tests, and the two friction notes (`public_key_id`/`public_key_value` have no writer yet; `IOrderStateMutator`/`IConfigurationRepository` production analogs exist as more than one non-unified implementation, which this ticket does not consolidate). | ||
|
|
||
| ## Testing / regression safety | ||
|
|
||
| - Existing PHPUnit + Behat suites must stay green after the changes. | ||
| - The one production code change (the additive `PayplugOrderStateMutator` call in `NotifyPaymentRequestHandler`) is guarded by the existing `can()` check, so it cannot alter current transition behavior — only observe/replay it against an already-applied transition. | ||
| - New real-infrastructure tests for `PayplugTokenCache` and `PayplugConfigurationRepository` are added, not replacing the existing unit-mocked spike tests. | ||
|
|
||
| ## Out of scope | ||
|
|
||
| - Unifying `PaymentTransitionApplier` and `PaymentStateResolver` into a single `IOrderStateMutator`-backed implementation. | ||
| - Any production wiring of `IPaymentRepository`. | ||
| - Building Hosted Fields / any actual consumer of `public_key_id`/`public_key_value`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace PayPlug\SyliusPayPlugPlugin\Migrations; | ||
|
|
||
| use Doctrine\DBAL\Schema\Schema; | ||
| use Doctrine\Migrations\AbstractMigration; | ||
|
|
||
| /** | ||
| * PRE-3469 spike only: schema for the throwaway PayplugOperation entity (src/Spike/Entity). | ||
| * A real migration — not a manual step or a SchemaTool call from the test — because the | ||
| * mapping is registered whenever kernel.environment=test (see | ||
| * PayPlugSyliusPayPlugExtension::prependSpikeDoctrineMapping()), which includes routine | ||
| * `sylius:fixtures:load` runs on a fresh checkout, not just the spike's own integration test. | ||
| * Without this migration, `sylius:fixtures:load` fails for anyone setting up the test | ||
| * application from scratch — found by testing this on a clean database, not by reasoning about | ||
| * it. Guarded to APP_ENV=test in both directions so a real merchant deployment never gets this | ||
| * table created (up) or, if it somehow did, never gets it dropped outside test either (down) — | ||
| * a normal plugin migration otherwise runs unconditionally, including in production. Drop this | ||
| * migration together with src/Spike/ once the spike is closed. | ||
| */ | ||
| final class Version20260720100000 extends AbstractMigration | ||
| { | ||
| public function getDescription(): string | ||
| { | ||
| return 'PRE-3469 spike: add payplug_operation table for the throwaway PayplugOperation entity.'; | ||
| } | ||
|
|
||
| public function up(Schema $schema): void | ||
| { | ||
| $this->skipIf( | ||
| 'test' !== ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null), | ||
| 'PRE-3469 spike-only schema, not applied outside the test environment.', | ||
| ); | ||
|
|
||
| $this->addSql('CREATE TABLE payplug_operation ( | ||
| id INT AUTO_INCREMENT NOT NULL, | ||
| operation_id VARCHAR(255) NOT NULL, | ||
| exec_code VARCHAR(255) NOT NULL, | ||
| outcome VARCHAR(255) NOT NULL, | ||
| amount INT NOT NULL, | ||
| order_id VARCHAR(255) NOT NULL, | ||
| treated TINYINT(1) NOT NULL, | ||
| treated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', | ||
| created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', | ||
| UNIQUE INDEX payplug_operation_id_unique (operation_id), | ||
| INDEX payplug_operation_order_id_idx (order_id), | ||
| PRIMARY KEY(id) | ||
| ) DEFAULT CHARACTER SET UTF8 COLLATE `UTF8_unicode_ci` ENGINE = InnoDB'); | ||
| } | ||
|
|
||
| public function down(Schema $schema): void | ||
| { | ||
| $this->skipIf( | ||
| 'test' !== ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null), | ||
| 'PRE-3469 spike-only schema, not applied outside the test environment.', | ||
| ); | ||
|
|
||
| $this->addSql('DROP TABLE payplug_operation'); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.