Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sylius Quickpay Refund Bridge Plugin Laravel Package

setono/sylius-quickpay-refund-bridge-plugin

A Sylius plugin that bridges QuickPay refunds into your store's payment workflow, letting you process and manage QuickPay refunds from within the Sylius admin and order management interface.

View on GitHub
Deep Wiki
Context7

Getting Started

This plugin is a bridge: it does not provide the QuickPay payment method itself, it only connects Sylius' refund workflow (from sylius/refund-plugin) to QuickPay's refund API. Before touching the bridge, make sure the two halves it connects are already working.

  1. Confirm the prerequisites are installed and green, in this order:
    • sylius/refund-plugin is installed, migrations are run (sylius_refund_* tables present), and the "Refunds" admin section works. Try a refund against a manual/offline payment first.
    • Your QuickPay gateway plugin is installed and configured as a Sylius payment method, and normal (non-refund) payments authorize/capture successfully in QuickPay's test environment.
  2. Install the bridge:
    composer require setono/sylius-quickpay-refund-bridge-plugin
    
  3. Register the bundle in config/bundles.php:
    Setono\SyliusQuickpayRefundBridgePlugin\SetonoSyliusQuickpayRefundBridgePlugin::class => ['all' => true],
    
  4. Check the compiler passes / services actually load — this plugin's whole surface area is a service that hooks the refund flow into a QuickPay API call. If the container doesn't wire it, nothing will visibly break until you attempt a refund.
  5. First use case: in the Sylius admin, open an order paid via QuickPay, go to Refunds, refund a single unit or the whole order. Expected outcome: Sylius records the refund, and a refund appears against the matching QuickPay payment (look up the transaction by order ID in the QuickPay dashboard). Verify in QuickPay's test account before ever touching production.

Where to look first when reading the code: find the service that implements the Refund plugin's refunder/processor interface (or the event listener on UnitsRefunded / RefundPaymentGenerated) — that class is the entire bridge and tells you exactly which QuickPay client method is called and with what amount.

Implementation Patterns

The bridge is event/state-machine driven, not UI driven. The canonical flow is: admin submits a refund → sylius/refund-plugin persists RefundPayment entities and fires an event → this bundle catches it → resolves the QuickPay payment → calls refund on the QuickPay client. There is no separate "Refund via QuickPay" button; you reuse Sylius' existing refund UI.

Order-level vs unit-level refunds. Sylius' refund plugin supports refunding individual order units and shipping, and it aggregates them into a RefundPayment. Test both:

  • a single unit refund (partial),
  • a full order refund,
  • a shipping-only refund,
  • two partial refunds in sequence (does the second correctly decrease the QuickPay refundable balance?).

QuickPay tracks refunded amount per payment, so sequential partials are where mismatches usually show up.

Match payment to order explicitly. QuickPay's dashboard keys on your order ID / transaction ID. Make sure your Sylius order number or a stable payment reference is what you send to QuickPay, not Sylius' internal payment ID — otherwise reconciliation in QuickPay is painful. Verify what the bundle sends by logging the outgoing request once.

Idempotency via a refund key. Any real integration must send a unique idempotency key per Sylius refund (e.g. the RefundPayment id or its UUID). Sylius admin double-clicks and state-machine re-transitions are common. If the bundle doesn't populate one, wrap the call yourself or guard with a "refund already requested" flag on the payment.

Integrate with your own post-refund hooks, don't fork the bundle. Listen to Sylius' refund events (UnitsRefunded, RefundPaymentGenerated, refund state transitions) in your app to do things the bridge intentionally doesn't: send a customer email, write an audit log row, tag the order. The bridge should stay a thin transport layer.

Wrap in a decorator for observability. Decorate the bridge's refunder/processor service and log (request payload, QuickPay response, HTTP status) around it. You will need this the first time a refund "worked in Sylius but not in QuickPay."

Testing pattern. Use QuickPay's test merchant (their test cards auto-approve) plus a Sylius functional test that:

  1. seeds an order paid by QuickPay,
  2. invokes the refund plugin's refunder,
  3. asserts (via a mocked/faked QuickPay HTTP client) that a refund request was issued with the correct amount and payment id.

Mock the QuickPay client at the HTTP boundary — don't hit the live test API in CI.

Gotchas and Tips

Dormancy / maintenance risk. Last release April 2022, 2 stars, effectively no dependents. Assume it is not tracking newer Sylius or QuickPay client versions. Pin symfony/*, sylius/*, and the refund plugin versions, and read the composer constraints before upgrading Sylius. If your Sylius is v1.12+, budget time to verify the bundle still compiles and the service wiring is intact.

It is only as good as the base QuickPay plugin. The bridge needs the QuickPay payment's transaction id (payment->getDetails() or a stored reference) to refund against. If your QuickPay plugin version stores state differently, the bridge may resolve null and fail silently or throw. Confirm the field it reads exists on your live payment objects before relying on it.

Amounts and currency units. QuickPay's API expects amounts in the currency's minor unit (integer, e.g. 10000 = 100.00 DKK). Sylius also stores integers in minor units, but mixing them (dividing by 100 "for readability") produces off-by-100 refunds. Grep the bundle for the amount construction and confirm it matches the QuickPay client's expectation; also confirm currency is forwarded (QuickPay refunds may need the payment currency).

Silent no-op is the common failure. If the bridge can't find a matching QuickPay payment or transaction, it may skip the API call rather than erroring. Always reconcile: after a Sylius refund, check the QuickPay dashboard. Add a log line or a RefundPayment note that records the QuickPay refund id returned — that is your proof the call happened.

State machine ordering. Sylius' refund plugin can move refund payments to "completed" independently of whether the gateway call succeeded. Don't assume a "completed" Sylius refund means QuickPay was refunded. Model a failure path (retry button / manual reconcile) in your admin.

Config quirks. There's typically little to no bundle config — check DependencyInjection/Configuration.php and the Resources/config/services.yaml for the exact service ids and whether any env var (API key, merchant) is expected. The QuickPay API credentials belong to the base payment method config (GatewayConfig), not this bundle; don't duplicate them.

Extension points are limited by design. The sane extension points are (a) decorating the refunder/processor service, (b) listening to Sylius refund events, (c) wrapping the QuickPay client. Avoid patching vendor code; if the bridge's idempotency or amount handling is wrong for your accounting, copy the tiny class into your app and wire it yourself rather than maintaining a fork.

Debugging checklist when a refund doesn't reach QuickPay:

  1. Is the bridge bundle actually registered and its service in debug:container?
  2. Which refund event fired — UnitsRefunded vs RefundPaymentGenerated? Is the listener subscribed to the one your flow emits?
  3. Did it resolve a QuickPay payment id? Log that value.
  4. HTTP status from QuickPay — 402/409 usually means the payment isn't captured or is already refunded; 401 means API key scope, not the bridge.
  5. Does the QuickPay payment's refundable balance cover the amount after previous partials?
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource