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

Sci Laravel Package

egopay/sci

egopay/sci is a Laravel package for EgoPay SCI payments, offering helpers to generate payment forms, process gateway callbacks, and validate transactions so you can add EgoPay checkout to your app quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this before you composer require anything. egopay/sci is a thin, effectively unmaintained wrapper around EgoPay's SCI (Shopping Cart Interface) form-post flow. Zero stars, zero dependents, and EgoPay itself stopped operating around 2015–2016. Treat the repo as a protocol reference for form field names, hash construction, and callback shape — not as a dependency you build on. Budget an hour to read the source before wiring it into anything customer-facing.

The description claims a Laravel service provider, a publishable config, and a facade. Verify that claim in composer.json before you write integration code — with a package this old it is often aspirational. If there is no provider, you are writing the ~40 lines of glue yourself.

Steps, in order:

  1. Vendor it, don't require it. Clone the repo and drop the source into app/Support/EgoPay/ (or keep it in packages/egopay/sci with a path repository and a pinned commit). You want the code frozen in your git history, not resolved at install time from a remote that may vanish.
  2. Read composer.json first. Look at require.php, autoload, and the PSR level. Expect PHP 5.x-era constraints and possibly no PSR-4 — that dictates how much surgery is needed for PHP 8.
  3. Read src/ next. Expect a small surface: a form/request builder, a currency constant list, a hash/signature helper, and maybe a callback parser. There is likely no HTTP client — SCI is a browser redirect, not a server-to-server call, so the "SDK" is mostly string assembly and hashing.
  4. Grep for credential names. grep -ri "sci_id\|sci_name\|payee\|hash\|password" src/ tells you exactly what belongs in .env. Note the exact field ordering used in the hash concatenation — that is the single most important detail in the whole library.
  5. First use case: the redirect. Build the signed form for an existing order, POST the customer to the EgoPay gateway URL, and get the round trip working in the vendor's sandbox (or whatever is left of it) before you write a single line of callback handling. The redirect is 80% of the value and 20% of the risk.

Put the credentials in config/services.php, not a package config file — it is the Laravel-idiomatic location and keeps secrets in the standard env pipeline:

// config/services.php
'egopay' => [
    'sci_id'       => env('EGOPAY_SCI_ID'),
    'sci_name'     => env('EGOPAY_SCI_NAME'),
    'payee'        => env('EGOPAY_PAYEE_ACCOUNT'),
    'secret'       => env('EGOPAY_SECRET'),   // used to build/verify the hash
    'gateway_url'  => env('EGOPAY_GATEWAY_URL'),
    'callback_url' => env('EGOPAY_CALLBACK_URL'),
    'sandbox'      => env('EGOPAY_SANDBOX', true),
],

Then bind once in a PaymentServiceProvider so no controller, model, or job ever touches the vendor namespace directly:

$this->app->bind(PaymentGateway::class, EgoPayGateway::class);

That binding is your entire migration story. Everything below exists to keep it a one-liner.

Implementation Patterns

Wrap the vendor behind your own contract. This is non-negotiable for a dead processor. Two classes and one interface:

interface PaymentGateway
{
    public function fields(Order $order): array;          // signed SCI form inputs
    public function verifyCallback(array $payload): bool; // hash check
    public function parseCallback(array $payload): PaymentResult;
}

EgoPayGateway implements it, NullGateway or FakeGateway implement it for tests, and a future PSP implements it when you migrate. Application code sees DTOs, never raw SCI field arrays.

Redirect with POST, never a GET query string. Amounts and credentials belong in a signed form body. Persist the order before redirecting and carry your own identifier through as the merchant reference — without it, reconciliation is guesswork:

Route::post('/checkout/{order}', CheckoutController::class);

public function __invoke(Order $order, PaymentGateway $gateway)
{
    abort_unless($order->user_id === auth()->id(), 403);
    abort_unless($order->status === 'pending', 409);

    $order->update([
        'payment_ref'    => (string) Str::uuid(),
        'payment_driver' => 'egopay',
        'status'         => 'awaiting_payment',
    ]);

    return view('payments.redirect', [
        'action' => config('services.egopay.gateway_url'),
        'fields' => $gateway->fields($order),
    ]);
}

The Blade view is a plain <form method="POST"> with hidden inputs and a window.onload submit. No JS framework required; keep it dumb so it works in every browser and in tests.

Callback route: the checklist matters more than the code.

  • Register the route in routes/web.php but strip CSRF: ->withoutMiddleware(VerifyCsrfToken::class).
  • Reject anything that is not POST.
  • Verify the hash before you touch the database. If verifyCallback() throws or returns false, return 403 and log the raw payload — do not "helpfully" process it.
  • Look up the order by your order_id, never by email or amount.
  • Be idempotent. Gateways retry. Wrap the state transition in a transaction with a lockForUpdate(), and add a unique index on (order_id, batch_number) (or a processed_at column). Duplicate callbacks are the most common real-world bug in this integration shape.
  • Return the acknowledgement the spec asks for (usually a bare 200 or a literal string). If you return a redirect or an HTML page, the provider may retry forever.
Route::post('/payments/egopay/callback', EgoPayCallbackController::class)
    ->withoutMiddleware(VerifyCsrfToken::class)
    ->name('egopay.callback');
