Build and integrate SimplePay online payment features using the netipar/simplepay Laravel package, including payment start, IPN webhook handling, back redirects, refunds, two-step payments, card storage (OneClick), recurring payments, auto payments, and Request to Pay (RTP) flows.
php artisan boost:add-skill netipar/simplepay
Save this content to: .claude/skills/simplepay-development/SKILL.md
# SimplePay Payment Integration Development
## When to use this skill
Use this skill when:
- Starting online payments and redirecting users to the SimplePay payment page
- Handling IPN (Instant Payment Notification) webhooks
- Processing back redirects after payment completion
- Implementing refunds, cancellations, or transaction queries
- Implementing two-step (authorize + capture) payment flows
- Storing cards for OneClick payments or recurring billing
- Implementing auto (PCI-DSS) payments with direct card data
- Implementing Request to Pay (RTP) bank transfer flows
- Accepting Google Pay with the Google Pay button on the merchant's own checkout page
- Sending customers to separate pages per payment outcome (`urls`)
- Configuring the simplepay package (sandbox, multi-currency, logging)
## Package overview
The `netipar/simplepay` package wraps the SimplePay PHP SDK v2.1 for Laravel. It provides typed DTOs, enums, events, and signature verification.
- Namespace: `Netipar\SimplePay\`
- Facade: `Netipar\SimplePay\Facades\SimplePay`
- Config: `config/simplepay.php`
- Routes: `POST /simplepay/ipn`, `GET /simplepay/back` (auto-registered)
## Service access
Access services via the facade:
```php
use Netipar\SimplePay\Facades\SimplePay;
SimplePay::payment() // PaymentService
SimplePay::cardStorage() // CardStorageService
SimplePay::autoPayment() // AutoPaymentService
SimplePay::rtp() // RtpService
```
## Start a payment
```php
use Netipar\SimplePay\Dto\PaymentRequest;
use Netipar\SimplePay\Dto\Item;
use Netipar\SimplePay\Dto\Address;
use Netipar\SimplePay\Enums\Currency;
use Netipar\SimplePay\Enums\PaymentMethod;
use Netipar\SimplePay\Facades\SimplePay;
$request = new PaymentRequest(
currency: Currency::HUF,
total: 6000,
orderRef: 'ORDER-001',
customerEmail: '[email protected]',
language: 'HU',
url: route('simplepay.back'),
items: [
new Item(title: 'Product name', price: 1000, quantity: 1),
new Item(title: 'Service', price: 2500, quantity: 2, tax: 27),
],
methods: [PaymentMethod::CARD],
invoice: new Address(
name: 'Customer Kft.',
country: 'HU',
city: 'Budapest',
zip: '1234',
address: 'Main Street 1.',
),
);
$response = SimplePay::payment()->start($request);
// $response->transactionId (int)
// $response->paymentUrl (string) — redirect user here
// $response->timeout (?string)
return redirect($response->paymentUrl);
```
## Two-step payment (authorize + capture)
```php
use Netipar\SimplePay\Dto\PaymentRequest;
use Netipar\SimplePay\Dto\FinishRequest;
// Step 1: authorize
$request = new PaymentRequest(
currency: Currency::HUF,
total: 5000,
orderRef: 'ORDER-002',
customerEmail: '[email protected]',
language: 'HU',
url: route('simplepay.back'),
items: [new Item(title: 'Product', price: 5000, quantity: 1)],
methods: [PaymentMethod::CARD],
twoStep: true,
);
$response = SimplePay::payment()->start($request);
// Step 2: capture (later)
$finish = new FinishRequest(
currency: Currency::HUF,
transactionId: $response->transactionId,
orderRef: 'ORDER-002',
amount: 5000, // the amount to capture (sent as approveTotal)
originalTotal: 5000, // the authorized amount
);
$finishResponse = SimplePay::payment()->finish($finish);
// $finishResponse->transactionId, $finishResponse->total
```
## IPN webhook handling
Routes auto-registered: `POST /simplepay/ipn` with `VerifySimplePaySignature` middleware.
Listen to dispatched events:
```php
use Netipar\SimplePay\Events\PaymentSucceeded;
use Netipar\SimplePay\Events\PaymentFailed;
use Netipar\SimplePay\Events\PaymentCancelled;
use Netipar\SimplePay\Events\PaymentTimedOut;
use Netipar\SimplePay\Events\PaymentAuthorized;
use Netipar\SimplePay\Events\PaymentRefunded;
use Netipar\SimplePay\Events\IpnReceived;
// In EventServiceProvider or as listener
PaymentSucceeded::class => [HandleSuccessfulPayment::class],
PaymentFailed::class => [HandleFailedPayment::class],
```
All events carry an `ipn` property of type `Netipar\SimplePay\Dto\IpnMessage`:
```php
class HandleSuccessfulPayment
{
public function handle(PaymentSucceeded $event): void
{
$ipn = $event->ipn;
$ipn->orderRef; // string
$ipn->transactionId; // int
$ipn->status; // PaymentStatus enum
$ipn->paymentMethod; // ?string, e.g. "CARD"
$ipn->finishDate; // ?string
}
}
```
## Available events
| Event | Dispatched when |
|---|---|
| `IpnReceived` | Every IPN webhook |
| `PaymentSucceeded` | Status = FINISHED |
| `PaymentAuthorized` | Status = AUTHORIZED |
| `PaymentFailed` | Status = NOTAUTHORIZED |
| `PaymentCancelled` | Status = CANCELLED |
| `PaymentTimedOut` | Status = TIMEOUT |
| `PaymentRefunded` | Status = REFUND |
## Handle back redirect
The `GET /simplepay/back` route is auto-registered. Customize the response by implementing `BackUrlResponse`:
```php
use Illuminate\Http\Request;
use Netipar\SimplePay\Contracts\BackUrlResponse;
use Netipar\SimplePay\Dto\BackResponse;
use Symfony\Component\HttpFoundation\Response;
class CustomBackResponse implements BackUrlResponse
{
public function toResponse(Request $request, BackResponse $back): Response
{
// $back->event (BackEvent enum: SUCCESS, FAIL, CANCEL, TIMEOUT, PENDING for Request to Pay)
// $back->transactionId
// $back->orderRef
return redirect()->route('payment.result', [
'status' => $back->event->value,
]);
}
}
// Register in AppServiceProvider
$this->app->singleton(BackUrlResponse::class, CustomBackResponse::class);
```
## Refund a payment
```php
use Netipar\SimplePay\Dto\RefundRequest;
$request = new RefundRequest(
currency: Currency::HUF,
transactionId: 123456789,
orderRef: 'ORDER-001',
amount: 1000, // sent as refundTotal
);
$response = SimplePay::payment()->refund($request);
// $response->transactionId, $response->remainingTotal
```
## Query a transaction
```php
$response = SimplePay::payment()->query(
currency: Currency::HUF,
transactionId: 123456789,
);
// $response->transactionId
// $response->status (PaymentStatus enum)
// $response->total
// $response->orderRef
// $response->merchant
// $response->remainingTotal
```
## Cancel a transaction
```php
SimplePay::payment()->cancel(
currency: Currency::HUF,
transactionId: 123456789,
);
```
## Card storage (OneClick) payment
```php
use Netipar\SimplePay\Dto\CardStorageDoRequest;
$request = new CardStorageDoRequest(
currency: Currency::HUF,
transactionId: null, // deprecated: not sent
cardId: 'stored-card-id',
orderRef: 'ORDER-003',
total: 1000,
customerEmail: '[email protected]',
url: route('simplepay.back'), // return URL after a 3DS challenge
cardSecret: $cardSecret,
threeDSReqAuthMethod: '02', // 01 guest, 02 registered, 05 third-party login
browser: $browserData, // BrowserData: accept, agent, language, colorDepth, screen size, timeZone, ip
);
$result = SimplePay::cardStorage()->do($request);
```
## Recurring payment
```php
use Netipar\SimplePay\Dto\RecurringRequest;
use Netipar\SimplePay\Enums\TransactionType;
// The token comes from the card registration start: $response->tokens (active after the successful payment)
$request = new RecurringRequest(
currency: Currency::HUF,
transactionId: null, // deprecated: not sent
orderRef: 'ORDER-004',
total: 2990,
customerEmail: '[email protected]',
url: route('simplepay.back'),
type: TransactionType::Mit,
token: 'recurring-token', // required, otherwise InvalidArgumentException
);
$result = SimplePay::cardStorage()->doRecurring($request);
```
## Card query and cancel
```php
// Query stored card
$result = SimplePay::cardStorage()->cardQuery(
currency: Currency::HUF,
cardId: 'stored-card-id',
history: true,
);
// Cancel stored card
SimplePay::cardStorage()->cardCancel(Currency::HUF, 'stored-card-id');
// Token management
SimplePay::cardStorage()->tokenQuery(Currency::HUF, 'token');
SimplePay::cardStorage()->tokenCancel(Currency::HUF, 'token');
```
## Request to Pay (RTP) — bank transfer
```php
use Netipar\SimplePay\Dto\RedirectUrls;
use Netipar\SimplePay\Dto\RtpStartRequest;
use Netipar\SimplePay\Dto\RtpDoRequest;
use Netipar\SimplePay\Dto\RtpPayment;
// Start a payment request; the customer approves it in their bank
$response = SimplePay::rtp()->start(new RtpStartRequest(
currency: Currency::HUF,
total: 5000,
orderRef: 'ORDER-005',
customerEmail: '[email protected]',
url: route('simplepay.back'),
customer: 'Customer Name',
// Optional per-event URLs; RTP adds `pending` (bank account given, transfer not arrived yet)
urls: new RedirectUrls(
success: route('payment.success'),
fail: route('payment.failed'),
cancel: route('checkout'),
timeout: route('payment.timeout'),
pending: route('payment.pending'),
),
));
// $response->paymentUrl, $response->transactionId; $response->timeout is null for RTP
// Batch payment requests without the customer being present (1–1000 per call)
$result = SimplePay::rtp()->do(new RtpDoRequest(
currency: Currency::HUF,
payments: [
new RtpPayment(orderRef: 'ORDER-006', total: 1000, customerEmail: '[email protected]', customer: 'Customer A'),
new RtpPayment(orderRef: 'ORDER-007', total: 2000, customerEmail: '[email protected]', customer: 'Customer B'),
],
));
// Query/refund/reverse RTP
SimplePay::rtp()->query(Currency::HUF, transactionId: '12345');
SimplePay::rtp()->refund(Currency::HUF, transactionId: '12345', amount: 5000, originalTotal: 5000);
SimplePay::rtp()->reverse(Currency::HUF, transactionId: '12345');
```
## Google Pay on the merchant's checkout page
Google Pay also works on the SimplePay payment page with `PaymentMethod::CARD`. For the Google Pay button on the merchant's own checkout page:
```php
use Netipar\SimplePay\Dto\GooglePayDoRequest;
// 1. Checkout page: pass the button configuration to the frontend (Blade variable or Inertia prop)
$googlePay = SimplePay::payment()->googlePayConfig(Currency::HUF, $order->total);
// gpayMerchantId, gpayMerchantName, gpayEnvironment (TEST in sandbox), gateway ('simplepay'),
// gatewayMerchantId, paymentType ('CARD'), total, currency
// 2. Backend endpoint: the frontend posts btoa(paymentMethodData.tokenizationData.token)
$validated = $request->validate(['googlePayToken' => ['required', 'string']]);
$response = SimplePay::payment()->doGooglePay(new GooglePayDoRequest(
currency: Currency::HUF,
total: $order->total,
orderRef: $order->reference,
customerEmail: $order->email,
googlePayToken: $validated['googlePayToken'],
url: route('simplepay.back'), // return URL after a 3DS challenge, from server-side config
threeDSReqAuthMethod: '02', // 01 guest, 02 registered, 05 third-party login
));
return response()->json([
'transactionId' => $response->transactionId,
'redirectUrl' => $response->redirectUrl, // set only when a 3DS challenge is required
]);
```
- A 3DS challenge (error code 3004) does not throw: `$response->requiresChallenge()` is true and the frontend navigates to `redirectUrl`. Other error codes throw `SimplePayApiException`.
- Without 3DS the customer stays on the checkout page and SimplePay sends no back redirect.
- Fulfil the order from the IPN or a `query()`, not from the synchronous response.
- Needs `SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID` and `SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME` (Google Pay & Wallet Console). Full JavaScript example: `examples/en/google-pay.md`.
## Event-specific redirect URLs
`PaymentRequest` and `RtpStartRequest` accept `urls: new RedirectUrls(success:, fail:, cancel:, timeout:, pending: null)`. SimplePay ignores `url` when `urls` is sent; `pending` is for Request to Pay only. Pages behind these URLs receive the signed `r`/`s` parameters: verify them with `SimplePay::payment()->handleBack($r, $s)`.
## Available enums
Always use enum cases instead of raw strings:
- `Currency`: `HUF`, `EUR`, `USD`
- `PaymentMethod`: `CARD`, `WIRE`, `EAM`
- `PaymentStatus`: `INIT`, `FINISHED`, `AUTHORIZED`, `NOTAUTHORIZED`, `INPAYMENT`, `CANCELLED`, `TIMEOUT`, `INFRAUD`, `FRAUD`, `REFUND`, `REVERSED`
- `BackEvent`: `SUCCESS`, `FAIL`, `CANCEL`, `TIMEOUT`, `PENDING` (Request to Pay only; cover it in exhaustive `match` expressions)
- `TransactionType`: `CIT` (Customer-Initiated), `MIT` (Merchant-Initiated), `REC` (Recurring)
- `ErrorCode`: 350+ error codes with `description()` method returning Hungarian text
## Error handling
```php
use Netipar\SimplePay\Exceptions\SimplePayApiException;
try {
$response = SimplePay::payment()->start($request);
} catch (SimplePayApiException $e) {
$e->getMessage(); // "[5083] Token times szükséges"
$e->getErrorCodes(); // [5083]
$e->hasErrorCode(5083); // true
$e->getResolvedCodes(); // [ErrorCode::TokenTimesRequired]
$e->getResponseData(); // full API response, e.g. redirectUrl of a 3DS challenge (3004)
}
```
## Configuration
Key `.env` variables:
```
SIMPLEPAY_SANDBOX=true
SIMPLEPAY_HUF_MERCHANT=merchant_id
SIMPLEPAY_HUF_SECRET_KEY=secret_key
SIMPLEPAY_EUR_MERCHANT=eur_merchant_id
SIMPLEPAY_EUR_SECRET_KEY=eur_secret_key
SIMPLEPAY_BACK_URL=https://yourdomain.com/payment/back
SIMPLEPAY_TIMEOUT=600
SIMPLEPAY_LOG_CHANNEL=simplepay
SIMPLEPAY_AUTO_CHALLENGE=true
SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID=google_pay_merchant_id
SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME="Shop name"
```
Multi-currency: configure separate merchant credentials per currency (HUF, EUR, USD).
Publish config with: `php artisan vendor:publish --tag=simplepay-config` (or `--tag=simplepay` for config and logos).
## Logo and asset publishing
Publish SimplePay logos and card brand images to `public/vendor/simplepay/`:
```bash
php artisan vendor:publish --tag=simplepay-assets
```
Published structure:
```
public/vendor/simplepay/
├── logos/ # Individual logos
│ ├── simplepay_horizontal_01.png # SimplePay horizontal (recommended)
│ ├── simplepay_horizontal_01-white.png
│ ├── simplepay_horizontal_02.png
│ ├── simplepay_horizontal_02-white.png
│ ├── simplepay_vertical.png
│ ├── simplepay_vertical-wite.png
│ ├── simplepay_top.png
│ ├── simplepay_top-white.png
│ ├── simplepay_200x50.png
│ ├── simplepay_w140.png / simplepay_w140-white.png
│ ├── simplepay_w240.png / simplepay_w240-white.png
│ ├── simplepay_w360.png / simplepay_w360-white.png
│ ├── visa_logo_color.png / visa_logo_bw.png
│ ├── mastercard_logo_color_new.png / mastercard_logo_bw_new.png
│ ├── maestro_logo_color_new.png / maestro_logo_bw_new.png
│ ├── amex_logo_color.png / amex_logo_bw.png
│ ├── Apple_Pay_Mark_RGB.png
│ ├── GPay_logo_color_new.png
│ ├── OTPszepkartya_logo_color.png
│ └── qvik_logo_color.png
└── variaciok/ # Pre-composed logo combinations
├── SimplePay_Logo_wo_Amex/ # SimplePay + Visa/MC/Maestro (no Amex)
├── SimplePay_Logo_with_Amex/ # SimplePay + Visa/MC/Maestro/Amex
├── SimplePay_Logo_wo_Amex+Qvik/
├── SimplePay_Logo_with_Amex+Qvik/
├── SimplePay_Logo_kartyak_wo_Amex/
├── SimplePay_Logo-kartyak_with_Amex/
├── SimplePay_Logo+MC-Maestro-Visa/
├── SimplePay_Logo+Qvik/
└── SimplePay_Logo+SZEP/
```
Use in Blade templates:
```html
<img src="{{ asset('vendor/simplepay/logos/simplepay_horizontal_01.png') }}" alt="SimplePay">
```
Each variation directory contains layout options: `_left`, `_top_horizontal`, `_top_vertical`, `_vertical`, with `-hu` Hungarian-labeled versions.
## DTOs overview
All DTOs use readonly properties. Request DTOs include a `toArray()` method that generates a random salt and adds SDK version info.
| DTO | Purpose |
|---|---|
| `PaymentRequest` | Start a payment with items, addresses, methods |
| `PaymentResponse` | Transaction ID, payment URL, timeout (null for RTP starts), recurring `tokens` of a card registration |
| `RedirectUrls` | Per-event redirect URLs (`urls`) for payment and RTP starts |
| `GooglePayDoRequest` / `GooglePayResponse` | Merchant-side Google Pay charge; `redirectUrl` when a 3DS challenge is required |
| `FinishRequest` / `FinishResponse` | Two-step capture |
| `RefundRequest` / `RefundResponse` | Refund with remaining total |
| `QueryResponse` | Full transaction status |
| `BackResponse` | Parsed back redirect (base64-decoded, signature-verified) |
| `IpnMessage` | Webhook payload with payment status |
| `Item` | Product: title, price, quantity, tax |
| `Address` | Name, country, city, zip, address |
| `CardStorageDoRequest` | OneClick payment with stored card |
| `RecurringRequest` | Recurring payment with token |
| `AutoPaymentRequest` | Direct card payment (PCI-DSS) |
| `RtpStartRequest` / `RtpDoRequest` | Request to Pay flows |
| `RtpPayment` | Bank transfer payment details |
| `CardData` | Card number, expiry, CVC |
| `BrowserData` | Browser data for the issuer's 3DS check, sent as `browser` (add `ip`) |
Use this skill when:
urls)The netipar/simplepay package wraps the SimplePay PHP SDK v2.1 for Laravel. It provides typed DTOs, enums, events, and signature verification.
Netipar\SimplePay\Netipar\SimplePay\Facades\SimplePayconfig/simplepay.phpPOST /simplepay/ipn, GET /simplepay/back (auto-registered)Access services via the facade:
use Netipar\SimplePay\Facades\SimplePay;
SimplePay::payment() // PaymentService
SimplePay::cardStorage() // CardStorageService
SimplePay::autoPayment() // AutoPaymentService
SimplePay::rtp() // RtpService
use Netipar\SimplePay\Dto\PaymentRequest;
use Netipar\SimplePay\Dto\Item;
use Netipar\SimplePay\Dto\Address;
use Netipar\SimplePay\Enums\Currency;
use Netipar\SimplePay\Enums\PaymentMethod;
use Netipar\SimplePay\Facades\SimplePay;
$request = new PaymentRequest(
currency: Currency::HUF,
total: 6000,
orderRef: 'ORDER-001',
customerEmail: '[email protected]',
language: 'HU',
url: route('simplepay.back'),
items: [
new Item(title: 'Product name', price: 1000, quantity: 1),
new Item(title: 'Service', price: 2500, quantity: 2, tax: 27),
],
methods: [PaymentMethod::CARD],
invoice: new Address(
name: 'Customer Kft.',
country: 'HU',
city: 'Budapest',
zip: '1234',
address: 'Main Street 1.',
),
);
$response = SimplePay::payment()->start($request);
// $response->transactionId (int)
// $response->paymentUrl (string) — redirect user here
// $response->timeout (?string)
return redirect($response->paymentUrl);
use Netipar\SimplePay\Dto\PaymentRequest;
use Netipar\SimplePay\Dto\FinishRequest;
// Step 1: authorize
$request = new PaymentRequest(
currency: Currency::HUF,
total: 5000,
orderRef: 'ORDER-002',
customerEmail: '[email protected]',
language: 'HU',
url: route('simplepay.back'),
items: [new Item(title: 'Product', price: 5000, quantity: 1)],
methods: [PaymentMethod::CARD],
twoStep: true,
);
$response = SimplePay::payment()->start($request);
// Step 2: capture (later)
$finish = new FinishRequest(
currency: Currency::HUF,
transactionId: $response->transactionId,
orderRef: 'ORDER-002',
amount: 5000, // the amount to capture (sent as approveTotal)
originalTotal: 5000, // the authorized amount
);
$finishResponse = SimplePay::payment()->finish($finish);
// $finishResponse->transactionId, $finishResponse->total
Routes auto-registered: POST /simplepay/ipn with VerifySimplePaySignature middleware.
Listen to dispatched events:
use Netipar\SimplePay\Events\PaymentSucceeded;
use Netipar\SimplePay\Events\PaymentFailed;
use Netipar\SimplePay\Events\PaymentCancelled;
use Netipar\SimplePay\Events\PaymentTimedOut;
use Netipar\SimplePay\Events\PaymentAuthorized;
use Netipar\SimplePay\Events\PaymentRefunded;
use Netipar\SimplePay\Events\IpnReceived;
// In EventServiceProvider or as listener
PaymentSucceeded::class => [HandleSuccessfulPayment::class],
PaymentFailed::class => [HandleFailedPayment::class],
All events carry an ipn property of type Netipar\SimplePay\Dto\IpnMessage:
class HandleSuccessfulPayment
{
public function handle(PaymentSucceeded $event): void
{
$ipn = $event->ipn;
$ipn->orderRef; // string
$ipn->transactionId; // int
$ipn->status; // PaymentStatus enum
$ipn->paymentMethod; // ?string, e.g. "CARD"
$ipn->finishDate; // ?string
}
}
| Event | Dispatched when |
|---|---|
IpnReceived |
Every IPN webhook |
PaymentSucceeded |
Status = FINISHED |
PaymentAuthorized |
Status = AUTHORIZED |
PaymentFailed |
Status = NOTAUTHORIZED |
PaymentCancelled |
Status = CANCELLED |
PaymentTimedOut |
Status = TIMEOUT |
PaymentRefunded |
Status = REFUND |
The GET /simplepay/back route is auto-registered. Customize the response by implementing BackUrlResponse:
use Illuminate\Http\Request;
use Netipar\SimplePay\Contracts\BackUrlResponse;
use Netipar\SimplePay\Dto\BackResponse;
use Symfony\Component\HttpFoundation\Response;
class CustomBackResponse implements BackUrlResponse
{
public function toResponse(Request $request, BackResponse $back): Response
{
// $back->event (BackEvent enum: SUCCESS, FAIL, CANCEL, TIMEOUT, PENDING for Request to Pay)
// $back->transactionId
// $back->orderRef
return redirect()->route('payment.result', [
'status' => $back->event->value,
]);
}
}
// Register in AppServiceProvider
$this->app->singleton(BackUrlResponse::class, CustomBackResponse::class);
use Netipar\SimplePay\Dto\RefundRequest;
$request = new RefundRequest(
currency: Currency::HUF,
transactionId: 123456789,
orderRef: 'ORDER-001',
amount: 1000, // sent as refundTotal
);
$response = SimplePay::payment()->refund($request);
// $response->transactionId, $response->remainingTotal
$response = SimplePay::payment()->query(
currency: Currency::HUF,
transactionId: 123456789,
);
// $response->transactionId
// $response->status (PaymentStatus enum)
// $response->total
// $response->orderRef
// $response->merchant
// $response->remainingTotal
SimplePay::payment()->cancel(
currency: Currency::HUF,
transactionId: 123456789,
);
use Netipar\SimplePay\Dto\CardStorageDoRequest;
$request = new CardStorageDoRequest(
currency: Currency::HUF,
transactionId: null, // deprecated: not sent
cardId: 'stored-card-id',
orderRef: 'ORDER-003',
total: 1000,
customerEmail: '[email protected]',
url: route('simplepay.back'), // return URL after a 3DS challenge
cardSecret: $cardSecret,
threeDSReqAuthMethod: '02', // 01 guest, 02 registered, 05 third-party login
browser: $browserData, // BrowserData: accept, agent, language, colorDepth, screen size, timeZone, ip
);
$result = SimplePay::cardStorage()->do($request);
use Netipar\SimplePay\Dto\RecurringRequest;
use Netipar\SimplePay\Enums\TransactionType;
// The token comes from the card registration start: $response->tokens (active after the successful payment)
$request = new RecurringRequest(
currency: Currency::HUF,
transactionId: null, // deprecated: not sent
orderRef: 'ORDER-004',
total: 2990,
customerEmail: '[email protected]',
url: route('simplepay.back'),
type: TransactionType::Mit,
token: 'recurring-token', // required, otherwise InvalidArgumentException
);
$result = SimplePay::cardStorage()->doRecurring($request);
// Query stored card
$result = SimplePay::cardStorage()->cardQuery(
currency: Currency::HUF,
cardId: 'stored-card-id',
history: true,
);
// Cancel stored card
SimplePay::cardStorage()->cardCancel(Currency::HUF, 'stored-card-id');
// Token management
SimplePay::cardStorage()->tokenQuery(Currency::HUF, 'token');
SimplePay::cardStorage()->tokenCancel(Currency::HUF, 'token');
use Netipar\SimplePay\Dto\RedirectUrls;
use Netipar\SimplePay\Dto\RtpStartRequest;
use Netipar\SimplePay\Dto\RtpDoRequest;
use Netipar\SimplePay\Dto\RtpPayment;
// Start a payment request; the customer approves it in their bank
$response = SimplePay::rtp()->start(new RtpStartRequest(
currency: Currency::HUF,
total: 5000,
orderRef: 'ORDER-005',
customerEmail: '[email protected]',
url: route('simplepay.back'),
customer: 'Customer Name',
// Optional per-event URLs; RTP adds `pending` (bank account given, transfer not arrived yet)
urls: new RedirectUrls(
success: route('payment.success'),
fail: route('payment.failed'),
cancel: route('checkout'),
timeout: route('payment.timeout'),
pending: route('payment.pending'),
),
));
// $response->paymentUrl, $response->transactionId; $response->timeout is null for RTP
// Batch payment requests without the customer being present (1–1000 per call)
$result = SimplePay::rtp()->do(new RtpDoRequest(
currency: Currency::HUF,
payments: [
new RtpPayment(orderRef: 'ORDER-006', total: 1000, customerEmail: '[email protected]', customer: 'Customer A'),
new RtpPayment(orderRef: 'ORDER-007', total: 2000, customerEmail: '[email protected]', customer: 'Customer B'),
],
));
// Query/refund/reverse RTP
SimplePay::rtp()->query(Currency::HUF, transactionId: '12345');
SimplePay::rtp()->refund(Currency::HUF, transactionId: '12345', amount: 5000, originalTotal: 5000);
SimplePay::rtp()->reverse(Currency::HUF, transactionId: '12345');
Google Pay also works on the SimplePay payment page with PaymentMethod::CARD. For the Google Pay button on the merchant's own checkout page:
use Netipar\SimplePay\Dto\GooglePayDoRequest;
// 1. Checkout page: pass the button configuration to the frontend (Blade variable or Inertia prop)
$googlePay = SimplePay::payment()->googlePayConfig(Currency::HUF, $order->total);
// gpayMerchantId, gpayMerchantName, gpayEnvironment (TEST in sandbox), gateway ('simplepay'),
// gatewayMerchantId, paymentType ('CARD'), total, currency
// 2. Backend endpoint: the frontend posts btoa(paymentMethodData.tokenizationData.token)
$validated = $request->validate(['googlePayToken' => ['required', 'string']]);
$response = SimplePay::payment()->doGooglePay(new GooglePayDoRequest(
currency: Currency::HUF,
total: $order->total,
orderRef: $order->reference,
customerEmail: $order->email,
googlePayToken: $validated['googlePayToken'],
url: route('simplepay.back'), // return URL after a 3DS challenge, from server-side config
threeDSReqAuthMethod: '02', // 01 guest, 02 registered, 05 third-party login
));
return response()->json([
'transactionId' => $response->transactionId,
'redirectUrl' => $response->redirectUrl, // set only when a 3DS challenge is required
]);
$response->requiresChallenge() is true and the frontend navigates to redirectUrl. Other error codes throw SimplePayApiException.query(), not from the synchronous response.SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID and SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME (Google Pay & Wallet Console). Full JavaScript example: examples/en/google-pay.md.PaymentRequest and RtpStartRequest accept urls: new RedirectUrls(success:, fail:, cancel:, timeout:, pending: null). SimplePay ignores url when urls is sent; pending is for Request to Pay only. Pages behind these URLs receive the signed r/s parameters: verify them with SimplePay::payment()->handleBack($r, $s).
Always use enum cases instead of raw strings:
Currency: HUF, EUR, USDPaymentMethod: CARD, WIRE, EAMPaymentStatus: INIT, FINISHED, AUTHORIZED, NOTAUTHORIZED, INPAYMENT, CANCELLED, TIMEOUT, INFRAUD, FRAUD, REFUND, REVERSEDBackEvent: SUCCESS, FAIL, CANCEL, TIMEOUT, PENDING (Request to Pay only; cover it in exhaustive match expressions)TransactionType: CIT (Customer-Initiated), MIT (Merchant-Initiated), REC (Recurring)ErrorCode: 350+ error codes with description() method returning Hungarian textuse Netipar\SimplePay\Exceptions\SimplePayApiException;
try {
$response = SimplePay::payment()->start($request);
} catch (SimplePayApiException $e) {
$e->getMessage(); // "[5083] Token times szükséges"
$e->getErrorCodes(); // [5083]
$e->hasErrorCode(5083); // true
$e->getResolvedCodes(); // [ErrorCode::TokenTimesRequired]
$e->getResponseData(); // full API response, e.g. redirectUrl of a 3DS challenge (3004)
}
Key .env variables:
SIMPLEPAY_SANDBOX=true
SIMPLEPAY_HUF_MERCHANT=merchant_id
SIMPLEPAY_HUF_SECRET_KEY=secret_key
SIMPLEPAY_EUR_MERCHANT=eur_merchant_id
SIMPLEPAY_EUR_SECRET_KEY=eur_secret_key
SIMPLEPAY_BACK_URL=https://yourdomain.com/payment/back
SIMPLEPAY_TIMEOUT=600
SIMPLEPAY_LOG_CHANNEL=simplepay
SIMPLEPAY_AUTO_CHALLENGE=true
SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID=google_pay_merchant_id
SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME="Shop name"
Multi-currency: configure separate merchant credentials per currency (HUF, EUR, USD).
Publish config with: php artisan vendor:publish --tag=simplepay-config (or --tag=simplepay for config and logos).
Publish SimplePay logos and card brand images to public/vendor/simplepay/:
php artisan vendor:publish --tag=simplepay-assets
Published structure:
public/vendor/simplepay/
├── logos/ # Individual logos
│ ├── simplepay_horizontal_01.png # SimplePay horizontal (recommended)
│ ├── simplepay_horizontal_01-white.png
│ ├── simplepay_horizontal_02.png
│ ├── simplepay_horizontal_02-white.png
│ ├── simplepay_vertical.png
│ ├── simplepay_vertical-wite.png
│ ├── simplepay_top.png
│ ├── simplepay_top-white.png
│ ├── simplepay_200x50.png
│ ├── simplepay_w140.png / simplepay_w140-white.png
│ ├── simplepay_w240.png / simplepay_w240-white.png
│ ├── simplepay_w360.png / simplepay_w360-white.png
│ ├── visa_logo_color.png / visa_logo_bw.png
│ ├── mastercard_logo_color_new.png / mastercard_logo_bw_new.png
│ ├── maestro_logo_color_new.png / maestro_logo_bw_new.png
│ ├── amex_logo_color.png / amex_logo_bw.png
│ ├── Apple_Pay_Mark_RGB.png
│ ├── GPay_logo_color_new.png
│ ├── OTPszepkartya_logo_color.png
│ └── qvik_logo_color.png
└── variaciok/ # Pre-composed logo combinations
├── SimplePay_Logo_wo_Amex/ # SimplePay + Visa/MC/Maestro (no Amex)
├── SimplePay_Logo_with_Amex/ # SimplePay + Visa/MC/Maestro/Amex
├── SimplePay_Logo_wo_Amex+Qvik/
├── SimplePay_Logo_with_Amex+Qvik/
├── SimplePay_Logo_kartyak_wo_Amex/
├── SimplePay_Logo-kartyak_with_Amex/
├── SimplePay_Logo+MC-Maestro-Visa/
├── SimplePay_Logo+Qvik/
└── SimplePay_Logo+SZEP/
Use in Blade templates:
<img src="{{ asset('vendor/simplepay/logos/simplepay_horizontal_01.png') }}" alt="SimplePay">
Each variation directory contains layout options: _left, _top_horizontal, _top_vertical, _vertical, with -hu Hungarian-labeled versions.
All DTOs use readonly properties. Request DTOs include a toArray() method that generates a random salt and adds SDK version info.
| DTO | Purpose |
|---|---|
PaymentRequest |
Start a payment with items, addresses, methods |
PaymentResponse |
Transaction ID, payment URL, timeout (null for RTP starts), recurring tokens of a card registration |
RedirectUrls |
Per-event redirect URLs (urls) for payment and RTP starts |
GooglePayDoRequest / GooglePayResponse |
Merchant-side Google Pay charge; redirectUrl when a 3DS challenge is required |
FinishRequest / FinishResponse |
Two-step capture |
RefundRequest / RefundResponse |
Refund with remaining total |
QueryResponse |
Full transaction status |
BackResponse |
Parsed back redirect (base64-decoded, signature-verified) |
IpnMessage |
Webhook payload with payment status |
Item |
Product: title, price, quantity, tax |
Address |
Name, country, city, zip, address |
CardStorageDoRequest |
OneClick payment with stored card |
RecurringRequest |
Recurring payment with token |
AutoPaymentRequest |
Direct card payment (PCI-DSS) |
RtpStartRequest / RtpDoRequest |
Request to Pay flows |
RtpPayment |
Bank transfer payment details |
CardData |
Card number, expiry, CVC |
BrowserData |
Browser data for the issuer's 3DS check, sent as browser (add ip) |
How can I help you explore Laravel packages today?