1. Go to this page and download the library: Download mariandumitru/netopay library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
use MarianDumitru\Netopay\Facades\Netopay;
$response = Netopay::start($orderData);
return redirect($response->paymentUrl); // see XHR note in section 1 below if you're on Inertia/Livewire
use Illuminate\Support\Facades\Event;
use MarianDumitru\Netopay\Events\NetopiaPaymentApproved;
Event::listen(NetopiaPaymentApproved::class, function ($event) {
// fulfil the order — see "Handling payment outcomes" for the full pattern
});
use MarianDumitru\Netopay\Dto\BillingDto;
use MarianDumitru\Netopay\Dto\OrderDto;
use MarianDumitru\Netopay\Facades\Netopay;
$billing = new BillingDto(
email: $user->email,
phone: $billingProfile->phone,
firstName: $user->first_name,
lastName: $user->last_name,
city: $billingProfile->city,
country: $billingProfile->numericCountryCode, // ISO 3166-1 numeric (e.g. 642 for Romania)
state: $billingProfile->state,
postalCode: $billingProfile->post_code,
details: $billingProfile->full_address,
);
$orderData = new OrderDto(
orderId: $payment->uuid, // your unique order identifier
amount: 149.99,
currency: 'RON',
description: 'Subscription — 2 devices (Monthly)',
billing: $billing,
);
$response = Netopay::start($orderData);
// Persist what you'll need later (ntpID, and the 3DS authenticationToken if present)
// so your return listener can look the payment up and verify 3DS.
$payment->update([
'provider_payment_id' => $response->providerPaymentId,
'payload' => [
'start' => [
'customerAction' => $response->customerAction, // contains authenticationToken when 3DS is
> // Inertia
> return Inertia::location($response->paymentUrl);
>
> // Livewire (v3)
> return $this->redirect($response->paymentUrl, navigate: false);
>
> // Axios / Fetch — return the URL as JSON and redirect on the client
> return response()->json(['paymentUrl' => $response->paymentUrl]);
> // then on the front-end: window.location.href = data.paymentUrl;
>
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Event;
use MarianDumitru\Netopay\Events\NetopiaIpnProcessingFailed;
use MarianDumitru\Netopay\Events\NetopiaPaymentApproved;
use MarianDumitru\Netopay\Events\NetopiaPaymentFailed;
use MarianDumitru\Netopay\Events\NetopiaPaymentPending;
use MarianDumitru\Netopay\Events\NetopiaReturnReceived;
public function boot(): void
{
Event::listen(NetopiaPaymentApproved::class, HandlePaymentApproved::class);
Event::listen(NetopiaPaymentFailed::class, HandlePaymentFailed::class);
Event::listen(NetopiaPaymentPending::class, HandlePaymentPending::class);
Event::listen(NetopiaReturnReceived::class, HandleNetopiaReturn::class);
Event::listen(NetopiaIpnProcessingFailed::class, ReportIpnFailure::class);
}
use Illuminate\Support\Facades\DB;
use MarianDumitru\Netopay\Events\NetopiaPaymentApproved;
class HandlePaymentApproved
{
public function handle(NetopiaPaymentApproved $event): void
{
$status = $event->status; // PaymentStatusDto
DB::transaction(function () use ($status) {
$payment = Payment::where('uuid', $status->orderId)
->lockForUpdate()
->first();
if (! $payment || $payment->status === 'paid') {
return; // already fulfilled by the other path
}
$payment->update([
'status' => 'paid',
'provider_payment_id' => $status->providerPaymentId,
'auth_code' => $status->authCode,
'rrn' => $status->rrn,
'paid_at' => now(),
]);
if ($status->paymentToken) {
PaymentToken::updateOrCreate(
['user_id' => $payment->user_id],
['token' => $status->paymentToken],
);
}
SubscriptionService::fulfil($payment);
});
}
}
use MarianDumitru\Netopay\Events\NetopiaIpnProcessingFailed;
class ReportIpnFailure
{
public function handle(NetopiaIpnProcessingFailed $event): void
{
report($event->exception); // send to Sentry / your reporter
Log::warning('Netopia IPN failed', [
'order_id' => $event->payload['order']['orderID'] ?? null,
'ntp_id' => $event->payload['payment']['ntpID'] ?? null,
]);
}
}
use MarianDumitru\Netopay\Events\NetopiaReturnReceived;
use MarianDumitru\Netopay\Facades\Netopay;
class HandleNetopiaReturn
{
public function handle(NetopiaReturnReceived $event): void
{
if ($event->orderId === '') {
// The package already logs a warning. Decide here whether to alert or silently drop.
return;
}
$payment = Payment::where('uuid', $event->orderId)->first();
if (! $payment) {
return;
}
// Set during the start call — see "Initiating a payment" above
$authToken = data_get($payment->payload, 'start.customerAction.authenticationToken');
if ($authToken) {
// 3DS flow: verify authentication first
$result = Netopay::verifyAuth(
$event->orderId,
$authToken,
$payment->provider_payment_id,
$event->formData,
);
} else {
// Hosted-page flow: retrieve confirmed status
$result = Netopay::retrieveStatus(
$payment->provider_payment_id,
$event->orderId,
);
}
$payment->update(['status' => $result->state->value]);
}
}
$response = Netopay::startWithToken($orderData, $savedToken);
if ($response->providerStatusCode === 3 || $response->providerStatusCode === 5) {
// Payment approved — fulfil the order
}
// routes/web.php
Route::get('/payments/landing', function () {
// The orderId arrives via NetopiaReturnReceived; the listener can stash it on the session
// under 'netopay.last_order_id' so this intermediate route knows where to go.
$orderId = session()->pull('netopay.last_order_id');
$url = session()->pull("netopay.post_payment_redirect.{$orderId}", '/dashboard');
return redirect($url);
});
// HandleNetopiaReturn listener
public function handle(NetopiaReturnReceived $event): void
{
session()->put('netopay.last_order_id', $event->orderId);
// ... the rest of your verifyAuth / retrieveStatus logic
}
use MarianDumitru\Netopay\Facades\Netopay;
// Initiate a hosted-page payment
Netopay::start(OrderDto $orderData): StartPaymentResponseDto
// Initiate a merchant-initiated recurring payment
Netopay::startWithToken(OrderDto $orderData, string $token): StartPaymentResponseDto
// Retrieve confirmed status from Netopia
Netopay::retrieveStatus(string $ntpId, string $orderId): PaymentStatusDto
// Complete a 3DS authentication
Netopay::verifyAuth(string $orderId, string $authToken, string $ntpId, array $formData): PaymentStatusDto
// Parse a raw IPN body without an API call (used internally)
Netopay::handleIpn(array $body, array $headers = []): PaymentStatusDto
use Illuminate\Support\Facades\Http;
use MarianDumitru\Netopay\Enums\PaymentStatus;
Http::fake([
'*/payment/card/start' => Http::response([
'customerAction' => [],
'error' => ['code' => '101', 'message' => 'Redirect user to payment page'],
'payment' => [
'ntpID' => '1234567',
'status' => 1,
'paymentURL' => 'https://secure-sandbox.netopia-payments.com/ui/card?p=TEST',
],
], 200),
]);
use MarianDumitru\Netopay\Contracts\NetopiaClientInterface;
$this->app->bind(NetopiaClientInterface::class, FakeNetopiaClient::class);
use Illuminate\Support\Facades\Event;
use MarianDumitru\Netopay\Events\NetopiaPaymentApproved;
Event::fake();
// ... trigger the IPN endpoint
Event::assertDispatched(NetopiaPaymentApproved::class, function ($event) {
return $event->status->orderId === 'your-order-id';
});