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

Pos Laravel Package

mews/pos

A PHP SDK for the Mews POS API that helps Laravel and PHP applications connect to Mews point-of-sale services. Build hospitality and retail integrations with a clean client for orders, bills, and POS data.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require mews/pos

Check composer.json for required PHP extensions before you start — most gateways need ext-curl, ext-dom, ext-mbstring, ext-json, plus ext-soap for the SOAP-based acquirers. Also grep the composer file for the php constraint; v2 targets modern PHP, v1 supports 7.x.

Where to look first (in this order)

  1. docs/ — per-bank markdown files (garanti.md, akbank.md, isbank.md, …). Each documents the bank's quirks: required credential fields, 3D Secure flow type, supported installments, mandatory extra fields. Read the one for your bank before touching src/.
  2. examples/ — runnable end-to-end scripts for each gateway. These are the fastest way to see the exact object graph your version expects (factory names have changed between major versions).
  3. config/pos.php — the account/transaction configuration shape. Publish/copy it as your starting point rather than inventing keys.
  4. src/Gateways/ — one class per bank; that file is the source of truth when docs disagree with behavior.

First use case: a test-mode non-secure payment

Create a gateway from the factory, build a credit-card value object, build a payment array, call pay():

use Mews\Pos\Factory\GatewayFactory;
use Mews\Pos\Factory\AccountFactory;
use Mews\Pos\Factory\CreditCardFactory;

$account = AccountFactory::create('garanti', [
    'merchant_id' => env('POS_MERCHANT_ID'),
    'username'    => env('POS_USERNAME'),
    'password'    => env('POS_PASSWORD'),
    'secret_key'  => env('POS_SECRET_KEY'),
    'test_mode'   => true,
]);

$gateway = GatewayFactory::create('garanti', $account);

$card = CreditCardFactory::create('4824894728063019', '23', '07', '428', 'Test User');

$response = $gateway->pay([
    'id'          => 'order-1001',
    'name'        => 'Test User',
    'amount'      => 10.00,
    'currency'    => 'TRY',
    'installment' => 0,
    'ip'          => request()->ip(),
    'card'        => $card,
]);

if ($response->isSuccess()) { /* ... */ }

Get a real test merchant ID from your bank (or from the examples/ file — some banks publish shared sandbox credentials). Run it, log the raw response, then do the same call with test_mode => false and a real 1-TRY transaction before building any UI on top of it.

Implementation Patterns

Wrap the package in your own façade, don't sprinkle it through controllers. Bank gateways differ in status codes, error text and field names. Put a single PaymentGateway interface in your app (charge(), authorize(), capture(), refund(), cancel(), status()) and one MewsPosGateway adapter that satisfies it. Swapping acquirers, or adding a second one for failover, then costs one class instead of a grep across the codebase. Inject that interface, never PosInterface/gateway classes directly.

Separate configuration from code. Keep credentials per bank in a config/pos.php config file (or a payment_gateways DB table), and resolve which bank to use at runtime. This is also what lets you offer "choose your bank" in a checkout page — the gateway list, installments and BIN restrictions are all config-driven.

The 3D Secure flow is two requests, so model it as two. Pattern:

  1. /checkout/pay → call the gateway's 3D method (e.g. get3DFormData() / make3DPayment() depending on version). You get back an HTML form and a gateway_url. Render the form and auto-submit it to the bank.
  2. The bank POSTs back to your public, CSRF-exempt callback route. Call the corresponding payment3DResponse($request) / ...3DResponse() method, which validates the bank's hash and returns the final response.

Persist the order in a pending state at step 1 and only mark it paid in step 2. Never trust a query-string status=success.

Distinguish 3D models. A few banks offer 3D_PAY/3D_HOST (store-side 3D, no redirect for some flows) alongside classic redirect 3D. Choose deliberately: redirect models make you deal with callbacks and timeouts; hosted-3D models shift liability but have stricter PCI/registration requirements. The docs/<bank>.md file states which are supported.