public function __invoke(Request $request, PaymentGateway $gateway)
{
    $payload = $request->all();

    if (! $gateway->verifyCallback($payload)) {
        Log::warning('egopay.callback.bad_hash', ['payload' => $payload]);
        abort(403);
    }

    $result = $gateway->parseCallback($payload);

    DB::transaction(function () use ($result) {
        $order = Order::where('payment_ref', $result->merchantReference)
            ->lockForUpdate()
            ->firstOrFail();

        if ($order->status !== 'awaiting_payment') {
            return; // already settled — idempotent no-op
        }

        $order->transitionTo($result->status, $result->rawPayload);
    });

    ProcessPaymentResult::dispatch($result)->onQueue('payments');

    return response('OK', 200);
}

Model money as integers or DECIMAL(12,2), and convert at the boundary. EgoPay expects a decimal string; your database should not contain floats. A single Money value object formats to number_format($cents / 100, 2, '.', '') — one place to change when the currency rules bite you.

Explicit state machine, not booleans. awaiting_payment → paid → refunded | failed, with an order_events table storing the raw callback body, the computed hash, the verification result, and the timestamp. When support asks "what did the gateway actually send us in March," that table is the only answer you'll have.

Test at the contract level. Because everything is behind PaymentGateway, you can:

  • Unit-test EgoPayGateway::fields() against a fixed fixture — golden output for a known order, asserting the hash string matches a recorded value. This is the only meaningful test of the crypto.
  • Unit-test verifyCallback() against recorded-good and recorded-tampered payloads (flip one character in the amount, expect rejection).
  • Feature-test the checkout and callback routes with $this->instance(PaymentGateway::class, new FakeGateway()), covering success, failure, duplicate callback, and unparseable payload.

Record a handful of real callback payloads from surviving logs (or from the library's fixtures) into tests/Fixtures/egopay/. Without those, your hash verification is untested and you will discover the field-ordering bug in production.

Feature-flag the driver. Resolve PaymentGateway from config (payments.driver) rather than hard-binding the concrete class. When you migrate — and you will — the cutover is an env change plus a data backfill, and you keep the EgoPay path alive only for in-flight orders.

Gotchas and Tips

The counterparty is gone. The single biggest gotcha: there is no sandbox to spike against, no merchant support to answer field-ordering questions, and no settlement on the other end. Any "test mode" URL inside the library likely points at a host that no longer resolves. Plan for the callback and redirect to be validated against recorded fixtures rather than live traffic, and design the runbook around manual reconciliation from historical data. If your team is expecting a working test environment, correct that expectation in the first standup.

PHP 5-era code will fatal on PHP 8. Expect dynamic properties, each(), missing return types, and possibly create_function. Check composer.json's require.php. Symptoms you'll see: Deprecated: Creation of dynamic property, Uncaught Error: Call to undefined function, or a composer install that refuses on the platform check. Budget for either a shim layer in the wrapper or a mechanical modernization pass (rector-style) over the vendored copy — which further argues for vendoring rather than requiring.

Assume no Laravel auto-discovery. The description promises a provider, config, and facade; the actual package very likely predates all three. Verify by checking composer.json for an extra.laravel.providers block. If it's absent, the provider and facade are yours to write, and any php artisan vendor:publish command in the docs will fail — nothing to publish.

The hash field order is the whole protocol. EgoPay's signature is a concatenation of a specific subset of fields in a specific order, salted with the SCI password, then SHA-256'd. If your verifyCallback() and fields() disagree by one field or one separator, every callback fails validation — usually silently, as a 403 you blame on the network. Copy the ordering character-for-character from the library source, add a comment citing the file and line, and pin it with a golden-output test. Treat any upstream change to that ordering as a breaking change.

CSRF will eat the callback. VerifyCsrfToken on a provider POST yields a 419, the provider records a delivery failure, and it retries — sometimes for days. Exclude the callback path (or use withoutMiddleware on the route) and confirm with a request test that a POST without a token returns 200, not 419.

Amounts and formatting. EgoPay expects a decimal string; 1.0 and 1.00 may hash differently if the formatting happens before or after hashing. Decide once: format the amount string, hash that exact string, and send the same string. Never re-format between building the form and signing it.

Idempotency is on you. There is no idempotency key in the SCI flow. Duplicate detection has to come from your unique index on (order_id, batch_number) and a state check inside a transaction. Test it explicitly: dispatch the same callback fixture three times and assert one state transition and zero duplicate emails.

Log with the payload redacted. The callback contains customer identifiers and the hash; your sci_id and secret must never appear in logs, exception traces, or dd() output. Add the credential keys to your logging scrubber in config/logging.php or a Monolog processor, and never log $request->all() raw on the checkout route.

Watch for global state. Old libraries sometimes read credentials from define()d constants, $_SERVER, or a static singleton. That breaks multi-tenant setups, php artisan config:cache, and parallel PHPUnit runs. If you find statics, wrap them in a constructor-injected config object at the boundary and never let the statics leak past EgoPayGateway.

Refunds and status lookups are probably out of scope. SCI is a redirect-and-callback protocol. If the library exposes no refund or status-poll method, don't invent one against a dead endpoint — build those behind the PaymentGateway interface as NotSupported, and let the migration target PS P fill them in. This keeps the interface honest and the swap mechanical.

Extension points worth using, in order: the PaymentGateway binding, the order_events table (your only forensic record), a queued ProcessPaymentResult job for everything post-callback, and a feature flag on the driver. Everything else in this package you should touch exactly once — when you delete it.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib