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

Webpay Bundle Laravel Package

quartet/webpay-bundle

Quartet WebPay Bundle integrates WebPay payment services into Symfony/PHP applications, with configurable service wiring and a streamlined setup for handling payment requests and responses. It reduces boilerplate so you can add WebPay support quickly while keeping payment configuration centralized a...

View on GitHub
Deep Wiki
Context7

Getting Started

First, correct the mental model. This is not a Laravel package and there is no composer require path into a modern app: it is a Symfony 2 bundle (getContainerExtension(), Resources/config/services.yml, AppKernel registration) wrapping the webpay/webpay PHP client for WebPay — the Japanese card processor now part of Stripe. "Leveraging it day-to-day" therefore means reading it as a specification and porting the thin API layer, not installing it.

Minimal steps, in order:

  1. Gate before code. Confirm (a) the repo has a LICENSE you can legally port from, and (b) whether WebPay merchant onboarding/sandbox is still open to you at all — WebPay's standalone service was wound into Stripe, and original endpoints/sandbox may be gone. If either fails, stop; the rest of this is reference reading only.
  2. Read in this order, roughly 30 minutes total:
    • DependencyInjection/Configuration.php — the config tree: API key, public key, and any api_version/endpoint overrides. This is the entire public surface you need to replicate in config/services.php.
    • Resources/config/services.yml — shows what the bundle actually constructs; usually one service for the \WebPay\WebPay client with the key injected. That tells you how much code there really is (very little).
    • DependencyInjection/*Extension.php — how config maps onto the client (e.g. \WebPay\WebPay::setApiKey() or a client constructor argument).
    • Any controller/form/event subscriber — where the charge call and response handling live.
  3. Read composer.json of the bundle and note the pinned webpay/webpay and Guzzle constraints. That constraint is the single most important fact for your port: if it pins Guzzle 3/5, do not let it near the app's dependency graph — copy the source into app/Support/WebPay/ (vendored) or, better, write the client yourself against the documented HTTP shape.
  4. First use case to internalize: the token-charge happy path — client-side card form → WebPay.js produces a one-time token → server calls charge-create with that token → charge object returns id, paid, amount, currency, captured. Everything else (auth-only capture, refund, customer/saved-card, webhooks) is a variation on that shape.

Your deliverable after that read is a Laravel WebPayGateway behind a PaymentGateway interface — not a Symfony bundle in your tree.

Implementation Patterns

Config → config/services.php + .env. Replicate only the keys you saw in Configuration.php, and never let the secret key reach a committed file:

// config/services.php
'webpay' => [
    'secret_key' => env('WEBPAY_SECRET_KEY'),
    'public_key' => env('WEBPAY_PUBLIC_KEY'),
    'api_version' => env('WEBPAY_API_VERSION', '2019-05-23'), // pin explicitly
],

Resolve via a singleton binding rather than env() calls inside services, so the value is config-cached safely.

Bind a contract, not the vendor class. The whole point of porting rather than adopting is that your application layer should not know "WebPay" exists:

interface PaymentGateway {
    public function charge(int $amountJpy, string $token, array $opts = []): ChargeResult;
    public function capture(string $chargeId, ?int $amountJpy = null): ChargeResult;
    public function refund(string $chargeId, ?int $amountJpy = null): ChargeResult;
    public function find(string $chargeId): ChargeResult;
}

ChargeResult is a framework-agnostic DTO (id, paid, captured, amount, currency, failureCode, raw). The WebPay adapter maps WebPay's response into it; Stripe/PAY.JP/GMO adapters map theirs. This is what makes the bundle's shape worth studying — its API surface is a decent template for that interface (charge, capture, refund, retrieve).

Extract, then wrap as a service provider. Keep the ported HTTP client in app/Services/WebPay/ as a plain class (constructor takes base URL + key), and register it:

$this->app->singleton(WebPayClient::class, fn ($app) => new WebPayClient(
    $app['config']->get('services.webpay'),
    // inject an HTTP client you control for fakes/timeouts
));
$this->app->bind(PaymentGateway::class, WebPayGateway::class);

The injection point for the HTTP client is your testability lever — the Symfony bundle's use of DI is the one pattern worth copying exactly.

Idempotency at the adapter boundary. WebPay-era APIs generally did not give you an idempotency key header, so synthesize one: a payments table row with your own idempotency_key unique index, written before the outbound call. On retry, look up the row first; if a charge ID exists, return it rather than re-charging. This is the single most important addition to anything you lift from the bundle — the original almost certainly does not have it.

Order state machine driven by webhooks, not by the redirect. Model pending → authorized → captured → refunded/failed and let webhook handlers (charge.succeeded, charge.failed, charge.refunded) advance state. Use the bundle to see whether it verified webhook signatures (\WebPay\WebPay::webhooks()-style verification against the signature header) — if it did not, you must implement verification yourself, and in the meantime treat the return URL purely as a UI signal.

Contract tests with recorded fixtures. Run the extract against the sandbox once, capture raw JSON responses (success, card declined, 3DS-required, refund, partial refund), and commit them as fixtures. Your adapter tests replay fixtures through a faked HTTP client — meaning your suite never needs sandbox credentials in CI. The bundle is useful here as a source of which responses to expect.

Local dev loop. Fake the gateway binding in local (PaymentGateway bound to a FakeGateway that returns canned charge IDs) so you never need network calls to iterate on order flows; swap in the real adapter only for the sandbox spike and integration tests.

Gotchas and Tips

  • Do not add it to composer.json. Symfony 2 is EOL (Nov 2018) and the bundle's dependency pins (Guzzle 3/5, old webpay/webpay) will fight your modern Laravel HTTP stack. Dependency-resolution failure hours into a sprint is the predictable outcome. Vendor or rewrite; never require.
  • Bundle ≠ library. A Symfony bundle has no value in Laravel. If you copy directories wholesale you inherit dead DI, YAML, and event wiring that your container will never call. Copy only the ~100–300 lines that touch HTTP.
  • License check is a real blocker. Abandoned bundles frequently ship without a LICENSE file. No license = no legal right to copy the code, which matters far more in a payments path. If unclear, write the client from the public API docs instead of porting source.
  • PHP 8 breakage in ported code. Expect 5.x-era idioms: dynamic properties (deprecated in 8.2), each()/create_function-adjacent patterns, loose == on response codes, and old TLS/cipher defaults in the HTTP layer. Grep for ->{$var} assignments on classes without #[AllowDynamicProperties]/declared props, and replace any bundled Guzzle options (verify, curl) with your own client config.
  • JPY is zero-decimal. WebPay amounts are integer yen. If you "convert to minor units" out of habit you will charge 100×. Put an assertion in the adapter (assert(in_array($currency, ['JPY']) ? $amount === (int) $amount : true)) and a test that pins 1000 → ¥1,000, not ¥10.
  • Tokens are single-use. A retried charge with the same token fails with a distinct "token already used" error — which looks like a decline. This is precisely why idempotency must be keyed on your own record, not on the token.
  • capture semantics. Many charge-create calls auto-capture. If you need auth-then-capture (common for order-then-ship), confirm the flag name in the bundle's call sites and test the auth-only → capture → partial-refund path explicitly; partial-capture arithmetic is a classic bug source.
  • Response validation. If the bundle validates a signed response, port that check and log failures loudly; if it doesn't, add it. Never parse a "success" from the browser redirect alone.
  • Never log the raw request/response unfiltered. Card-adjacent payloads (tokens, masked PANs, card blocks) must be scrubbed at the adapter boundary before any Log::debug. Wire it into a finally that redacts known keys.
  • Extension point worth keeping: the bundle's Configuration.php is a clean inventory of "everything variable about this gateway." Mirror it as your config array and your future gateway swap becomes a config change plus one class.
  • Debugging strategy: when something fails, dump the raw HTTP status + body from your client before your DTO mapping. Almost every porting bug lives in the mapping layer (field renamed, nested object, error code enum), not in the transport.
  • Plan the exit before you start. Since the gateway's lifecycle is uncertain, keep the PaymentGateway interface narrow and the WebPay-specific code in one directory. The day you switch to Stripe/PAY.JP/GMO, the diff should be one adapter plus a config block — not a search-and-replace across the domain.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons