Most Drupal Commerce sites that take card payments do so through a payment gateway module that talks to a third-party processor. The choice of gateway matters more than it looks. It decides where the card data flows, how much of the PCI DSS burden lands on you, and how much trust you are placing in code that sits directly in the payment path. Cybersource (Visa’s payment platform) is a common processor for businesses that have outgrown the simplest options, but the contributed Drupal support for it has been thin and not always to the standard a production payment path deserves. So I built one and published it.
Cybersource SOP for Drupal Commerce
- Project
- drupal.org/project/cybersource_sop
- Install
composer require drupal/cybersource_sop- Method
- Cybersource Secure Acceptance, Silent Order POST (SOP)
- Requires
- Drupal Commerce 3, Drupal 10.3+ or 11
- Licence
- GPL-2.0-or-later
- Source
- git.drupalcode.org/project/cybersource_sop
What Silent Order POST actually means
Cybersource Secure Acceptance comes in a few flavours, and the difference between them is precisely the difference in where the card number goes and therefore how exposed you are. Silent Order POST is the variant where the card fields are rendered on your own checkout page, styled like the rest of your site, but the form does not submit back to your server. It POSTs the card details directly to Cybersource over HTTPS. Cybersource processes the payment and sends a signed reply back to a return URL on your site, and the module verifies that signature before it believes a word of it.
The practical upshot is that the sensitive primary account number never lands on your infrastructure, is never written to your logs, and is never held in your database, even for a moment. You get a checkout that looks like part of your site rather than a redirect to someone else’s branded page, without your servers ever touching the card number. That is the appeal of SOP, and it is the whole reason to prefer it over a plain on-site form.
Where it sits on the PCI scale. Because the card fields are served from your page, an SOP integration falls under SAQ A-EP rather than the lighter SAQ A you would get from a fully hosted iframe. That is an honest trade: you accept a slightly larger compliance questionnaire in exchange for a checkout that is fully part of your own site and under your own styling. If your priority is the absolute minimum PCI scope, an iframe-based method is lighter; if you want the card data off your server and a native-looking checkout, SOP is the sweet spot, and this module is built for it.
How the module works
It registers as an off-site payment gateway in Drupal Commerce. At checkout, it builds the set of fields Cybersource expects, signs them with an HMAC derived from your secret key, and renders the form so the browser posts it to Cybersource. When Cybersource finishes, it calls back to a return route on your site with a signed reply. The module recomputes the signature over the returned fields and rejects anything that does not match, which is what lets that route be open to the world while still being trustworthy: the signature, not a session, is the authentication.
Only once the signature checks out does it interpret the result and record the payment against the order through Commerce’s normal payment API. It supports both authorisation only and sale (authorise and capture in one step). Managing a payment after the fact, capturing an authorisation, voids, refunds, is deliberately left to staff in the Cybersource Business Center rather than being driven from Drupal, which keeps the module’s surface small and keeps the most sensitive operations behind Cybersource’s own access controls.
Credentials live in a file, on purpose
One design decision is worth calling out because it is a security choice rather than a convenience one. The Cybersource credentials are never stored in Drupal configuration, so they are never written to the database and never exported into your config or your git repository. They are read at runtime from a fixed file in Drupal’s private filesystem, outside the web root, resolved per environment and currency: one test profile for everything in test mode, and one live profile per currency in live mode.
The path is hardcoded and intentionally not configurable. A configurable credential path sounds harmless until you notice that anyone with the “administer payment gateways” permission could then point the gateway at credentials they control. Removing the setting removes that entire class of attack. It is a small example of a recurring theme in this module: prefer the design that has fewer ways to go wrong, even when it is slightly less flexible.
The bar I held it to before publishing
Anyone can push code to drupal.org. The question is what state it is in when you do. Code that sits in the payment path of a live shop should be held higher than ordinary contrib, and I treated it that way.
- Static analysis at level 6. The module passes PHPStan at level 6. For context, drupal.org’s default continuous integration runs contrib at level 1. Level 6 catches a great deal that level 1 waves through, and the module ships its own configuration so the same strict bar runs locally and in drupal.org’s CI.
- Coding standards clean. It passes Drupal’s PHP_CodeSniffer rules (the Drupal and DrupalPractice standards), with JavaScript linted separately by its own tooling rather than misfiled through the PHP standard.
- Tested where it counts. It carries a PHPUnit suite of unit and kernel tests, with the kernel tests aimed squarely at the return and decision-handling paths, the exact places where a payment gateway is dangerous to get wrong.
The security review, and what it changed
Before release the gateway went through a focused security review, and that review found real problems. I think it is more useful to be specific about them than to wave at “it has been reviewed,” so here are the three that mattered, and what each fix does.
1. A signing quirk that could let two different values share one signature
The code that builds the HMAC signature was running an old input-cleaning step (a magic-quotes-era stripslashes) over the data before signing it. The effect was that two genuinely different field values could be reduced to the same string and therefore produce the same signature, while the replay protection keyed on the raw, un-reduced value. That mismatch is exactly the kind of seam an attacker looks for. Cybersource signs the literal bytes it is sent, so now the module does too: no cleaning step, sign what is actually transmitted, and the signature and the replay guard agree on what they are protecting.
2. Trusting the verdict, not just the signature
A signed reply from Cybersource is authentic, but authentic is not the same as “place the order.” Cybersource’s Decision Manager can return a result that is correctly signed yet carries a review or reject code, meaning the transaction should be held, not fulfilled. The original flow branched on the raw decision string; it now interprets the reply into an explicit outcome (accept, or hold for review) and the controller acts on that interpreted outcome. A signed acceptance that actually carries a review code is now held for a human rather than quietly placed.
3. Closing a replay window under load
The check that stops the same reply being processed twice was a read-then-write, and Commerce’s remote-transaction column is not uniquely constrained, so two near-simultaneous callbacks could in principle both pass the check before either had recorded anything. The check and the record are now wrapped in a Drupal lock keyed to the order and transaction, and it fails closed: if the lock cannot be taken, the request is refused rather than risked.
None of these were dramatic, and that is rather the point. A payment gateway earns trust by being boring in exactly the places that are easy to be subtly wrong: signing, verifying, and not doing the same thing twice. Finding and fixing this class of issue before a release, with a regression test added for each, is what the pre-publication review is for.
Why I built it
The honest answer is that I needed a Cybersource gateway I would be comfortable putting in front of real money, and the existing contributed option did not clear that bar for the kind of work I do. Rather than carry a private patch pile, I wrote a clean implementation from scratch, held it to a stricter standard than contrib usually demands, had it reviewed, and then put it where anyone else with the same problem can use it. Publishing it is partly that: contributing back something I needed anyway. It is also a statement of capability. If you are choosing who should build or look after a Drupal Commerce site that takes card payments, “here is the payment module I wrote, reviewed, and published, go and read it” is a more useful answer than a paragraph of claims.
The project is live on drupal.org now and installable with composer require drupal/cybersource_sop. Formal security-advisory coverage is a separate drupal.org process with its own waiting period, which is under way; until that completes the project carries the standard “not covered by the security advisory policy” note that every new project starts with, and I will update the project page when it changes.
If you run a Drupal shop on Cybersource
If you are on Drupal Commerce 3 and taking, or planning to take, card payments through Cybersource, the module is there to be used, and I would genuinely welcome issues and merge requests on the project page. If you would rather have someone implement it for you, get the Secure Acceptance profile configured correctly (a few of those settings are easy to get subtly wrong), and stand behind the result on a site that handles real payments, that is the work I do. The contact details are below.
