Skip to main content

Mounting your own payment element

This package ships no front-end JavaScript. Not as an omission — as a decision, and one worth understanding before you work around it.

The shipped path: the card never reaches your application

PaymentMethods::addMethodUrl() sends the customer to the provider's own page. They enter their card there, the provider stores it, and your application is told the result. No card data passes through your servers, your logs, your error tracker or your session storage.

That is the whole point. The moment a card number is typed into a field your application rendered, your application is in scope for PCI DSS — even if a provider script is the thing that reads it. The hosted path keeps you out of that scope by construction rather than by care.

For most applications this is the right answer, and it is the one that works with no build step, no asset pipeline, and no JavaScript at all.

The DIY seam: when you want the fields inline

Some products genuinely need the card fields on their own page — a checkout that must not lose the customer to a redirect, a flow where a provider page would look like a different company.

PaymentMethods::setupIntent() exists for exactly that. It returns a ClientIntent:

$intent = app(\Pushery\Billing\Contracts\PaymentMethods::class)->setupIntent($billable);

$intent->driver; // 'mollie', 'stripe', …
$intent->payload; // whatever that driver's client library needs
$intent->offSessionCapable; // whether what this creates can be charged later without the customer

The payload is driver-shaped on purpose. A neutral shape would have to be the intersection of every provider's client library, and that intersection is empty: Stripe hands you a client secret, Mollie hands you a customer reference and a sequence type. Flattening them would produce a payload that fits neither.

// Mollie
['customerReference' => 'cst_…', 'sequenceType' => 'first']

Mount your provider's own element from that, submit the token it produces to your own route, and hand it to the driver. The package does not do this for you, and that is deliberate: shipping an element would mean shipping a build pipeline, versioning a compiled artifact, and taking a position on your bundler.

What you take on

Three things move to your side of the line, and none of them is hard — but all three are yours:

  • PCI scope. A provider element renders the fields in an iframe, which is far better than raw inputs, but it is not the same as "never touches the application".
  • A second path to maintain. The hosted route does not go away; you now have two ways to add a card, and two ways drift.
  • Content Security Policy. The provider's script and frame origins must be allowed. The package already knows them: the scoped account CSP loads each driver's hosts through its capabilities, so this works without you listing origins by hand — as long as you keep using that CSP.

Degrading without JavaScript

Whatever you mount, keep the hosted link reachable. A customer with a blocked script, an extension that breaks the iframe, or a network that drops the provider's CDN still needs a way to pay you — and the hosted page is that way. addMethodUrl() stays available on every driver that offers one.