Skip to content

Let sites map custom payment gateways to their own Splash code - #14

Open
Pichinov-Jose wants to merge 1 commit into
SplashSync:masterfrom
Pichinov-Jose:feat/filter-payment-method-encoding
Open

Pichinov-Jose wants to merge 1 commit into
SplashSync:masterfrom
Pichinov-Jose:feat/filter-payment-method-encoding

Conversation

@Pichinov-Jose

@Pichinov-Jose Pichinov-Jose commented Sep 5, 2026

Copy link
Copy Markdown

The problem

encodePaymentMethod() matches six known gateway ids and sends everything else as DirectDebit:

switch (strtolower($method)) {
    case "bacs": case "amazon":   return "ByBankTransferInAdvance";
    case "cheque":                return "CheckInAdvance";
    case "paypal":                return "PayPal";
    case "cod": case "alma":      return "COD";
    case "cash":                  return "Cash";
    case "other":
    default:                      return "DirectDebit";
}

A bank transfer, a holiday voucher and a card therefore reach the target as the same thing. There is
no extension point, so a site using a custom gateway has nowhere to say what its gateway actually is.

It also makes getGatewaysList() misleading: it indexes available gateways by their encoded
code, so every custom gateway on a site collapses under the single DirectDebit key.

Targets are more capable than this

Dolibarr's PaymentMethods::getDoliCode() does not stop at its own dictionary — when the code is
unknown it searches the site's payment methods by code, then by label:

$typeCodes = self::KNOWN[$splashCode] ?? null;
if (is_array($typeCodes)) { return array_shift($typeCodes); }
foreach (self::getAllMethods() as $method) {
    if ($method["code"] == $splashCode) { return $method["code"]; }
}

So a site sending ANCV would get its holiday-voucher method resolved on arrival. The information is
lost before it leaves WooCommerce, not on the target.

What this costs in practice

On a production install syncing to Dolibarr, 45 orders were paid with French ANCV holiday vouchers
through a custom gateway. All 45 arrived as DirectDebit, became card payments on the gateway bank
account, and had to be re-entered by hand on the correct account — 8 319,56 € of manual work.

Thirteen of them were then destroyed on a later sync, because a payment the source cannot express is
also a payment the source does not declare — see #13 for how that happens, and
SplashSync/Dolibarr#25 for the target-side guard.

The change

Two symmetrical filters:

apply_filters('splash_encode_payment_method', $code, $gatewayId)
apply_filters('splash_decode_payment_method', $gatewayId, $code)

Symmetry is not cosmetic here. setPaymentsFields() compares the encoded method with the
incoming one, then writes back decodePaymentMethod():

if ($this->encodePaymentMethod() != $paymentData["_payment_method"]) {
    $this->setGeneric("_payment_method", $this->decodePaymentMethod($paymentData["_payment_method"]));
}

With only the encode filter, a custom code mapped on the way out would come back as other on the
way in and overwrite the order's real gateway — the fix would cause the damage it prevents.

The detection logic moves unchanged into detectPaymentMethod() and detectGateway(). Default
behaviour is byte-for-byte identical when no filter is registered.
The @SuppressWarnings on the
switch follows it to its new home.

A site then needs a few lines:

add_filter('splash_encode_payment_method', function ($code, $gateway) {
    return str_starts_with($gateway, 'alg_wc_cpg_ancv') ? 'ANCV' : $code;
}, 10, 2);

add_filter('splash_decode_payment_method', function ($gateway, $code) {
    return ('ANCV' === $code) ? 'alg_wc_cpg_ancv_1' : $gateway;
}, 10, 2);

This mirrors splash_prepend_order_statuses, which the connector already exposes for order statuses,
so the pattern should be familiar.

Alternative considered

Passing the raw gateway id through instead of falling back to DirectDebit would fix this without
any configuration, and Dolibarr would resolve it. It is not proposed here because other targets may
not tolerate an unknown code, and the change would alter behaviour for every existing installation. A
filter is opt-in and cannot regress anyone.

encodePaymentMethod() matches six known gateway ids and sends everything
else as "DirectDebit". A bank transfer, a holiday voucher and a card
therefore reach the target as the same thing, and getGatewaysList() collapses
every custom gateway under a single key.

Targets are more capable than that. Dolibarr's PaymentMethods::getDoliCode(),
for one, falls back to searching its own payment methods by code and by label
when a code is not in its dictionary — so a site that sends "ANCV" gets its
holiday-voucher method resolved. The information is lost before it leaves
WooCommerce, not on arrival.

Add two symmetrical filters:

  - splash_encode_payment_method($code, $gatewayId)
  - splash_decode_payment_method($gatewayId, $code)

Symmetry matters: setPaymentsFields() compares the encoded method with the
incoming one and writes back decodePaymentMethod(). Without the second filter,
a custom code mapped on the way out would come back as "other" and overwrite
the order's real gateway.

The detection logic moves unchanged into detectPaymentMethod() and
detectGateway(). Default behaviour is identical when no filter is registered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Pichinov-Jose
Pichinov-Jose marked this pull request as ready for review September 5, 2026 00:21
@Pichinov-Jose
Pichinov-Jose marked this pull request as draft September 5, 2026 00:50
@Pichinov-Jose
Pichinov-Jose marked this pull request as ready for review September 5, 2026 00:52
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