Skip to content

Add supportsRefund/doRefund for gateways whose Omnipay driver implements refund - #304

Open
magnolia61 wants to merge 1 commit into
eileenmcnaughton:masterfrom
magnolia61:refund-support
Open

Add supportsRefund/doRefund for gateways whose Omnipay driver implements refund#304
magnolia61 wants to merge 1 commit into
eileenmcnaughton:masterfrom
magnolia61:refund-support

Conversation

@magnolia61

@magnolia61 magnolia61 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Overview

This adds refund support to the processor, wired to CiviCRM's standard refund contract (supportsRefund() capability + doRefund() as called by the PaymentProcessor.refund APIv4 action, and used by e.g. mjwshared's refund UI).

Without this, extensions like mjwshared fall back to their record-only branch for any Omnipay processor: the refund is booked in CiviCRM but no money moves at the gateway.

Approach

  • supportsRefund() resolves the Omnipay gateway class via \Omnipay\Common\Helper::getGatewayClassName() without instantiating it and returns TRUE only when that class exposes a refund() method — so this stays generic across the Omnipay universe and is safe to call in listing contexts.
  • doRefund() initializes the gateway (same sequence initialize() uses, minus the form/session steps that need component/qfKey), then sends refund(['transactionReference' => trxn_id, 'amount', 'currency']). Currency defaults to the original financial_trxn's currency.
  • On success it returns refund_trxn_id (the gateway's refund id) and refund_status 'Completed' per the core contract. Any failure throws PaymentProcessorException rather than returning a failure array, so CiviCRM never records a refund the gateway rejected.

Testing

Deployed and verified against Mollie (iDeal/Bancontact) on CiviCRM 6.x: supportsRefund() correctly reports TRUE for Mollie processors (whose Omnipay driver implements refund()) and would report FALSE for drivers without one. New code passes civilint with zero findings.

Related issue:
#242

@civibot civibot Bot added the master label Jul 4, 2026
@magnolia61 magnolia61 changed the title Add supportsRefund/doRefund for gateways whose Omnipay driver implements refund() Add supportsRefund/doRefund for gateways whose Omnipay driver implements refund Jul 4, 2026
…nts refund()

Implements CiviCRM's refund contract (PaymentProcessor.refund API /
supportsRefund capability) generically: supportsRefund() resolves the
Omnipay gateway class without instantiating it and reports TRUE only when
that class exposes a refund() method. doRefund() sends the refund via the
gateway (transactionReference = original trxn_id) and returns
refund_trxn_id / refund_status per the core contract. Failures throw
PaymentProcessorException so CiviCRM never records a refund the gateway
rejected.

Details:
- A description is always sent: Mollie's create-refund API requires one
  and it may be shown to the customer (e.g. on their bank statement).
  Callers can pass their own via $params['description'].
- The existing alterPaymentProcessorParams hook fires before sending,
  with an 'action' => 'Refund' marker (removed after the hook), so
  extensions can customise refund parameters the same way they already
  can for purchases.
- The fallback currency is resolved from the original transaction via
  APIv4 FinancialTrxn::get, defaulting to the processor currency.
- refund_trxn_id prefers getTransactionId() (Mollie exposes the new
  re_... id there) and falls back to getTransactionReference(), which is
  where most other Omnipay drivers expose the refund reference.

Verified live against Mollie (iDeal) on CiviCRM 6.x: refund accepted and
the re_... id recorded as refund_trxn_id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant