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

Masterpass Bundle Laravel Package

hoya/masterpass-bundle

Symfony bundle that brings Masterpass online checkout to PHP applications, handling the payment flow and service configuration so shoppers can pay with their Masterpass wallet. Review the README for installation and integration steps.

View on GitHub
Deep Wiki
Context7

Getting Started

Set expectations before you install anything. hoya/masterpass-bundle is a Symfony bundle, not a Laravel package: no service provider, no facade, no php artisan vendor:publish. Last release was 2018-07-02 and it targets Symfony 2.x/3.x-era conventions against a wallet program Mastercard has since retired. Day-to-day, there are exactly two ways to "leverage" it:

  1. As a reference implementation — read it to map the Masterpass handshake, then write your own thin client. This is the normal case.
  2. As a legacy artifact — you have a live Masterpass merchant relationship and a Symfony app already running it, and you're keeping it breathing.

If neither is true, stop here.

Step 0 — confirm the product still exists for you. Ask the acquirer/PSP whether Masterpass (or its successor Click to Pay / MPGS) is still contractually available in your markets. This gates every line of code below. A 2018 client pointed at retired endpoints is not a coding problem.

Step 1 — check whether it even resolves.

composer require hoya/masterpass-bundle --dry-run

Read the conflict output before committing. Expect PHP 5.6/7.x and old Symfony component constraints. On Laravel 10/11/12 + PHP 8.2 you'll need a fork with relaxed constraints, and even then only the plain-PHP protocol classes are likely usable.

Step 2 — read the bundle in this order. This is the only documentation you have:

