diff --git a/AGENTS.md b/AGENTS.md
index 1bad700..e5ab7e9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -30,9 +30,9 @@ This repository is a Laravel package. Keep the package focused, idiomatic, and e
## SimplePay Architecture
- The package follows the official SimplePay PHP SDKs (API v2, published at https://simplepay.hu/fejlesztoknek/). `official-docs/` holds the reference PDFs; it is not part of the dist archive.
-- `src/SimplePay.php` sits behind the `SimplePay` facade and exposes the services in `src/Services`: `PaymentService` (start, finish, refund, query, cancel, qvik/EAM, Apple Pay), `CardStorageService` (OneClick `do`, recurring, card and token query/cancel), `AutoPaymentService` (merchant-side card data) and `RtpService` (Request to Pay).
+- `src/SimplePay.php` sits behind the `SimplePay` facade and exposes the services in `src/Services`: `PaymentService` (start, finish, refund, query, cancel, qvik/EAM, Apple Pay, merchant-side Google Pay with `doGooglePay()` and `googlePayConfig()`), `CardStorageService` (OneClick `do`, recurring, card and token query/cancel), `AutoPaymentService` (merchant-side card data) and `RtpService` (Request to Pay).
- Request and response payloads are readonly DTOs in `src/Dto`: `toArray()` builds the API body including `salt` and `sdkVersion`, `fromArray()` maps the response. Enums live in `src/Enums`; `ErrorCode` mirrors the official error code list with Hungarian descriptions.
-- `src/Support/Client.php` signs every request with HMAC-SHA384 (`Support/Signature`), verifies the response signature and throws `SimplePayApiException` when `errorCodes` is not empty; the exception keeps the response body (`getResponseData()`), e.g. the `redirectUrl` of a 3DS challenge (3004).
+- `src/Support/Client.php` signs every request with HMAC-SHA384 (`Support/Signature`), verifies the response signature and throws `SimplePayApiException` when `errorCodes` is not empty; the exception keeps the response body (`getResponseData()`), e.g. the `redirectUrl` of a 3DS challenge (3004). `doGooglePay()` turns a 3004 with a `redirectUrl` into a normal `GooglePayResponse`.
- Merchant credentials are configured per currency (`simplepay.merchants`) and resolved by `Support/MerchantResolver`; IPN bodies without `currency` are resolved by their `merchant` field.
- Routes: `POST simplepay/ipn` (`VerifySimplePaySignature` middleware; `SimplePayWebhookController` dispatches the events in `src/Events`) and `GET simplepay/back` (`SimplePayBackController`, response customisable through the `BackUrlResponse` contract).
- Consumer documentation is bilingual: keep `examples/en/` and `examples/hu/` in sync with each other and with the README.
diff --git a/README.md b/README.md
index 1ff8791..02a3013 100644
--- a/README.md
+++ b/README.md
@@ -138,6 +138,26 @@ $response->timeout; // Payment timeout
return redirect($response->paymentUrl);
```
+### Event-Specific Redirect URLs
+
+SimplePay returns the customer to `url` after every outcome. Pass `urls` to use a separate page per outcome; SimplePay ignores `url` when `urls` is sent:
+
+```php
+use Netipar\SimplePay\Dto\RedirectUrls;
+
+$response = SimplePay::payment()->start(new PaymentRequest(
+ // ... same as above
+ urls: new RedirectUrls(
+ success: route('payment.success'),
+ fail: route('payment.failed'),
+ cancel: route('checkout'),
+ timeout: route('payment.timeout'),
+ ),
+));
+```
+
+The order reference of a failed transaction can be sent again until the first successful payment; every start creates a new SimplePay transaction ID.
+
### Two-Step Payment (Authorize + Finish)
```php
@@ -261,6 +281,35 @@ $result = SimplePay::payment()->doApplePay(new ApplePayDoRequest(
));
```
+### Google Pay on Your Checkout Page
+
+Google Pay is also available on the SimplePay payment page with `PaymentMethod::CARD`. To show the Google Pay button on your own checkout page, render it with `googlePayConfig()` and charge the token from your backend:
+
+```php
+use Netipar\SimplePay\Dto\GooglePayDoRequest;
+
+// Checkout page: the values of the Google Pay button
+$googlePay = SimplePay::payment()->googlePayConfig(Currency::HUF, 5000);
+
+// Backend endpoint called by the button with the Base64 encoded Google Pay token
+$validated = $request->validate(['googlePayToken' => ['required', 'string']]);
+
+$response = SimplePay::payment()->doGooglePay(new GooglePayDoRequest(
+ currency: Currency::HUF,
+ total: 5000,
+ orderRef: 'ORDER-GP-001',
+ customerEmail: 'customer@example.com',
+ googlePayToken: $validated['googlePayToken'],
+ url: route('simplepay.back'), // where SimplePay returns the customer after a 3DS challenge
+));
+
+if ($response->requiresChallenge()) {
+ // Send the browser to $response->redirectUrl
+}
+```
+
+Set `SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID` and `SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME` from the Google Pay & Wallet Console. The full flow with the JavaScript button is in the [Google Pay example](examples/en/google-pay.md).
+
### EAM / Qvik Payment
```php
@@ -287,6 +336,7 @@ $result = SimplePay::rtp()->start(new RtpStartRequest(
orderRef: 'RTP-2026-001',
customerEmail: 'customer@example.com',
url: route('simplepay.back'),
+ customer: 'Customer Name',
));
// Query RTP status
@@ -299,6 +349,8 @@ SimplePay::rtp()->refund(Currency::HUF, $transactionId, 15000);
SimplePay::rtp()->reverse(Currency::HUF, $transactionId);
```
+Request to Pay back redirects can carry `BackEvent::Pending`: the customer gave the bank account number, but the transfer has not arrived yet. `RtpStartRequest` accepts `urls` with a `pending` URL as well.
+
## IPN Webhook
The package automatically registers a `POST /simplepay/ipn` route with signature verification middleware. Listen to payment events in your application:
@@ -362,6 +414,7 @@ class SimplePayBackResponse implements BackUrlResponse
BackEvent::Fail => redirect()->route('payment.failed'),
BackEvent::Cancel => redirect()->route('checkout'),
BackEvent::Timeout => redirect()->route('payment.timeout'),
+ BackEvent::Pending => redirect()->route('payment.pending'), // Request to Pay only
};
}
}
@@ -374,7 +427,7 @@ 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`
+- **`BackEvent`** -- `SUCCESS`, `FAIL`, `CANCEL`, `TIMEOUT`, `PENDING` (Request to Pay)
- **`TransactionType`** -- `CIT` (Customer Initiated), `MIT` (Merchant Initiated), `REC` (Recurring)
- **`ErrorCode`** -- 358+ error codes with Hungarian descriptions
@@ -424,6 +477,8 @@ SIMPLEPAY_BACK_URL=https://yourdomain.com/payment/back
SIMPLEPAY_TIMEOUT=600
SIMPLEPAY_LOG_CHANNEL=simplepay
SIMPLEPAY_AUTO_CHALLENGE=true
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID=your_google_pay_merchant_id
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME="Your Shop"
```
Multi-currency support -- each currency has its own merchant credentials:
@@ -457,7 +512,7 @@ Usage in Blade, Vue.js, or React:
```
-Available logos: SimplePay (multiple sizes/orientations + white variants), Visa, Mastercard, Maestro, Amex, Apple Pay, Google Pay, Qvik, OTP SZÉP. Pre-composed card type combinations are also included. See [examples](examples/en/logos-and-assets.md) for Blade, Vue.js, and React checkout page examples.
+Available logos: SimplePay (multiple sizes/orientations + white variants, and an SVG of the vertical logo), Visa, Mastercard, Maestro, Amex, Apple Pay, Google Pay, Qvik, OTP SZÉP. Pre-composed card type combinations are also included. See [examples](examples/en/logos-and-assets.md) for Blade, Vue.js, and React checkout page examples.
## Logging
@@ -475,7 +530,7 @@ Set `SIMPLEPAY_LOG_CHANNEL` to any Laravel log channel name. Example dedicated c
| Service | Access | Purpose |
|---|---|---|
-| `PaymentService` | `SimplePay::payment()` | Standard payments, refunds, queries, Apple Pay, EAM |
+| `PaymentService` | `SimplePay::payment()` | Standard payments, refunds, queries, Apple Pay, Google Pay, EAM |
| `CardStorageService` | `SimplePay::cardStorage()` | OneClick, recurring, card/token management |
| `AutoPaymentService` | `SimplePay::autoPayment()` | PCI-DSS direct card payments |
| `RtpService` | `SimplePay::rtp()` | Request to Pay (bank transfers) |
@@ -484,6 +539,8 @@ Set `SIMPLEPAY_LOG_CHANNEL` to any Laravel log channel name. Example dedicated c
This package is built based on the **SimplePay PHP SDK v2.1** (API v2). All API requests include the `sdkVersion: SimplePay_PHP_SDK_2.1_Laravel` identifier.
+The merchant-side Google Pay flow follows the Payment PHP SDK 2.1.5 (2026-06-27) and the SimplePay Google Pay documentation (2026-09-01); the `urls` and `pending` handling follows the Request to Pay documentation (2026-08-17).
+
## Examples
- [English examples](examples/en/)
diff --git a/config/simplepay.php b/config/simplepay.php
index 317ca87..dac4454 100644
--- a/config/simplepay.php
+++ b/config/simplepay.php
@@ -105,6 +105,22 @@
'log_channel' => env('SIMPLEPAY_LOG_CHANNEL'),
+ /*
+ |--------------------------------------------------------------------------
+ | Google Pay
+ |--------------------------------------------------------------------------
+ |
+ | Merchant-side Google Pay acceptance through SimplePay. The merchant ID
+ | and name come from the Google Pay & Wallet Console; the checkout page
+ | needs them to render the Google Pay button.
+ |
+ */
+
+ 'google_pay' => [
+ 'merchant_id' => env('SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID'),
+ 'merchant_name' => env('SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME'),
+ ],
+
/*
|--------------------------------------------------------------------------
| Auto Challenge
diff --git a/examples/en/back-url.md b/examples/en/back-url.md
index 3fa87eb..a68b01c 100644
--- a/examples/en/back-url.md
+++ b/examples/en/back-url.md
@@ -37,6 +37,7 @@ class SimplePayBackResponse implements BackUrlResponse
BackEvent::Fail => redirect()->route('payment.failed'),
BackEvent::Cancel => redirect()->route('payment.cancelled'),
BackEvent::Timeout => redirect()->route('payment.timeout'),
+ BackEvent::Pending => redirect()->route('payment.pending'),
};
}
}
@@ -81,6 +82,9 @@ The `BackResponse` object contains the following properties:
| `BackEvent::Fail` | Payment failed |
| `BackEvent::Cancel` | Customer cancelled the payment |
| `BackEvent::Timeout` | Payment timed out |
+| `BackEvent::Pending` | Request to Pay only: the bank account number is given, the transfer has not arrived yet |
+
+A `match` over `$back->event` has to cover `BackEvent::Pending` as well, otherwise a Request to Pay redirect throws an `UnhandledMatchError`.
## Example: Advanced Handling
@@ -102,6 +106,7 @@ class SimplePayBackResponse implements BackUrlResponse
BackEvent::Fail => $this->handleFailure($back),
BackEvent::Cancel => $this->handleCancel($back),
BackEvent::Timeout => $this->handleTimeout($back),
+ BackEvent::Pending => response()->view('payment.pending', ['orderRef' => $back->orderRef]),
};
}
diff --git a/examples/en/configuration.md b/examples/en/configuration.md
index 30f69a6..1564f35 100644
--- a/examples/en/configuration.md
+++ b/examples/en/configuration.md
@@ -21,6 +21,8 @@ SIMPLEPAY_EUR_SECRET_KEY=your_eur_secret_key
SIMPLEPAY_BACK_URL=https://yourdomain.com/payment/back
SIMPLEPAY_LOG_CHANNEL=simplepay
SIMPLEPAY_TIMEOUT=600
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID=your_google_pay_merchant_id
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME="Your Shop"
```
## Configuration Structure
@@ -109,6 +111,20 @@ return [
*/
'log_channel' => env('SIMPLEPAY_LOG_CHANNEL'),
+ /*
+ |--------------------------------------------------------------------------
+ | Google Pay
+ |--------------------------------------------------------------------------
+ |
+ | The Google Pay merchant ID and name for the Google Pay button on your
+ | own checkout page.
+ |
+ */
+ 'google_pay' => [
+ 'merchant_id' => env('SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID'),
+ 'merchant_name' => env('SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME'),
+ ],
+
/*
|--------------------------------------------------------------------------
| Auto Challenge
@@ -173,3 +189,7 @@ The default value is 600 seconds (10 minutes).
## Auto Challenge
When `auto_challenge` is enabled, the package automatically handles 3D Secure challenge flows during card payments. This is enabled by default and is recommended for most use cases.
+
+## Google Pay
+
+`google_pay.merchant_id` and `google_pay.merchant_name` come from the Google Pay & Wallet Console. They are only needed when the Google Pay button is on your own checkout page; see [Google Pay on Your Checkout Page](google-pay.md).
diff --git a/examples/en/google-pay.md b/examples/en/google-pay.md
new file mode 100644
index 0000000..061c3fa
--- /dev/null
+++ b/examples/en/google-pay.md
@@ -0,0 +1,221 @@
+# Google Pay on Your Checkout Page
+
+Customers can already pay with Google Pay on the SimplePay payment page (`PaymentMethod::CARD`). This page covers the merchant-side flow that SimplePay introduced in 2026: the Google Pay button sits on your own checkout page, and your backend charges the Google Pay token with `SimplePay::payment()->doGooglePay()`.
+
+The flow follows the official SimplePay Google Pay documentation (2026-09-01) and the `dogooglepay.php` sample of the SimplePay PHP SDK 2.1.5.
+
+## Prerequisites
+
+- Google Pay is enabled for your SimplePay merchant account.
+- You have a Google Pay merchant ID from the Google Pay & Wallet Console, and Google has approved your website (HTTPS domain, screenshots of the checkout). Send the merchant ID, the domain and the screenshots to SimplePay support.
+- The checkout follows the Google Pay [brand guidelines](https://developers.google.com/pay/api/web/guides/brand-guidelines) and the [integration checklist](https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist).
+
+## Configuration
+
+```env
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID=your_google_pay_merchant_id
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME="Your Shop"
+```
+
+The SimplePay merchant credentials of the currency are used as for any other payment.
+
+## The Flow
+
+1. The checkout page renders the Google Pay button with the values of `googlePayConfig()`.
+2. After the customer approves the payment in the Google Pay sheet, the page posts the Base64 encoded Google Pay token to your backend.
+3. Your backend calls `doGooglePay()` and returns the result to the page.
+4. Without a 3DS challenge the customer stays on the checkout page, and SimplePay makes no back redirect. Show the result on the page.
+5. If the card issuer asks for a 3DS challenge, the response has a `redirectUrl`: send the browser there. After the challenge SimplePay redirects the customer to the `url` of the request with the signed `r` and `s` parameters, which the package's `simplepay.back` route handles.
+6. Fulfil the order from the IPN (or a `query()`), never from the synchronous response or the back redirect alone.
+
+## Backend
+
+```php
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Netipar\SimplePay\Dto\GooglePayDoRequest;
+use Netipar\SimplePay\Enums\Currency;
+use Netipar\SimplePay\Exceptions\SimplePayApiException;
+use Netipar\SimplePay\Facades\SimplePay;
+
+class GooglePayController
+{
+ public function __invoke(Request $request, Order $order): JsonResponse
+ {
+ $validated = $request->validate([
+ 'googlePayToken' => ['required', 'string'],
+ ]);
+
+ try {
+ $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'),
+ threeDSReqAuthMethod: '02',
+ ));
+ } catch (SimplePayApiException $e) {
+ return response()->json(['errorCodes' => $e->getErrorCodes()], 422);
+ }
+
+ return response()->json([
+ 'transactionId' => $response->transactionId,
+ 'redirectUrl' => $response->redirectUrl,
+ ]);
+ }
+}
+```
+
+Take the amount, the order reference and the return `url` from your own data, not from the browser.
+
+### Request Fields
+
+| Field | Description |
+|---|---|
+| `currency`, `total`, `orderRef`, `customerEmail` | As for a normal payment |
+| `googlePayToken` | `paymentMethodData.tokenizationData.token` from Google Pay, Base64 encoded (`btoa()` in the browser) |
+| `url` | Where SimplePay sends the customer after a 3DS challenge |
+| `language` | Defaults to `HU` |
+| `type` | Defaults to `TransactionType::Cit` |
+| `threeDSReqAuthMethod` | How the customer is registered: `01` guest, `02` registered, `05` third-party login |
+| `invoice`, `customer` | Optional billing data |
+| `timeout` | In seconds; defaults to `simplepay.timeout` |
+
+### Response
+
+| Property | Description |
+|---|---|
+| `$response->transactionId` | SimplePay transaction ID |
+| `$response->orderRef` | Your order reference |
+| `$response->currency`, `$response->total` | When SimplePay returns them |
+| `$response->redirectUrl` | Set only when a 3DS challenge is required |
+| `$response->requiresChallenge()` | `true` when `redirectUrl` is set |
+
+A 3DS challenge (error code `3004`) is a normal outcome and does not throw. Every other error code throws `SimplePayApiException`.
+
+## Frontend
+
+Pass the button configuration to the checkout page:
+
+```php
+return view('checkout', [
+ 'order' => $order,
+ 'googlePay' => SimplePay::payment()->googlePayConfig(Currency::HUF, $order->total),
+]);
+```
+
+| Key | Value |
+|---|---|
+| `gpayMerchantId`, `gpayMerchantName` | `simplepay.google_pay.merchant_id` and `merchant_name` |
+| `gpayEnvironment` | `TEST` in sandbox mode, `PRODUCTION` otherwise |
+| `gateway` | `simplepay` |
+| `gatewayMerchantId` | Your SimplePay merchant ID for the currency |
+| `paymentType` | `CARD` |
+| `total`, `currency` | The amount shown in the Google Pay sheet |
+
+A minimal Blade page, based on the official sample:
+
+```blade
+
+
+
+
+```
+
+In an Inertia (Vue or React) page pass `googlePayConfig()` as a prop, load `pay.js` when the component mounts, and use the same request objects.
+
+## Testing
+
+In sandbox mode the button runs in Google's `TEST` environment. The official checklist asks you to test:
+
+- a successful payment without 3DS, staying on the checkout page;
+- a failed payment without 3DS;
+- the `3004` response and the redirect to `redirectUrl`;
+- a successful 3DS challenge and the return to `url`;
+- a failed, cancelled and expired 3DS challenge;
+- a missing or invalid `r`/`s` parameter;
+- the back redirect and the IPN arriving in either order;
+- a customer who never returns to your site.
diff --git a/examples/en/logos-and-assets.md b/examples/en/logos-and-assets.md
index f4abc60..dd7216e 100644
--- a/examples/en/logos-and-assets.md
+++ b/examples/en/logos-and-assets.md
@@ -22,6 +22,7 @@ public/vendor/simplepay/
│ ├── simplepay_horizontal_01.png # Horizontal logo v1
│ ├── simplepay_horizontal_02.png # Horizontal logo v2
│ ├── simplepay_vertical.png # Vertical logo
+│ ├── simplepay_vertical.svg # Vertical logo (SVG)
│ ├── simplepay_top.png # Top-placed logo
│ ├── simplepay_*-white.png # White variants (for dark backgrounds)
│ ├── visa_logo_color.png # Visa (color)
diff --git a/examples/en/payment-start.md b/examples/en/payment-start.md
index 820cf61..429879a 100644
--- a/examples/en/payment-start.md
+++ b/examples/en/payment-start.md
@@ -43,7 +43,7 @@ return redirect($response->paymentUrl);
|---|---|---|
| `$response->transactionId` | `int` | SimplePay transaction ID |
| `$response->paymentUrl` | `string` | URL to redirect the customer to |
-| `$response->timeout` | `string` | Payment timeout |
+| `$response->timeout` | `?string` | Payment timeout (missing from Request to Pay starts) |
## Two-Step Payment (Preauthorization)
@@ -154,6 +154,35 @@ $response = SimplePay::payment()->start(new PaymentRequest(
return redirect($response->paymentUrl);
```
+## Event-Specific Redirect URLs
+
+By default SimplePay sends the customer back to `url` after every outcome. To use a separate page per outcome, pass `urls`. SimplePay ignores `url` when `urls` is sent, so give all four events.
+
+```php
+use Netipar\SimplePay\Dto\RedirectUrls;
+
+$response = SimplePay::payment()->start(new PaymentRequest(
+ currency: Currency::HUF,
+ total: 2500,
+ orderRef: 'ORDER-124',
+ customerEmail: 'customer@example.com',
+ language: 'HU',
+ url: route('simplepay.back'),
+ urls: new RedirectUrls(
+ success: route('payment.success'),
+ fail: route('payment.failed'),
+ cancel: route('checkout'),
+ timeout: route('payment.timeout'),
+ ),
+));
+```
+
+These pages receive the signed `r` and `s` query parameters too; verify them with `SimplePay::payment()->handleBack($request->query('r'), $request->query('s'))`.
+
+## Reusing an Order Reference
+
+`orderRef` identifies the order in your system. The order reference of a failed transaction can be sent again until the first successful payment; every new start creates a new SimplePay `transactionId`, so store the transaction ID of each attempt.
+
## Available Payment Methods
| Enum | Description |
diff --git a/examples/en/rtp.md b/examples/en/rtp.md
index f495c0e..5fa3d8f 100644
--- a/examples/en/rtp.md
+++ b/examples/en/rtp.md
@@ -19,8 +19,38 @@ $response = SimplePay::rtp()->start(new RtpStartRequest(
url: route('simplepay.back'),
customer: 'Test Customer',
));
+
+$response->paymentUrl; // Redirect the customer here
+$response->transactionId; // SimplePay transaction ID
+$response->timeout; // null: RTP start responses have no timeout
```
+### Event-Specific Redirect URLs
+
+Request to Pay adds a `pending` event to the card payment events: the customer gave the bank account number, but the transfer has not arrived yet. SimplePay ignores `url` when `urls` is sent.
+
+```php
+use Netipar\SimplePay\Dto\RedirectUrls;
+
+$response = SimplePay::rtp()->start(new RtpStartRequest(
+ currency: Currency::HUF,
+ total: 5000,
+ orderRef: 'RTP-ORDER-002',
+ customerEmail: 'customer@example.com',
+ url: route('simplepay.back'),
+ customer: 'Test Customer',
+ urls: new RedirectUrls(
+ success: route('payment.success'),
+ fail: route('payment.failed'),
+ cancel: route('checkout'),
+ timeout: route('payment.timeout'),
+ pending: route('payment.pending'),
+ ),
+));
+```
+
+On the package's `simplepay.back` route the pending redirect arrives as `BackEvent::Pending`.
+
## Batch RTP
Send multiple payment requests at once using the batch RTP method.
diff --git a/examples/hu/fizetes-inditas.md b/examples/hu/fizetes-inditas.md
index d250ce6..2d69be6 100644
--- a/examples/hu/fizetes-inditas.md
+++ b/examples/hu/fizetes-inditas.md
@@ -44,7 +44,7 @@ A `start()` metódus által visszaadott válasz a következő adatokat tartalmaz
```php
$response->transactionId; // SimplePay tranzakció azonosító (int)
$response->paymentUrl; // Fizetési oldal URL, ahova a vásárlót át kell irányítani
-$response->timeout; // Időtúllépés másodpercben
+$response->timeout; // A fizetés indításának határideje (Request to Pay indításnál null)
```
## Kétlépcsős fizetés (preautorizáció)
@@ -166,6 +166,35 @@ $response = SimplePay::payment()->start(new PaymentRequest(
));
```
+## Eseményenkénti visszairányítási címek
+
+Alapesetben a SimplePay minden kimenet után az `url` címre irányítja vissza a vásárlót. Ha kimenetenként külön oldalt szeretnél, add meg az `urls` mezőt. Ha az `urls` szerepel a kérésben, a SimplePay figyelmen kívül hagyja az `url`-t, ezért mind a négy eseményt meg kell adni.
+
+```php
+use Netipar\SimplePay\Dto\RedirectUrls;
+
+$response = SimplePay::payment()->start(new PaymentRequest(
+ currency: Currency::HUF,
+ total: 2500,
+ orderRef: 'ORDER-124',
+ customerEmail: 'vasarlo@example.com',
+ language: 'HU',
+ url: route('simplepay.back'),
+ urls: new RedirectUrls(
+ success: route('payment.success'),
+ fail: route('payment.failed'),
+ cancel: route('checkout'),
+ timeout: route('payment.timeout'),
+ ),
+));
+```
+
+Ezek az oldalak is megkapják az aláírt `r` és `s` paramétert; ellenőrizd őket a `SimplePay::payment()->handleBack($request->query('r'), $request->query('s'))` hívással.
+
+## Rendelésazonosító újrahasználata
+
+Az `orderRef` a rendelést azonosítja a rendszeredben. Egy sikertelen tranzakció rendelésazonosítója az első sikeres fizetésig újra elküldhető; minden új indítás új SimplePay `transactionId`-t kap, ezért a tranzakcióazonosítót próbálkozásonként tárold.
+
## Fizetési módszerek
A `methods` tömbben megadható értékek:
diff --git a/examples/hu/google-pay.md b/examples/hu/google-pay.md
new file mode 100644
index 0000000..b520839
--- /dev/null
+++ b/examples/hu/google-pay.md
@@ -0,0 +1,221 @@
+# Google Pay a saját fizetési oldalon
+
+A vásárlók eddig is fizethettek Google Pay-jel a SimplePay fizetőoldalán (`PaymentMethod::CARD`). Ez az oldal a SimplePay 2026-ban bevezetett, kereskedő oldali megoldását mutatja be: a Google Pay gomb a saját fizetési oldaladon jelenik meg, a backend pedig a `SimplePay::payment()->doGooglePay()` hívással terheli meg a Google Pay tokent.
+
+A folyamat a SimplePay hivatalos Google Pay dokumentációját (2026.09.01.) és a SimplePay PHP SDK 2.1.5 `dogooglepay.php` mintáját követi.
+
+## Előfeltételek
+
+- A SimplePay kereskedői fiókodon engedélyezve van a Google Pay.
+- Van Google Pay kereskedői azonosítód a Google Pay & Wallet Console-ból, és a Google jóváhagyta a weboldaladat (HTTPS domain, képernyőképek a fizetési folyamatról). Az azonosítót, a domaint és a képernyőképeket a SimplePay ügyfélszolgálatának kell elküldeni.
+- A fizetési oldal megfelel a Google Pay [márkairányelveinek](https://developers.google.com/pay/api/web/guides/brand-guidelines) és [integrációs ellenőrzőlistájának](https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist).
+
+## Konfiguráció
+
+```env
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID=google_pay_kereskedoi_azonosito
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME="Webáruházad neve"
+```
+
+A pénznemhez tartozó SimplePay kereskedői adatokat ugyanúgy használja, mint bármely más fizetésnél.
+
+## A folyamat
+
+1. A fizetési oldal a `googlePayConfig()` értékeivel jeleníti meg a Google Pay gombot.
+2. Miután a vásárló jóváhagyta a fizetést a Google Pay felületén, az oldal elküldi a Base64-kódolt Google Pay tokent a backendnek.
+3. A backend meghívja a `doGooglePay()`-t, és visszaadja az eredményt az oldalnak.
+4. 3DS challenge nélkül a vásárló a fizetési oldalon marad, és a SimplePay nem irányítja vissza. Az eredményt az oldalon kell megjeleníteni.
+5. Ha a kártyakibocsátó 3DS challenge-et kér, a válaszban `redirectUrl` érkezik, ide kell átirányítani a böngészőt. A challenge után a SimplePay a kérés `url` címére irányítja vissza a vásárlót az aláírt `r` és `s` paraméterekkel; ezeket a csomag `simplepay.back` route-ja kezeli.
+6. A rendelést az IPN (vagy egy `query()`) alapján teljesítsd, soha ne csak a szinkron válasz vagy a visszairányítás alapján.
+
+## Backend
+
+```php
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Netipar\SimplePay\Dto\GooglePayDoRequest;
+use Netipar\SimplePay\Enums\Currency;
+use Netipar\SimplePay\Exceptions\SimplePayApiException;
+use Netipar\SimplePay\Facades\SimplePay;
+
+class GooglePayController
+{
+ public function __invoke(Request $request, Order $order): JsonResponse
+ {
+ $validated = $request->validate([
+ 'googlePayToken' => ['required', 'string'],
+ ]);
+
+ try {
+ $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'),
+ threeDSReqAuthMethod: '02',
+ ));
+ } catch (SimplePayApiException $e) {
+ return response()->json(['errorCodes' => $e->getErrorCodes()], 422);
+ }
+
+ return response()->json([
+ 'transactionId' => $response->transactionId,
+ 'redirectUrl' => $response->redirectUrl,
+ ]);
+ }
+}
+```
+
+Az összeget, a rendelésazonosítót és a visszatérési `url`-t a saját adataidból vedd, ne a böngészőből.
+
+### A kérés mezői
+
+| Mező | Leírás |
+|---|---|
+| `currency`, `total`, `orderRef`, `customerEmail` | Mint egy normál fizetésnél |
+| `googlePayToken` | A Google Pay `paymentMethodData.tokenizationData.token` értéke Base64-kódolva (a böngészőben `btoa()`) |
+| `url` | Ide irányítja vissza a SimplePay a vásárlót a 3DS challenge után |
+| `language` | Alapértéke `HU` |
+| `type` | Alapértéke `TransactionType::Cit` |
+| `threeDSReqAuthMethod` | A vásárló regisztrációja: `01` vendég, `02` regisztrált, `05` harmadik feles belépés |
+| `invoice`, `customer` | Opcionális számlázási adatok |
+| `timeout` | Másodpercben; alapértéke a `simplepay.timeout` |
+
+### A válasz
+
+| Tulajdonság | Leírás |
+|---|---|
+| `$response->transactionId` | SimplePay tranzakcióazonosító |
+| `$response->orderRef` | A rendelésazonosítód |
+| `$response->currency`, `$response->total` | Ha a SimplePay visszaadja |
+| `$response->redirectUrl` | Csak akkor van értéke, ha 3DS challenge szükséges |
+| `$response->requiresChallenge()` | `true`, ha van `redirectUrl` |
+
+A 3DS challenge (`3004`-es hibakód) normál kimenet, nem dob kivételt. Minden más hibakód `SimplePayApiException`-t dob.
+
+## Frontend
+
+Add át a gomb beállításait a fizetési oldalnak:
+
+```php
+return view('checkout', [
+ 'order' => $order,
+ 'googlePay' => SimplePay::payment()->googlePayConfig(Currency::HUF, $order->total),
+]);
+```
+
+| Kulcs | Érték |
+|---|---|
+| `gpayMerchantId`, `gpayMerchantName` | `simplepay.google_pay.merchant_id` és `merchant_name` |
+| `gpayEnvironment` | Sandbox módban `TEST`, egyébként `PRODUCTION` |
+| `gateway` | `simplepay` |
+| `gatewayMerchantId` | A pénznemhez tartozó SimplePay kereskedői azonosító |
+| `paymentType` | `CARD` |
+| `total`, `currency` | A Google Pay felületen megjelenő összeg |
+
+Egy minimális Blade-oldal a hivatalos minta alapján:
+
+```blade
+
+
+
+
+```
+
+Inertia-oldalon (Vue vagy React) a `googlePayConfig()` eredményét propként add át, a `pay.js`-t a komponens betöltésekor töltsd be, és ugyanezeket a kérésobjektumokat használd.
+
+## Tesztelés
+
+Sandbox módban a gomb a Google `TEST` környezetében fut. A hivatalos ellenőrzőlista szerint ezeket kell tesztelni:
+
+- sikeres fizetés 3DS nélkül, a fizetési oldalon maradva;
+- sikertelen fizetés 3DS nélkül;
+- a `3004`-es válasz és az átirányítás a `redirectUrl` címre;
+- sikeres 3DS challenge és a visszatérés az `url` címre;
+- sikertelen, megszakított és lejárt 3DS challenge;
+- hiányzó vagy hibás `r`/`s` paraméter;
+- a visszairányítás és az IPN tetszőleges sorrendben érkezik;
+- a vásárló nem tér vissza az oldaladra.
diff --git a/examples/hu/konfiguracio.md b/examples/hu/konfiguracio.md
index afdcde0..35dda24 100644
--- a/examples/hu/konfiguracio.md
+++ b/examples/hu/konfiguracio.md
@@ -21,6 +21,8 @@ SIMPLEPAY_EUR_SECRET_KEY=your_eur_secret_key
SIMPLEPAY_BACK_URL=https://yourdomain.com/payment/back
SIMPLEPAY_LOG_CHANNEL=simplepay
SIMPLEPAY_TIMEOUT=600
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID=your_google_pay_merchant_id
+SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME="Your Shop"
```
## Konfigurációs struktúra
@@ -111,6 +113,20 @@ return [
*/
'timeout' => env('SIMPLEPAY_TIMEOUT', 600),
+ /*
+ |--------------------------------------------------------------------------
+ | Google Pay
+ |--------------------------------------------------------------------------
+ |
+ | A Google Pay kereskedői azonosító és név a saját fizetési oldalon
+ | megjelenő Google Pay gombhoz.
+ |
+ */
+ 'google_pay' => [
+ 'merchant_id' => env('SIMPLEPAY_GOOGLE_PAY_MERCHANT_ID'),
+ 'merchant_name' => env('SIMPLEPAY_GOOGLE_PAY_MERCHANT_NAME'),
+ ],
+
/*
|--------------------------------------------------------------------------
| Auto challenge (3DS)
@@ -197,3 +213,7 @@ Az `auto_challenge` beállítás szabályozza, hogy a package automatikusan kér
```env
SIMPLEPAY_AUTO_CHALLENGE=true
```
+
+## Google Pay
+
+A `google_pay.merchant_id` és a `google_pay.merchant_name` értéke a Google Pay & Wallet Console-ból származik. Csak akkor kell, ha a Google Pay gomb a saját fizetési oldaladon jelenik meg; lásd [Google Pay a saját fizetési oldalon](google-pay.md).
diff --git a/examples/hu/logok-es-asetek.md b/examples/hu/logok-es-asetek.md
index d3a7fc7..b8ec0e7 100644
--- a/examples/hu/logok-es-asetek.md
+++ b/examples/hu/logok-es-asetek.md
@@ -22,6 +22,7 @@ public/vendor/simplepay/
│ ├── simplepay_horizontal_01.png # Fekvő logó v1
│ ├── simplepay_horizontal_02.png # Fekvő logó v2
│ ├── simplepay_vertical.png # Álló logó
+│ ├── simplepay_vertical.svg # Álló logó (SVG)
│ ├── simplepay_top.png # Felül elhelyezett logó
│ ├── simplepay_*-white.png # Fehér változatok (sötét háttérhez)
│ ├── visa_logo_color.png # Visa (színes)
diff --git a/examples/hu/rtp.md b/examples/hu/rtp.md
index 4c5b0de..33f3866 100644
--- a/examples/hu/rtp.md
+++ b/examples/hu/rtp.md
@@ -23,8 +23,35 @@ $response = SimplePay::rtp()->start(new RtpStartRequest(
// Válasz adatok
$response->paymentUrl; // URL a fizetési oldalhoz
$response->transactionId; // SimplePay tranzakció azonosító
+$response->timeout; // null: az RTP indítás válaszában nincs timeout
```
+### Eseményenkénti visszairányítási címek
+
+A Request to Pay a kártyás események mellé egy `pending` eseményt is ad: a vásárló már megadta a bankszámlaszámát, de az utalás még nem érkezett meg. Ha az `urls` szerepel a kérésben, a SimplePay figyelmen kívül hagyja az `url`-t.
+
+```php
+use Netipar\SimplePay\Dto\RedirectUrls;
+
+$response = SimplePay::rtp()->start(new RtpStartRequest(
+ currency: Currency::HUF,
+ total: 5000,
+ orderRef: 'RTP-ORDER-002',
+ customerEmail: 'vasarlo@example.com',
+ url: route('simplepay.back'),
+ customer: 'Teszt Vásárló',
+ urls: new RedirectUrls(
+ success: route('payment.success'),
+ fail: route('payment.failed'),
+ cancel: route('checkout'),
+ timeout: route('payment.timeout'),
+ pending: route('payment.pending'),
+ ),
+));
+```
+
+A csomag `simplepay.back` route-ján a pending visszairányítás `BackEvent::Pending` eseményként érkezik.
+
## Kötegelt RTP
Több fizetési kérés egyidejűleg történő küldése:
diff --git a/examples/hu/visszaterites.md b/examples/hu/visszaterites.md
index 96f9746..d36e05a 100644
--- a/examples/hu/visszaterites.md
+++ b/examples/hu/visszaterites.md
@@ -37,6 +37,7 @@ class SimplePayBackResponse implements BackUrlResponse
BackEvent::Fail => redirect()->route('payment.failed'),
BackEvent::Cancel => redirect()->route('payment.cancelled'),
BackEvent::Timeout => redirect()->route('payment.timeout'),
+ BackEvent::Pending => redirect()->route('payment.pending'),
};
}
}
@@ -66,7 +67,7 @@ class AppServiceProvider extends ServiceProvider
A `BackResponse` objektum az alábbi tulajdonságokat tartalmazza:
```php
-$back->event; // BackEvent enum (Success, Fail, Cancel, Timeout)
+$back->event; // BackEvent enum (Success, Fail, Cancel, Timeout, Pending)
$back->transactionId; // SimplePay tranzakció azonosító (int)
$back->orderRef; // Az eredeti megrendelési hivatkozás (string)
$back->merchant; // Kereskedői azonosító (string)
@@ -81,6 +82,9 @@ $back->total; // A fizetés összege (float)
| `BackEvent::Fail` | A fizetés sikertelen volt (pl. elutasított kártya) |
| `BackEvent::Cancel` | A vásárló megszakította a fizetést |
| `BackEvent::Timeout` | A fizetés időtúllépésbe futott, a vásárló nem fejezte be |
+| `BackEvent::Pending` | Csak Request to Pay: a bankszámlaszám megvan, az utalás még nem érkezett meg |
+
+A `$back->event`-re írt `match`-nek a `BackEvent::Pending` esetet is kezelnie kell, különben egy Request to Pay visszairányítás `UnhandledMatchError`-t dob.
## Példa: összetett kezelés
@@ -102,6 +106,7 @@ class SimplePayBackResponse implements BackUrlResponse
BackEvent::Fail => $this->handleFailure($back),
BackEvent::Cancel => $this->handleCancel($back),
BackEvent::Timeout => $this->handleTimeout($back),
+ BackEvent::Pending => response()->view('payment.pending', ['orderRef' => $back->orderRef]),
};
}
diff --git a/official-docs/en/SimplePay_GooglePay_EN.pdf b/official-docs/en/SimplePay_GooglePay_EN.pdf
new file mode 100644
index 0000000..e1140c8
Binary files /dev/null and b/official-docs/en/SimplePay_GooglePay_EN.pdf differ
diff --git a/official-docs/en/SimplePay_v21_Developer_Documentation_EN.pdf b/official-docs/en/SimplePay_v21_Developer_Documentation_EN.pdf
index 5c94fe6..649d907 100644
Binary files a/official-docs/en/SimplePay_v21_Developer_Documentation_EN.pdf and b/official-docs/en/SimplePay_v21_Developer_Documentation_EN.pdf differ
diff --git a/official-docs/hu/SimplePay_GooglePay_HU.pdf b/official-docs/hu/SimplePay_GooglePay_HU.pdf
new file mode 100644
index 0000000..8957073
Binary files /dev/null and b/official-docs/hu/SimplePay_GooglePay_HU.pdf differ
diff --git a/official-docs/hu/SimplePay_v21_Fejlesztoi_Dokumentacio_HU.pdf b/official-docs/hu/SimplePay_v21_Fejlesztoi_Dokumentacio_HU.pdf
index e421c3c..514cbb7 100644
Binary files a/official-docs/hu/SimplePay_v21_Fejlesztoi_Dokumentacio_HU.pdf and b/official-docs/hu/SimplePay_v21_Fejlesztoi_Dokumentacio_HU.pdf differ
diff --git a/public/logos/simplepay_vertical.svg b/public/logos/simplepay_vertical.svg
new file mode 100644
index 0000000..7feb370
--- /dev/null
+++ b/public/logos/simplepay_vertical.svg
@@ -0,0 +1,211 @@
+
+
+
diff --git a/resources/boost/skills/simplepay-development/SKILL.md b/resources/boost/skills/simplepay-development/SKILL.md
index ea3bf43..0a217ff 100644
--- a/resources/boost/skills/simplepay-development/SKILL.md
+++ b/resources/boost/skills/simplepay-development/SKILL.md
@@ -19,6 +19,8 @@ Use this skill when:
- 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
@@ -164,19 +166,21 @@ class HandleSuccessfulPayment
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(BackResponse $response): mixed
+ public function toResponse(Request $request, BackResponse $back): Response
{
- // $response->event (BackEvent enum: SUCCESS, FAIL, CANCEL, TIMEOUT)
- // $response->transactionId
- // $response->orderRef
+ // $back->event (BackEvent enum: SUCCESS, FAIL, CANCEL, TIMEOUT, PENDING for Request to Pay)
+ // $back->transactionId
+ // $back->orderRef
return redirect()->route('payment.result', [
- 'status' => $response->event->value,
+ 'status' => $back->event->value,
]);
}
}
@@ -280,32 +284,38 @@ 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 RTP session
-$request = new RtpStartRequest(
+// Start a payment request; the customer approves it in their bank
+$response = SimplePay::rtp()->start(new RtpStartRequest(
currency: Currency::HUF,
- customerEmail: 'customer@example.com',
+ total: 5000,
orderRef: 'ORDER-005',
- items: [new Item(title: 'Product', price: 5000, quantity: 1)],
- url: route('payment.back'),
-);
-$response = SimplePay::rtp()->start($request);
+ customerEmail: 'customer@example.com',
+ 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
-// Execute RTP payment
-$doRequest = new RtpDoRequest(
+// Batch payment requests without the customer being present (1–1000 per call)
+$result = SimplePay::rtp()->do(new RtpDoRequest(
currency: Currency::HUF,
- transactionId: $response->transactionId,
payments: [
- new RtpPayment(
- bankCode: 'BANK_CODE',
- accountNumber: 'HU12345678901234567890123456',
- ),
+ new RtpPayment(orderRef: 'ORDER-006', total: 1000, customerEmail: 'a@example.com', customer: 'Customer A'),
+ new RtpPayment(orderRef: 'ORDER-007', total: 2000, customerEmail: 'b@example.com', customer: 'Customer B'),
],
-);
-$result = SimplePay::rtp()->do($doRequest);
+));
// Query/refund/reverse RTP
SimplePay::rtp()->query(Currency::HUF, transactionId: '12345');
@@ -313,6 +323,46 @@ SimplePay::rtp()->refund(Currency::HUF, transactionId: '12345', amount: 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:
@@ -320,7 +370,7 @@ 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`
+- `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
@@ -336,6 +386,7 @@ try {
$e->getErrorCodes(); // [5083]
$e->hasErrorCode(5083); // true
$e->getResolvedCodes(); // [ErrorCode::TokenTimesRequired]
+ $e->getResponseData(); // full API response, e.g. redirectUrl of a 3DS challenge (3004)
}
```
@@ -353,11 +404,13 @@ 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`
+Publish config with: `php artisan vendor:publish --tag=simplepay-config` (or `--tag=simplepay` for config and logos).
## Logo and asset publishing
@@ -419,7 +472,9 @@ All DTOs use readonly properties. Request DTOs include a `toArray()` method that
| DTO | Purpose |
|---|---|
| `PaymentRequest` | Start a payment with items, addresses, methods |
-| `PaymentResponse` | Transaction ID, payment URL, timeout |
+| `PaymentResponse` | Transaction ID, payment URL, timeout (null for RTP starts) |
+| `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 |
diff --git a/src/Dto/GooglePayDoRequest.php b/src/Dto/GooglePayDoRequest.php
new file mode 100644
index 0000000..47fc1fa
--- /dev/null
+++ b/src/Dto/GooglePayDoRequest.php
@@ -0,0 +1,58 @@
+
+ *
+ * @throws RandomException
+ */
+ public function toArray(): array
+ {
+ return array_filter([
+ 'salt' => bin2hex(random_bytes(16)),
+ 'merchant' => '',
+ 'orderRef' => $this->orderRef,
+ 'currency' => $this->currency->value,
+ 'total' => $this->total,
+ 'customerEmail' => $this->customerEmail,
+ 'language' => $this->language,
+ 'googlePayToken' => $this->googlePayToken,
+ 'type' => $this->type->value,
+ 'threeDSReqAuthMethod' => $this->threeDSReqAuthMethod,
+ 'invoice' => $this->invoice?->toArray(),
+ 'customer' => $this->customer,
+ 'url' => $this->url,
+ 'timeout' => date('c', time() + ($this->timeout ?? (int) config('simplepay.timeout', 600))),
+ 'sdkVersion' => 'SimplePay_PHP_SDK_2.1_Laravel',
+ ], fn (mixed $value): bool => $value !== null);
+ }
+}
diff --git a/src/Dto/GooglePayResponse.php b/src/Dto/GooglePayResponse.php
new file mode 100644
index 0000000..49adb47
--- /dev/null
+++ b/src/Dto/GooglePayResponse.php
@@ -0,0 +1,40 @@
+ $data
+ */
+ public static function fromArray(array $data): self
+ {
+ return new self(
+ transactionId: (int) $data['transactionId'],
+ orderRef: (string) $data['orderRef'],
+ currency: isset($data['currency']) ? Currency::from($data['currency']) : null,
+ total: isset($data['total']) ? (float) $data['total'] : null,
+ redirectUrl: isset($data['redirectUrl']) ? (string) $data['redirectUrl'] : null,
+ );
+ }
+
+ /**
+ * Whether the card issuer asked for a 3DS challenge: send the customer to $redirectUrl.
+ */
+ public function requiresChallenge(): bool
+ {
+ return $this->redirectUrl !== null;
+ }
+}
diff --git a/src/Dto/PaymentRequest.php b/src/Dto/PaymentRequest.php
index e585229..b7b96f9 100644
--- a/src/Dto/PaymentRequest.php
+++ b/src/Dto/PaymentRequest.php
@@ -14,6 +14,7 @@
* @param array $methods
* @param array|null $items
* @param array|null $recurring
+ * @param RedirectUrls|null $urls Event-specific redirect URLs; SimplePay ignores $url when they are sent.
*/
public function __construct(
public Currency $currency,
@@ -36,6 +37,7 @@ public function __construct(
public ?float $shippingPrice = null,
public ?float $discount = null,
public ?string $customer = null,
+ public ?RedirectUrls $urls = null,
) {}
/**
@@ -107,6 +109,10 @@ public function toArray(): array
$data['customer'] = $this->customer;
}
+ if ($this->urls !== null) {
+ $data['urls'] = $this->urls->toArray();
+ }
+
return $data;
}
}
diff --git a/src/Dto/PaymentResponse.php b/src/Dto/PaymentResponse.php
index 9c7f2cd..e8b9e04 100644
--- a/src/Dto/PaymentResponse.php
+++ b/src/Dto/PaymentResponse.php
@@ -6,10 +6,13 @@
readonly class PaymentResponse
{
+ /**
+ * @param string|null $timeout Missing from Request to Pay start responses.
+ */
public function __construct(
public int $transactionId,
public string $paymentUrl,
- public string $timeout,
+ public ?string $timeout = null,
) {}
/**
@@ -20,7 +23,7 @@ public static function fromArray(array $data): self
return new self(
transactionId: (int) $data['transactionId'],
paymentUrl: $data['paymentUrl'],
- timeout: $data['timeout'],
+ timeout: $data['timeout'] ?? null,
);
}
}
diff --git a/src/Dto/RedirectUrls.php b/src/Dto/RedirectUrls.php
new file mode 100644
index 0000000..23416a2
--- /dev/null
+++ b/src/Dto/RedirectUrls.php
@@ -0,0 +1,39 @@
+
+ */
+ public function toArray(): array
+ {
+ $urls = [
+ 'success' => $this->success,
+ 'fail' => $this->fail,
+ 'cancel' => $this->cancel,
+ 'timeout' => $this->timeout,
+ ];
+
+ if ($this->pending !== null) {
+ $urls['pending'] = $this->pending;
+ }
+
+ return $urls;
+ }
+}
diff --git a/src/Dto/RtpStartRequest.php b/src/Dto/RtpStartRequest.php
index 4fcc6f4..c95e20d 100644
--- a/src/Dto/RtpStartRequest.php
+++ b/src/Dto/RtpStartRequest.php
@@ -11,6 +11,7 @@
{
/**
* @param array|null $items
+ * @param RedirectUrls|null $urls Event-specific redirect URLs, including `pending`; SimplePay ignores $url when they are sent.
*/
public function __construct(
public Currency $currency,
@@ -22,6 +23,7 @@ public function __construct(
public ?Address $invoice = null,
public ?array $items = null,
public ?int $timeout = null,
+ public ?RedirectUrls $urls = null,
) {}
/**
@@ -52,6 +54,10 @@ public function toArray(): array
$data['items'] = array_map(fn (Item $item) => $item->toArray(), $this->items);
}
+ if ($this->urls !== null) {
+ $data['urls'] = $this->urls->toArray();
+ }
+
return $data;
}
}
diff --git a/src/Enums/BackEvent.php b/src/Enums/BackEvent.php
index e5658ee..329fac5 100644
--- a/src/Enums/BackEvent.php
+++ b/src/Enums/BackEvent.php
@@ -10,4 +10,9 @@ enum BackEvent: string
case Fail = 'FAIL';
case Cancel = 'CANCEL';
case Timeout = 'TIMEOUT';
+
+ /**
+ * Request to Pay only: the customer gave the bank account number, the transfer has not arrived yet.
+ */
+ case Pending = 'PENDING';
}
diff --git a/src/Services/PaymentService.php b/src/Services/PaymentService.php
index 6e874b3..eb2d594 100644
--- a/src/Services/PaymentService.php
+++ b/src/Services/PaymentService.php
@@ -11,6 +11,8 @@
use Netipar\SimplePay\Dto\EamStartRequest;
use Netipar\SimplePay\Dto\FinishRequest;
use Netipar\SimplePay\Dto\FinishResponse;
+use Netipar\SimplePay\Dto\GooglePayDoRequest;
+use Netipar\SimplePay\Dto\GooglePayResponse;
use Netipar\SimplePay\Dto\IpnMessage;
use Netipar\SimplePay\Dto\PaymentRequest;
use Netipar\SimplePay\Dto\PaymentResponse;
@@ -18,6 +20,8 @@
use Netipar\SimplePay\Dto\RefundRequest;
use Netipar\SimplePay\Dto\RefundResponse;
use Netipar\SimplePay\Enums\Currency;
+use Netipar\SimplePay\Enums\ErrorCode;
+use Netipar\SimplePay\Exceptions\SimplePayApiException;
use Netipar\SimplePay\Support\Client;
use Netipar\SimplePay\Support\MerchantResolver;
use Netipar\SimplePay\Support\Signature;
@@ -97,6 +101,59 @@ public function doApplePay(ApplePayDoRequest $request): array
return $this->client->post('/v2/doapplepay', $payload, $secretKey);
}
+ /**
+ * Charge a Google Pay token on the merchant's own checkout page.
+ *
+ * A 3DS challenge (error code 3004) is a normal outcome here: the response
+ * carries the redirectUrl where the customer has to complete it.
+ *
+ * @throws RandomException
+ * @throws ConnectionException
+ */
+ public function doGooglePay(GooglePayDoRequest $request): GooglePayResponse
+ {
+ $merchant = $this->merchantResolver->getMerchant($request->currency);
+ $secretKey = $this->merchantResolver->getSecretKey($request->currency);
+
+ $payload = $request->toArray();
+ $payload['merchant'] = $merchant;
+
+ try {
+ $response = $this->client->post('/v2/dogooglepay', $payload, $secretKey);
+ } catch (SimplePayApiException $exception) {
+ $response = $exception->getResponseData();
+
+ $isChallenge = $exception->hasErrorCode(ErrorCode::ThreeDsChallengeRedirect->value)
+ && is_string($response['redirectUrl'] ?? null)
+ && isset($response['transactionId'], $response['orderRef']);
+
+ if (! $isChallenge) {
+ throw $exception;
+ }
+ }
+
+ return GooglePayResponse::fromArray($response);
+ }
+
+ /**
+ * The values the Google Pay button needs on the checkout page.
+ *
+ * @return array{gpayMerchantId: mixed, gpayMerchantName: mixed, gpayEnvironment: string, gateway: string, gatewayMerchantId: string, paymentType: string, total: string, currency: string}
+ */
+ public function googlePayConfig(Currency $currency, float $total): array
+ {
+ return [
+ 'gpayMerchantId' => config('simplepay.google_pay.merchant_id'),
+ 'gpayMerchantName' => config('simplepay.google_pay.merchant_name'),
+ 'gpayEnvironment' => $this->client->isSandbox() ? 'TEST' : 'PRODUCTION',
+ 'gateway' => 'simplepay',
+ 'gatewayMerchantId' => $this->merchantResolver->getMerchant($currency),
+ 'paymentType' => 'CARD',
+ 'total' => rtrim(rtrim(number_format($total, 2, '.', ''), '0'), '.'),
+ 'currency' => $currency->value,
+ ];
+ }
+
public function handleBack(string $r, string $s): BackResponse
{
return new BackResponse($r, $s);
diff --git a/src/Support/Client.php b/src/Support/Client.php
index 9ec0df8..2906f40 100644
--- a/src/Support/Client.php
+++ b/src/Support/Client.php
@@ -18,6 +18,7 @@ class Client
* @return array
*
* @throws ConnectionException
+ * @throws SimplePayApiException
*/
public function post(string $endpoint, array $data, string $merchantKey): array
{
@@ -45,6 +46,7 @@ public function post(string $endpoint, array $data, string $merchantKey): array
* @return array
*
* @throws ConnectionException
+ * @throws SimplePayApiException
*/
public function postSecurePay(string $endpoint, array $data, string $merchantKey): array
{
diff --git a/tests/Pest.php b/tests/Pest.php
index 28b3657..ff0a2fe 100644
--- a/tests/Pest.php
+++ b/tests/Pest.php
@@ -2,6 +2,22 @@
declare(strict_types=1);
+use GuzzleHttp\Promise\PromiseInterface;
+use Illuminate\Support\Facades\Http;
+use Netipar\SimplePay\Support\Signature;
use Netipar\SimplePay\Tests\TestCase;
uses(TestCase::class)->in(__DIR__);
+
+/**
+ * @param array $data
+ */
+function fakeSimplePayResponse(array $data, string $secretKey): PromiseInterface
+{
+ $json = json_encode($data);
+ $signature = Signature::generate($secretKey, $json);
+
+ return Http::response($data, 200, [
+ 'Signature' => $signature,
+ ]);
+}
diff --git a/tests/Unit/BackResponseTest.php b/tests/Unit/BackResponseTest.php
index f6fe24f..47f6cb8 100644
--- a/tests/Unit/BackResponseTest.php
+++ b/tests/Unit/BackResponseTest.php
@@ -25,6 +25,20 @@
expect($response->merchant)->toBe('TEST_HUF_MERCHANT');
});
+it('parses a pending back response of a Request to Pay', function () {
+ $data = json_encode([
+ 'e' => 'PENDING',
+ 't' => 502272810,
+ 'o' => 'RTP-001',
+ 'm' => 'TEST_HUF_MERCHANT',
+ ]);
+
+ $response = new BackResponse(base64_encode($data), Signature::generate('TEST_HUF_SECRET_KEY', $data));
+
+ expect($response->event)->toBe(BackEvent::Pending)
+ ->and($response->transactionId)->toBe(502272810);
+});
+
it('throws on invalid base64', function () {
new BackResponse('not-valid-base64!!!', 'fake-signature');
})->throws(RuntimeException::class);
diff --git a/tests/Unit/GooglePayTest.php b/tests/Unit/GooglePayTest.php
new file mode 100644
index 0000000..4481b9f
--- /dev/null
+++ b/tests/Unit/GooglePayTest.php
@@ -0,0 +1,149 @@
+ fakeSimplePayResponse([
+ 'transactionId' => 10312754,
+ 'orderRef' => 'ORD-GPAY-001',
+ 'currency' => 'HUF',
+ 'total' => 1500.0,
+ 'merchant' => 'TEST_HUF_MERCHANT',
+ ], 'TEST_HUF_SECRET_KEY'),
+ ]);
+
+ $response = app(PaymentService::class)->doGooglePay(googlePayRequest());
+
+ expect($response->transactionId)->toBe(10312754)
+ ->and($response->orderRef)->toBe('ORD-GPAY-001')
+ ->and($response->currency)->toBe(Currency::HUF)
+ ->and($response->total)->toBe(1500.0)
+ ->and($response->requiresChallenge())->toBeFalse()
+ ->and($response->redirectUrl)->toBeNull();
+
+ Http::assertSent(function ($request) {
+ $body = json_decode($request->body(), true);
+
+ return str_ends_with($request->url(), '/payment/v2/dogooglepay')
+ && $body['merchant'] === 'TEST_HUF_MERCHANT'
+ && $body['googlePayToken'] === base64_encode('{"signature":"test"}')
+ && $body['currency'] === 'HUF'
+ && (float) $body['total'] === 1500.0
+ && $body['orderRef'] === 'ORD-GPAY-001'
+ && $body['customerEmail'] === 'test@example.com'
+ && $body['language'] === 'HU'
+ && $body['type'] === 'CIT'
+ && $body['threeDSReqAuthMethod'] === '02'
+ && $body['url'] === 'https://example.com/simplepay/back'
+ && $body['invoice']['city'] === 'Budapest'
+ && $body['sdkVersion'] === 'SimplePay_PHP_SDK_2.1_Laravel'
+ && isset($body['salt'], $body['timeout']);
+ });
+});
+
+it('returns the 3DS challenge redirect instead of throwing', function () {
+ Http::fake([
+ 'sandbox.simplepay.hu/payment/v2/dogooglepay' => fakeSimplePayResponse([
+ 'errorCodes' => [3004],
+ 'transactionId' => 10312754,
+ 'orderRef' => 'ORD-GPAY-001',
+ 'currency' => 'HUF',
+ 'total' => 1500.0,
+ 'redirectUrl' => 'https://sb-checkout.simplepay.hu/pay/pay/pspHU/challenge',
+ ], 'TEST_HUF_SECRET_KEY'),
+ ]);
+
+ $response = app(PaymentService::class)->doGooglePay(googlePayRequest());
+
+ expect($response->requiresChallenge())->toBeTrue()
+ ->and($response->redirectUrl)->toBe('https://sb-checkout.simplepay.hu/pay/pay/pspHU/challenge')
+ ->and($response->transactionId)->toBe(10312754);
+});
+
+it('throws when a 3004 response has no redirect url', function () {
+ Http::fake([
+ 'sandbox.simplepay.hu/payment/v2/dogooglepay' => fakeSimplePayResponse([
+ 'errorCodes' => [3004],
+ 'transactionId' => 10312754,
+ 'orderRef' => 'ORD-GPAY-001',
+ ], 'TEST_HUF_SECRET_KEY'),
+ ]);
+
+ app(PaymentService::class)->doGooglePay(googlePayRequest());
+})->throws(SimplePayApiException::class);
+
+it('throws when a 3004 response has no transaction id', function () {
+ Http::fake([
+ 'sandbox.simplepay.hu/payment/v2/dogooglepay' => fakeSimplePayResponse([
+ 'errorCodes' => [3004],
+ 'redirectUrl' => 'https://sb-checkout.simplepay.hu/pay/pay/pspHU/challenge',
+ ], 'TEST_HUF_SECRET_KEY'),
+ ]);
+
+ app(PaymentService::class)->doGooglePay(googlePayRequest());
+})->throws(SimplePayApiException::class);
+
+it('throws for other error codes', function () {
+ Http::fake([
+ 'sandbox.simplepay.hu/payment/v2/dogooglepay' => fakeSimplePayResponse([
+ 'errorCodes' => [5321],
+ 'orderRef' => 'ORD-GPAY-001',
+ ], 'TEST_HUF_SECRET_KEY'),
+ ]);
+
+ app(PaymentService::class)->doGooglePay(googlePayRequest());
+})->throws(SimplePayApiException::class);
+
+it('builds the Google Pay button config for the sandbox', function () {
+ config()->set('simplepay.google_pay.merchant_id', 'BCR2DN4TEST');
+ config()->set('simplepay.google_pay.merchant_name', 'Test Shop');
+
+ expect(app(PaymentService::class)->googlePayConfig(Currency::EUR, 12.5))->toBe([
+ 'gpayMerchantId' => 'BCR2DN4TEST',
+ 'gpayMerchantName' => 'Test Shop',
+ 'gpayEnvironment' => 'TEST',
+ 'gateway' => 'simplepay',
+ 'gatewayMerchantId' => 'TEST_EUR_MERCHANT',
+ 'paymentType' => 'CARD',
+ 'total' => '12.5',
+ 'currency' => 'EUR',
+ ]);
+});
+
+it('uses the production Google Pay environment outside the sandbox', function () {
+ config()->set('simplepay.sandbox', false);
+
+ $config = app(PaymentService::class)->googlePayConfig(Currency::HUF, 2500);
+
+ expect($config['gpayEnvironment'])->toBe('PRODUCTION')
+ ->and($config['gatewayMerchantId'])->toBe('TEST_HUF_MERCHANT')
+ ->and($config['total'])->toBe('2500');
+});
diff --git a/tests/Unit/PaymentResponseTest.php b/tests/Unit/PaymentResponseTest.php
index 406d673..cdb4cfc 100644
--- a/tests/Unit/PaymentResponseTest.php
+++ b/tests/Unit/PaymentResponseTest.php
@@ -13,3 +13,17 @@
expect($response->transactionId)->toBe(502272810);
});
+
+it('accepts a Request to Pay start response, which has no timeout', function () {
+ $response = PaymentResponse::fromArray([
+ 'orderRef' => 'RTP-001',
+ 'transactionId' => 502272810,
+ 'paymentUrl' => 'https://sandbox.simplepay.hu/pay/rtp/pspHU/abc',
+ 'currency' => 'HUF',
+ 'total' => 25.0,
+ 'merchant' => 'TEST_HUF_MERCHANT',
+ ]);
+
+ expect($response->transactionId)->toBe(502272810)
+ ->and($response->timeout)->toBeNull();
+});
diff --git a/tests/Unit/PaymentServiceTest.php b/tests/Unit/PaymentServiceTest.php
index 4a24f17..78da6a5 100644
--- a/tests/Unit/PaymentServiceTest.php
+++ b/tests/Unit/PaymentServiceTest.php
@@ -2,10 +2,10 @@
declare(strict_types=1);
-use GuzzleHttp\Promise\PromiseInterface;
use Illuminate\Support\Facades\Http;
use Netipar\SimplePay\Dto\FinishRequest;
use Netipar\SimplePay\Dto\PaymentRequest;
+use Netipar\SimplePay\Dto\RedirectUrls;
use Netipar\SimplePay\Dto\RefundRequest;
use Netipar\SimplePay\Enums\Currency;
use Netipar\SimplePay\Enums\PaymentMethod;
@@ -13,16 +13,6 @@
use Netipar\SimplePay\Services\PaymentService;
use Netipar\SimplePay\Support\Signature;
-function fakeSimplePayResponse(array $data, string $secretKey): PromiseInterface
-{
- $json = json_encode($data);
- $signature = Signature::generate($secretKey, $json);
-
- return Http::response($data, 200, [
- 'Signature' => $signature,
- ]);
-}
-
it('sends a start request and returns PaymentResponse', function () {
$secretKey = 'TEST_HUF_SECRET_KEY';
@@ -295,6 +285,31 @@ function fakeSimplePayResponse(array $data, string $secretKey): PromiseInterface
expect($data)->not->toHaveKey('invoice');
expect($data)->not->toHaveKey('delivery');
expect($data)->not->toHaveKey('items');
+ expect($data)->not->toHaveKey('urls');
+});
+
+it('builds PaymentRequest payload with event-specific redirect urls', function () {
+ $request = new PaymentRequest(
+ currency: Currency::HUF,
+ total: 1500.0,
+ orderRef: 'ORD-URLS',
+ customerEmail: 'test@example.com',
+ language: 'HU',
+ url: 'https://example.com/back',
+ urls: new RedirectUrls(
+ success: 'https://example.com/payment/success',
+ fail: 'https://example.com/payment/fail',
+ cancel: 'https://example.com/payment/cancel',
+ timeout: 'https://example.com/payment/timeout',
+ ),
+ );
+
+ expect($request->toArray()['urls'])->toBe([
+ 'success' => 'https://example.com/payment/success',
+ 'fail' => 'https://example.com/payment/fail',
+ 'cancel' => 'https://example.com/payment/cancel',
+ 'timeout' => 'https://example.com/payment/timeout',
+ ]);
});
it('throws SimplePayApiException when API returns errorCodes', function () {
diff --git a/tests/Unit/RtpServiceTest.php b/tests/Unit/RtpServiceTest.php
index 0f5cb5b..d507bdb 100644
--- a/tests/Unit/RtpServiceTest.php
+++ b/tests/Unit/RtpServiceTest.php
@@ -4,6 +4,7 @@
use Illuminate\Support\Facades\Http;
use Netipar\SimplePay\Dto\PaymentResponse;
+use Netipar\SimplePay\Dto\RedirectUrls;
use Netipar\SimplePay\Dto\RtpDoRequest;
use Netipar\SimplePay\Dto\RtpPayment;
use Netipar\SimplePay\Dto\RtpStartRequest;
@@ -66,6 +67,35 @@
});
});
+it('sends event-specific redirect urls with the pending url', function () {
+ $this->rtpService->start(new RtpStartRequest(
+ currency: Currency::HUF,
+ total: 1000.0,
+ orderRef: 'ORDER-URLS',
+ customerEmail: 'test@example.com',
+ url: 'https://example.com/rtp/back',
+ customer: 'Test Customer',
+ urls: new RedirectUrls(
+ success: 'https://example.com/rtp/success',
+ fail: 'https://example.com/rtp/fail',
+ cancel: 'https://example.com/rtp/cancel',
+ timeout: 'https://example.com/rtp/timeout',
+ pending: 'https://example.com/rtp/pending',
+ ),
+ ));
+
+ Http::assertSent(function ($httpRequest) {
+ return str_contains($httpRequest->url(), '/rtp/start')
+ && $httpRequest['urls'] === [
+ 'success' => 'https://example.com/rtp/success',
+ 'fail' => 'https://example.com/rtp/fail',
+ 'cancel' => 'https://example.com/rtp/cancel',
+ 'timeout' => 'https://example.com/rtp/timeout',
+ 'pending' => 'https://example.com/rtp/pending',
+ ];
+ });
+});
+
it('sends do request to /rtp/do with payments array', function () {
$request = new RtpDoRequest(
currency: Currency::HUF,