Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ This repository is a Laravel package. Keep the package focused, idiomatic, and e

- 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, 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). `doGooglePay()` turns a 3004 with a `redirectUrl` into a normal `GooglePayResponse`.
- Request and response payloads are readonly DTOs in `src/Dto`: `toArray()` builds the API body with the field names of the official SDK samples (check `tests/Unit/RequestPayloadTest.php` when adding fields), `fromArray()` maps the response. Deprecated constructor parameters stay until the next major version but are not sent. Enums live in `src/Enums`; `ErrorCode` mirrors the official error code list with Hungarian descriptions.
- `src/Support/Client.php` adds `sdkVersion` (`Client::SDK_VERSION`) to every request that lacks it, as the official SDK does, signs every request with HMAC-SHA384 (`Support/Signature`), masks card data and tokens in the debug log, 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.
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,15 @@ use Netipar\SimplePay\Dto\CardStorageDoRequest;
// Pay with a stored card
$result = SimplePay::cardStorage()->do(new CardStorageDoRequest(
currency: Currency::HUF,
total: 5000,
transactionId: null, // deprecated: not sent, removed in the next major version
cardId: $savedCardId,
orderRef: 'ORDER-2026-002',
total: 5000,
customerEmail: 'customer@example.com',
cardId: $savedCardId,
url: route('simplepay.back'),
cardSecret: $cardSecret,
threeDSReqAuthMethod: '02',
browser: $browserData, // the customer's browser data for the 3DS check
));

// Query stored card
Expand All @@ -247,14 +252,20 @@ SimplePay::cardStorage()->cardCancel(Currency::HUF, $cardId);

### Recurring Payments

The start of a card registration (`recurring` in `PaymentRequest`) returns the recurring tokens in `$response->tokens`; they become active when the IPN reports the successful payment.

```php
use Netipar\SimplePay\Dto\RecurringRequest;
use Netipar\SimplePay\Enums\TransactionType;

$result = SimplePay::cardStorage()->doRecurring(new RecurringRequest(
currency: Currency::HUF,
total: 9900,
transactionId: null, // deprecated: not sent, removed in the next major version
orderRef: 'SUB-2026-003',
total: 9900,
customerEmail: 'customer@example.com',
url: route('simplepay.back'),
type: TransactionType::Mit,
token: $recurringToken,
));
```
Expand All @@ -265,19 +276,23 @@ $result = SimplePay::cardStorage()->doRecurring(new RecurringRequest(
use Netipar\SimplePay\Dto\ApplePayStartRequest;
use Netipar\SimplePay\Dto\ApplePayDoRequest;

// Start Apple Pay session
// Start Apple Pay session (returns the API response with applePaySession).
// Pass `domain` when the checkout does not run on the host of APP_URL.
$session = SimplePay::payment()->startApplePay(new ApplePayStartRequest(
currency: Currency::HUF,
total: 5000,
orderRef: 'ORDER-AP-001',
customerEmail: 'customer@example.com',
url: route('simplepay.back'),
domain: 'shop.example.com', // the FQDN registered for Apple Pay; defaults to the host of app.url
));

// Complete Apple Pay payment
// Complete Apple Pay payment with the token from Apple Pay JS
$result = SimplePay::payment()->doApplePay(new ApplePayDoRequest(
currency: Currency::HUF,
transactionId: $session->transactionId,
applePayToken: $tokenFromJs,
transactionId: $session['transactionId'],
orderRef: 'ORDER-AP-001',
token: $tokenFromJs,
));
```

Expand Down Expand Up @@ -310,6 +325,30 @@ if ($response->requiresChallenge()) {

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).

### Auto Payment (Card Data on Your Page)

PCI-DSS certified merchants can send the card data themselves:

```php
use Netipar\SimplePay\Dto\AutoPaymentRequest;
use Netipar\SimplePay\Dto\CardData;
use Netipar\SimplePay\Enums\TransactionType;

$result = SimplePay::autoPayment()->auto(new AutoPaymentRequest(
currency: Currency::HUF,
total: 2500,
orderRef: 'ORDER-AUTO-001',
customerEmail: 'customer@example.com',
cardData: new CardData(cardNumber: $number, expiry: '0130', cvc: $cvc, cardHolder: 'John Doe'),
type: TransactionType::Cit,
browserData: $browserData, // the customer's browser data for the 3DS check
url: route('simplepay.back'), // where SimplePay returns the customer after a 3DS challenge
threeDSReqAuthMethod: '02',
));
```

See [3DS browser data](examples/en/card-storage.md#3ds-browser-data) for building `BrowserData`.

### EAM / Qvik Payment

```php
Expand All @@ -321,6 +360,7 @@ $response = SimplePay::payment()->startEam(new EamStartRequest(
orderRef: 'ORDER-EAM-001',
customerEmail: 'customer@example.com',
url: route('simplepay.back'),
eamType: 1, // 1 QR, 2 deeplink, 3 NFC, 4-7 combinations
));
```

Expand All @@ -342,8 +382,8 @@ $result = SimplePay::rtp()->start(new RtpStartRequest(
// Query RTP status
$status = SimplePay::rtp()->query(Currency::HUF, transactionId: $id);

// Refund RTP
SimplePay::rtp()->refund(Currency::HUF, $transactionId, 15000);
// Refund RTP (refund total, then the original total)
SimplePay::rtp()->refund(Currency::HUF, $transactionId, 15000, originalTotal: 15000);

// Reverse RTP
SimplePay::rtp()->reverse(Currency::HUF, $transactionId);
Expand Down
41 changes: 35 additions & 6 deletions examples/en/card-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,40 @@ use Netipar\SimplePay\Dto\CardStorageDoRequest;

$result = SimplePay::cardStorage()->do(new CardStorageDoRequest(
currency: Currency::HUF,
transactionId: 123456,
transactionId: null, // deprecated: not sent, removed in the next major version
cardId: 'card-abc-123',
orderRef: 'ORDER-456',
total: 3000,
customerEmail: 'customer@example.com',
url: route('simplepay.back'),
url: route('simplepay.back'), // where SimplePay returns the customer after a 3DS challenge
cardSecret: $cardSecret, // the secret the customer gave when the card was stored
threeDSReqAuthMethod: '02', // 01 guest, 02 registered, 05 third-party login
browser: $browser, // see "3DS Browser Data" below
));
```

### 3DS Browser Data

OneClick and auto payments send the customer's browser data to the card issuer's 3DS check. Collect `navigator.language`, `screen.colorDepth`, `screen.height`, `screen.width` and `new Date().getTimezoneOffset()` on the checkout page, and add the request headers and IP address on the server:

```php
use Netipar\SimplePay\Dto\BrowserData;

$browser = new BrowserData(
accept: (string) $request->header('Accept'),
agent: (string) $request->userAgent(),
language: $request->input('browser.lang'),
javaEnabled: false,
colorDepth: (int) $request->input('browser.color'),
screenHeight: (int) $request->input('browser.height'),
screenWidth: (int) $request->input('browser.width'),
timeZone: (int) $request->input('browser.tz'),
ip: $request->ip(),
);
```

Without browser data the issuer's 3DS check may fail. If the issuer asks for a challenge, the API answers with error code `3004`; send the customer to `$e->getResponseData()['redirectUrl']`.

## Recurring Payment (Merchant-Initiated)

Charge a stored card for a merchant-initiated recurring transaction (e.g., subscriptions, scheduled billing).
Expand All @@ -38,15 +63,18 @@ use Netipar\SimplePay\Dto\RecurringRequest;

$result = SimplePay::cardStorage()->doRecurring(new RecurringRequest(
currency: Currency::HUF,
transactionId: 789012,
transactionId: null, // deprecated: not sent, removed in the next major version
orderRef: 'ORDER-REC-001',
total: 5000,
customerEmail: 'customer@example.com',
url: route('simplepay.back'),
type: TransactionType::Mit,
token: $recurringToken, // required: one of the tokens of the card registration
));
```

A recurring payment without `token` throws an `InvalidArgumentException` before the request is sent.

### Transaction Types

| Type | Description |
Expand Down Expand Up @@ -127,18 +155,19 @@ $response = SimplePay::payment()->start(new PaymentRequest(
items: [new Item(title: 'Monthly plan', price: 5000, quantity: 1)],
));

// 2. Customer completes payment, card is stored...
// 3. IPN webhook receives cardId in the response...
// 2. Store the recurring tokens of the registration; they become active when the IPN reports the successful payment
$recurringToken = $response->tokens[0];

// 4. Monthly recurring charge (merchant-initiated, no customer interaction)
$result = SimplePay::cardStorage()->doRecurring(new RecurringRequest(
currency: Currency::HUF,
transactionId: $originalTransactionId,
transactionId: null,
orderRef: 'SUB-MONTH-02',
total: 5000,
customerEmail: 'customer@example.com',
url: route('simplepay.back'),
type: TransactionType::Mit,
token: $recurringToken,
));

// 5. When subscription is cancelled, remove the stored card
Expand Down
14 changes: 9 additions & 5 deletions examples/en/rtp.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Request to Pay (RTP) is a payment initiation method where the merchant sends a p

## Start an RTP Payment

Initiate a single Request to Pay transaction.
Initiate a single Request to Pay transaction. The request expires at `deadline` (ISO 8601); without it, 2 hours 5 minutes after the start, as in the official SDK sample, or `timeout` seconds later if you pass `timeout`.

```php
use Netipar\SimplePay\Facades\SimplePay;
Expand Down Expand Up @@ -53,7 +53,7 @@ On the package's `simplepay.back` route the pending redirect arrives as `BackEve

## Batch RTP

Send multiple payment requests at once using the batch RTP method.
Send multiple payment requests at once using the batch RTP method. The batch is sent as a package: pass `packageRef` to identify it in your system (it is generated otherwise), and `deadline` per payment (30 days from now by default).

```php
use Netipar\SimplePay\Facades\SimplePay;
Expand Down Expand Up @@ -95,6 +95,10 @@ use Netipar\SimplePay\Facades\SimplePay;
use Netipar\SimplePay\Enums\Currency;

$query = SimplePay::rtp()->query(Currency::HUF, transactionId: '12345');

// By your order reference, or by the package reference of a batch
$query = SimplePay::rtp()->query(Currency::HUF, orderRef: 'RTP-ORDER-001');
$query = SimplePay::rtp()->query(Currency::HUF, packageRef: 'PKG-2026-10');
```

## Refund an RTP Transaction
Expand All @@ -105,11 +109,11 @@ Issue a refund for a completed RTP payment.
use Netipar\SimplePay\Facades\SimplePay;
use Netipar\SimplePay\Enums\Currency;

// Partial refund
SimplePay::rtp()->refund(Currency::HUF, '12345', 1000);
// Partial refund: the refund total, then the original total of the transaction
SimplePay::rtp()->refund(Currency::HUF, '12345', 1000, originalTotal: 5000);

// Full refund
SimplePay::rtp()->refund(Currency::HUF, '12345', 5000);
SimplePay::rtp()->refund(Currency::HUF, '12345', 5000, originalTotal: 5000);
```

## Reverse an RTP Transaction
Expand Down
36 changes: 32 additions & 4 deletions examples/hu/kartya-tarolas.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,40 @@ use Netipar\SimplePay\Dto\CardStorageDoRequest;

$result = SimplePay::cardStorage()->do(new CardStorageDoRequest(
currency: Currency::HUF,
transactionId: 123456,
transactionId: null, // deprecated: not sent, removed in the next major version
cardId: 'card-abc-123',
orderRef: 'ORDER-456',
total: 3000,
customerEmail: 'vasarlo@example.com',
url: route('simplepay.back'),
url: route('simplepay.back'), // where SimplePay returns the customer after a 3DS challenge
cardSecret: $cardSecret, // the secret the customer gave when the card was stored
threeDSReqAuthMethod: '02', // 01 guest, 02 registered, 05 third-party login
browser: $browser, // see the "Böngészőadatok a 3DS-hez" section below
));
```

### Böngészőadatok a 3DS-hez

A OneClick és az AutoPayment fizetés a vásárló böngészőadatait is elküldi a kártyakibocsátó 3DS-ellenőrzéséhez. A fizetési oldalon gyűjtsd be a `navigator.language`, a `screen.colorDepth`, a `screen.height`, a `screen.width` és a `new Date().getTimezoneOffset()` értékét, a szerveren pedig add hozzá a kérés fejléceit és az IP-címet:

```php
use Netipar\SimplePay\Dto\BrowserData;

$browser = new BrowserData(
accept: (string) $request->header('Accept'),
agent: (string) $request->userAgent(),
language: $request->input('browser.lang'),
javaEnabled: false,
colorDepth: (int) $request->input('browser.color'),
screenHeight: (int) $request->input('browser.height'),
screenWidth: (int) $request->input('browser.width'),
timeZone: (int) $request->input('browser.tz'),
ip: $request->ip(),
);
```

Böngészőadatok nélkül a 3DS-ellenőrzés hibával zárulhat. Ha a kibocsátó challenge-et kér, az API `3004`-es hibakóddal válaszol; ilyenkor a vásárlót a `$e->getResponseData()['redirectUrl']` címre kell irányítani.

## Ismétlődő fizetés (recurring)

Az ismétlődő fizetés (MIT - Merchant Initiated Transaction) lehetővé teszi, hogy a kereskedői oldalról indítsunk fizetést a vásárló közvetlen közreműködése nélkül:
Expand All @@ -32,15 +57,18 @@ use Netipar\SimplePay\Enums\TransactionType;

$result = SimplePay::cardStorage()->doRecurring(new RecurringRequest(
currency: Currency::HUF,
transactionId: 789012,
transactionId: null, // deprecated: not sent, removed in the next major version
orderRef: 'ORDER-REC-001',
total: 5000,
customerEmail: 'vasarlo@example.com',
url: route('simplepay.back'),
type: TransactionType::Mit,
token: $recurringToken, // required: one of the tokens of the card registration
));
```

A `token` nélküli ismétlődő fizetés még a kérés elküldése előtt `InvalidArgumentException`-t dob.

### Tranzakció típusok

| Típus | Leírás |
Expand Down Expand Up @@ -93,7 +121,7 @@ SimplePay::cardStorage()->tokenCancel(Currency::HUF, 'token-xyz');
A kártyatárolással történő fizetés teljes folyamata a következő:

1. **Első fizetés** - A vásárló fizet és hozzájárul a kártya tárolásához (`recurring` paraméter megadásával a `start()` hívásban).
2. **Kártya tárolása** - A SimplePay tárolja a kártyát, és visszaküldi a `cardId`-t és/vagy `token`-t az IPN-ben.
2. **Kártya tárolása** - A `start()` válasza a regisztráció ismétlődő fizetéshez használható tokenjeit is visszaadja (`$response->tokens`); ezek a sikeres fizetést jelző IPN-nel aktiválódnak.
3. **Következő fizetések** - A tárolt `cardId`-vel vagy `token`-nel indíthatod a következő fizetéseket (`do()` vagy `doRecurring()`).
4. **Kártya törlés** - Amikor a vásárló kérelmezi, törölheted a tárolt kártyát (`cardCancel()`).

Expand Down
12 changes: 8 additions & 4 deletions examples/hu/rtp.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A `SimplePay::rtp()` metódusain keresztül kezelheted a Request to Pay (azonnal

## RTP indítás

Egyedi fizetési kérés indítása:
Egyedi fizetési kérés indítása. A kérés a `deadline` időpontban (ISO 8601) jár le; ha nincs megadva, az indítás után 2 óra 5 perccel, ahogy a hivatalos SDK mintája, vagy `timeout` megadása esetén annyi másodperccel később.

```php
use Netipar\SimplePay\Facades\SimplePay;
Expand Down Expand Up @@ -54,7 +54,7 @@ A csomag `simplepay.back` route-ján a pending visszairányítás `BackEvent::Pe

## Kötegelt RTP

Több fizetési kérés egyidejűleg történő küldése:
Több fizetési kérés egyidejűleg történő küldése. A köteg csomagként megy: a `packageRef` azonosítja a rendszeredben (ha nem adod meg, a csomag generál egyet), a `deadline` pedig fizetési kérésenként adható meg (alapértéke 30 nap).

```php
use Netipar\SimplePay\Dto\RtpDoRequest;
Expand Down Expand Up @@ -91,17 +91,21 @@ Egy RTP tranzakció állapotának lekérdezése:

```php
$query = SimplePay::rtp()->query(Currency::HUF, transactionId: '12345');

// By your order reference, or by the package reference of a batch
$query = SimplePay::rtp()->query(Currency::HUF, orderRef: 'RTP-ORDER-001');
$query = SimplePay::rtp()->query(Currency::HUF, packageRef: 'PKG-2026-10');
```

## RTP visszatérítés (refund)

RTP tranzakció visszatérítése:

```php
SimplePay::rtp()->refund(Currency::HUF, '12345', 1000);
SimplePay::rtp()->refund(Currency::HUF, '12345', 1000, originalTotal: 5000);
```

A harmadik paraméter a visszatérítendő összeg. Részleges visszatérítés is lehetséges.
A harmadik paraméter a visszatérítendő összeg, az `originalTotal` az eredeti tranzakció összege. Részleges visszatérítés is lehetséges.

## RTP sztornó (reverse)

Expand Down
Loading
Loading