DependencyInjection/Configuration.php   ← authoritative config keys (consumer key/secret, callback, sandbox vs prod)
DependencyInjection/*Extension.php      ← how those keys become service args
Resources/config/services.yml           ← service IDs + constructor arguments you must re-declare
src/…Client / …Gateway                   ← the real API surface: request token, access token, checkout resource
Tests/                                  ← the most reliable spec of request/response shapes (if any exist)

Step 3 — pick your integration shape.

  • Symfony host: register the bundle in the kernel, configure it, inject the service into your controller. Cheapest path if you genuinely run that stack.
  • Laravel host: do not try to emulate Symfony's container. Instantiate the surviving client class(es) directly from a service provider:
// app/Providers/MasterpassServiceProvider.php
public function register(): void
{
    $this->app->singleton(MasterpassGateway::class, fn () => new MasterpassClient(
        config('masterpass.consumer_key'),
        config('masterpass.consumer_secret'),
        config('masterpass.callback_url'),
        config('masterpass.endpoint'),
    ));
}

First use case — sandbox spike, not production. One "Pay with Masterpass" button on one checkout page: request token → redirect user to the wallet → receive the callback with the verifier → exchange for an access token → fetch the checkout resource → log the masked card/billing payload. Do not create orders yet. The goal is to prove the handshake end to end and record what the sandbox actually returns.

Implementation Patterns

Wrap it behind an interface immediately. The single highest-value pattern: your app should never import a Masterpass class directly.

interface WalletGateway {
    public function begin(array $order): Redirect;
    public function complete(Request $request): WalletResult;
}

MasterpassGateway implements WalletGateway; later ClickToPayGateway implements WalletGateway and nothing upstream changes. This is what turns a migration from a rewrite into a config swap.

Two-route redirect flow. The protocol is redirect-based, so model it as two endpoints plus state:

  • POST /checkout/pay → create order (yours), call requestToken, persist {order_id, request_token, created_at}, return a redirect to the wallet URL.
  • GET /checkout/callback?oauth_token=…&oauth_verifier=… → look up the stored row by oauth_token, exchange for access token, fetch the checkout resource, persist the raw payload, then hand off to your normal order-completion path.

Never trust the callback alone for order identity — always resolve the order from your stored request_token mapping, not from anything in the query string.

Mirror the bundle's config into Laravel conventions. Copy the keys out of Configuration.php verbatim so the fork stays diffable against upstream:

// config/masterpass.php
return [
    'consumer_key'    => env('MASTERPASS_CONSUMER_KEY'),
    'consumer_secret' => env('MASTERPASS_CONSUMER_SECRET'),
    'callback_url'    => env('MASTERPASS_CALLBACK_URL'),
    'endpoint'        => env('MASTERPASS_ENDPOINT'), // sandbox vs prod
    'timeout'         => env('MASTERPASS_TIMEOUT', 15),
];

Fork-and-pin, not vendor-patch. Fork into an internal namespace, then delete the bundle plumbing that will never execute in Laravel:

*Bundle.php          ← delete
DependencyInjection/ ← delete
Resources/config/*.yml ← delete
src/ (clients, DTOs, signer) ← keep, pin, test

Pin every transitive dependency to a current version and replace abandoned HTTP/OAuth subscribers with maintained equivalents. Assign a named owner in the PR description; the fork is first-party payment code the moment you create it.

Record fixtures, not just sandbox runs. Capture sandbox request/response pairs as JSON fixtures and write contract tests around them. With no upstream test suite, characterization tests are the only regression net you'll have when you next touch the signer.

Reconcile the postback leg separately. The wallet confirming payment and your server learning about it are two different events. Queue a recurring job that scans orders in awaiting_confirmation past N minutes and polls/voids them. Treat "wallet says paid, app says pending" as an expected state, not an exception.

Feature-flag the button. Roll out to one market/segment behind a flag. The kill switch matters more than the rollout when the gateway can deprecate under you without a release note.

Legacy Symfony usage (if that's your host): register in AppKernel, put the keys under the bundle's config root, inject the service by constructor argument into your checkout controller, and keep the controller thin — the bundle should own transport, your app should own orders.

Gotchas and Tips

The bundle's DI never runs on Laravel. That is the #1 time sink. Configuration.php keys are inert; services.yml is never compiled. Everything must be re-bound by you. If a config value "isn't being picked up," this is why.

OAuth 1.0a signing is where it fails, not HTTP. Expect 401 invalid signature from clock skew, a wrong signature base string, or percent-encoding differences (RFC 3986, not urlencode). Log the base string on failure, and make sure the server clock is NTP-synced. Request tokens are short-lived and single-use — a user who sits on the wallet page and comes back late will fail; handle that with a friendly restart path, not a 500.

Callback replay and duplicate completion. oauth_verifier can arrive twice (back button, refresh, retry). Make the callback idempotent: a unique constraint on the request-token mapping plus a status guard, so the second hit returns the same result instead of double-charging or double-completing.

Silent error swallowing. 2018-era PHP code often catches and continues or returns false. Instrument every client call with structured logging (endpoint, correlation ID, status, latency) before go-live. You will otherwise discover failures as missing orders, weeks later.

Composer drift is a permanent tax. Pin the lockfile, expect Dependabot alerts you must triage manually (no upstream patch path exists), and re-verify the sandbox after any dependency bump. Check TLS/cipher defaults too — old Guzzle versions negotiate settings gateways now reject.

Symfony component version skew against Laravel. Laravel vendors Symfony components; a bundle that constrains old ones can collide even when the package itself "installs." Isolate by keeping the extracted classes dependency-light (PSR HTTP client, no Symfony framework classes in the protocol layer).

PCI scope check. Determine from the code whether cardholder data ever posts to your server or whether it's a pure redirect + token handoff. It changes your compliance obligations and should be written down in the runbook, not inferred later.

Don't auto-enable for all traffic on install. Wire it behind an interface and a flag from day one; a dormant dependency with no CVE response path is a liability, not a feature, the moment it takes real money.

Extension points are yours, not the package's. There is no upstream event system to hook and no maintainer to accept a patch. The interface boundary (WalletGateway) and your fork are the extension surface — design accordingly, and write the runbook while you fork, because the retired product's docs are largely offline and nothing in-repo records your divergences.

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.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr