Skip to content

PRE-3551: create payment with Hosted Fields - #311

Draft
adumont-payplug wants to merge 1 commit into
developfrom
feature/PRE-3551_payment_hosted_fields
Draft

PRE-3551: create payment with Hosted Fields#311
adumont-payplug wants to merge 1 commit into
developfrom
feature/PRE-3551_payment_hosted_fields

Conversation

@adumont-payplug

@adumont-payplug adumont-payplug commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Description

Wires the payplug_uhf (Unified Hosted Fields) gateway to actually create and confirm payments through PayPlug's Unified API via UPC (payplug/unified-plugin-core), replacing PRE-3550's stub that only stored the hosted-fields token and force-completed checkout without ever calling a payment API.

Motivation:

PRE-3550 tokenizes a card client-side into an hfToken (+ selected brand + save-card flag) and PRE-3553 added the gateway configuration, but nothing between the two actually creates a payment: NullHostedFieldsPaymentProcessor just stores the token on Payment::details and bypasses Payum's Capture/Notify/Status pipeline entirely. This PR closes that gap end-to-end: Capture creates the payment via the Unified API, redirects for 3DS/SCA when required, and the asynchronous UPC webhook confirms the final outcome and updates the Sylius payment/order state — with all payment/outcome-mapping logic living in unified-plugin-core, not duplicated in the plugin.

Related issue(s): Closes PRE-3551


What changed

  • Six new Sylius adapters (src/Upc/) implementing UPC contracts that didn't have one yet:
    • SyliusUpcLoggerILogger (wraps the existing monolog.logger.payplug channel)
    • SyliusUpcConfigurationRepositoryIConfigurationRepository (reads the payplug_uhf gateway's Payum config — OAuth client_id/client_secret, public key material)
    • SyliusUnifiedApiHttpClientIUnifiedApiHttpClient (adapts Symfony's HttpClientInterface, same transport-exception handling as SyliusOAuthHttpClient)
    • SyliusUpcLockILock (non-blocking wrapper over Symfony LockFactory, for webhook idempotency)
    • SyliusPaymentOperationRepositoryIPaymentRepository (backed by the new PayPlugOperation entity)
    • SyliusOrderStateMutatorIOrderStateMutator (maps PaymentOutcome::* to Sylius PaymentTransitions via the state machine)
  • New PayPlugOperation entity + migration (Version20260810120000), persisting each webhook operation (orderId, operationId, execCode, outcome, treated, createdAt) for idempotent webhook processing.
  • Capture pipeline for payplug_uhf: CaptureHostedPaymentRequest / Handler / CommandProvider — builds a HostedFieldDto from the stored hfToken/selectedBrand plus order/customer/browser context, calls UnifiedApiHostedPaymentService::createHostedPayment(), and either redirects for 3DS or leaves the payment "processing" pending the webhook.
  • Notify pipeline for payplug_uhf: NotifyHostedPaymentRequest / Handler / CommandProvider — a new, parallel handler (doesn't reuse the legacy NotifyPaymentRequestHandler, which is built around the old SDK's response shape). Verifies the webhook via WebhookNotificationHelper, guards against concurrent/retried delivery with ILock, persists the operation, and applies the outcome via IOrderStateMutator.
  • Status pipeline for payplug_uhf: StatusHostedPaymentRequest / Handler / CommandProvider — no polling; just reflects whatever state the webhook has already applied.
  • PostPaymentSelectEventSubscriber: Hosted Fields now redirects to sylius_shop_order_pay like every other gateway, instead of the old sylius_shop_order_show bypass that skipped Payum entirely.
  • CaptureHttpResponseProvider: tagged for payplug_uhf (the tag PRE-3550 left out).
  • composer.json: bumps payplug/unified-plugin-core to 1.0.0-rc0 (ships createHostedPayment() / WebhookNotificationHelper, which this PR depends on).
  • config/services.yaml: wires the six new adapters, the payplug_uhf command-provider/http-response-provider services, and new config parameters (payplug.unified_api_base_url, payplug.uhf_account_id, payplug.uhf_submerchant_external_id).

Known limitations (deferred on purpose)

  • No local Card persistence for saved UHF cards.
  • No status-polling fallback if a webhook is lost — the payment stays pending until resolved manually via the existing UpdatePaymentStateCommand.
  • Refunds for UHF payments are out of scope (tracked separately as PRE-3552).
  • payplug.uhf_account_id / payplug.uhf_submerchant_external_id are temporarily hardcoded via env-var defaults for QA (see the TEMPORARY (PRE-3551 QA testing) comments in config/services.yaml) — real per-merchant account resolution is a follow-up.
  • The new Behat feature (features/shop/hosted_fields_payment_and_webhook_flow.feature) documents the two target end-to-end scenarios, but several steps are marked # NEW — not yet defined: the Mocker/step-definition infrastructure they need doesn't exist yet, so this file is not runnable as-is.
  • No change to the legacy payplug/Oney/Bancontact/Scalapay/Apple Pay/American Express gateways — they keep using the existing PayPlugApiClient SDK flow untouched.

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)
  • 📦 Dependency update [x]

Checklist

Code Quality

  • Code is linted and formatted
  • No unnecessary commented-out code or debug logs
  • No hardcoded values (use env variables or config) — see "Known limitations": uhf_account_id/uhf_submerchant_external_id are intentionally hardcoded via env-var defaults for this QA phase

Testing

  • Unit tests added / updated — one test class per new src/Upc/ adapter and per new Capture/Notify/Status handler, covering the redirect vs. no-redirect branches, API exceptions, lock contention, and already-treated webhook replay
  • Behat scenarios are written but not yet executable (see "Known limitations")

Security & Ops

  • No sensitive data or secrets introduced
  • Logging and error handling are appropriate — webhook failures are logged and the lock is always released in a finally; Capture-time API errors fail the payment through the standard checkout error path

@adumont-payplug adumont-payplug self-assigned this Aug 12, 2026
@adumont-payplug
adumont-payplug marked this pull request as draft August 12, 2026 13:40
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.

1 participant