Pre-auth then capture for anything where you ship after payment (hotels, custom orders). Use the pre-auth transaction type, store the returned bank transaction ID, and post-auth/capture within the bank's window (often 7 days). Your DB needs: order_id, bank_order_id/transaction_id, auth_code, amount, currency, installment, status, raw_response.

Refund vs cancel is not symmetric. Cancel voids before settlement (same day, usually full amount only); refund works after settlement and can be partial. Most banks allow multiple partial refunds up to the total — track refunded amounts yourself and reconcile with status().

Reconcile on a schedule. Add a daily job that pulls status() / statement data for anything still pending or authorized. Banks drop callbacks, browsers close mid-3D, and timeouts produce ambiguous state. This job is what makes discrepancies visible instead of silently losing money.

Laravel wiring sketch:

// app/Providers/PaymentServiceProvider.php
$this->app->singleton(PaymentGateway::class, fn ($app) => new MewsPosGateway(
    config('pos.gateway'),
    config('pos.accounts'),
    $app['log'],
));

Publish/copy the package config, put credentials in .env, and add the callback route to VerifyCsrfToken::$except.

Testing: two layers. Use the banks' sandbox environments and their published test cards for integration tests (mark these as @group external, they're slow and flaky). For unit tests, mock your own PaymentGateway interface — the package's response objects are simple enough but constructing them by hand is brittle across versions.

Gotchas and Tips

  • Amount format is per bank and it's the #1 source of "declined" bugs. Some gateways want "10.00", some want minor units ("1000"), some want a comma decimal separator. Never pass floats; format to a string based on the bank's docs, and assert it in a test.
  • Response objects don't throw. pay() returns a response; check isSuccess() (or getStatus() === 'approved') and read getErrorCode() + getErrorMessage(). Wrap every call in a try/catch anyway — network/XML-parse exceptions do bubble up from the HTTP layer.
  • Verify the hash on 3D callbacks. The bank signs its callback response; if you skip validation, a user can POST a forged success to your callback URL. Use the package's response-handling method rather than parsing $_POST yourself.
  • The callback route must be POST, public, and CSRF-exempt. In Laravel that means both VerifyCsrfToken::$except and no auth middleware. Also make sure your route isn't behind a proxy that rewrites the body.
  • Log raw requests and responses. The package emits events and/or accepts a PSR-3 logger depending on version — wire a channel that captures the full XML body, the order ID and the gateway name. Without this, debugging a bank-side decline is impossible. Mask PAN and CVV before writing to disk.
  • Timeouts are real. Bank endpoints regularly take 10–30s under load. Set generous curl/HTTP client timeouts (the package may not expose them — check the gateway class) and make your own endpoint idempotent by order_id, because a timeout doesn't mean the charge failed.
  • Test environments lie. Several Turkish sandboxes approve every transaction or ignore the hash. Do at least one real production transaction (small amount, then refund it) before launch.
  • Credentials are not uniform. Garanti uses merchant_id/terminal_id/prov_user/prov_password/store_key; İş Bankası and others use different names, and some need a client certificate file path. Copy the field names exactly from the bank's doc — unknown keys are often silently ignored, producing a cryptic "invalid merchant" error.
  • Version churn is high. The v1 and v2 APIs (PosFactory vs GatewayFactory, response method names, config array shape) are substantially different, and the README sometimes lags the code. Pin the version in composer.json ("mews/pos": "^2.0"), read CHANGELOG, and treat a major bump as a migration task.
  • Docs and code comments are largely Turkish. Bank error messages come back in Turkish too. Budget time for this if your team doesn't read Turkish, and store the raw error alongside your own user-facing message.
  • Check the license file. Metadata reports NOASSERTION; confirm the actual LICENSE contents before shipping commercially. In practice it's permissive, but verify rather than assume.
  • Extension points: for an unsupported bank, subclass the package's abstract gateway, implement the request/response mapping, and register it in your config + factory — that's cheaper than forking. For per-request behavior (custom logging, retry, metric emission), prefer the package's event dispatcher over subclassing the gateway.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource