aratkruglik / wayforpay-laravel
Native Laravel integration for WayForPay payment gateway.
Requires
- php: ^8.2
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- pestphp/pest-plugin-mutate: ^4.0
README
Native Laravel integration for the WayForPay payment gateway. Built on Illuminate\Http\Client with no external SDK dependencies. Provides strict DTOs, automatic HMAC_MD5 signature handling, and built-in webhook support.
Supports Laravel 11.x-13.x and PHP 8.2+.
Table of Contents
Installation
composer require aratkruglik/wayforpay-laravel
Configuration
Publish the configuration file:
php artisan vendor:publish --tag=wayforpay-config
Add credentials to .env:
WAYFORPAY_MERCHANT_ACCOUNT=your_merchant_login WAYFORPAY_SECRET_KEY=your_secret_key WAYFORPAY_MERCHANT_DOMAIN=your_domain.com
Usage
1. Purchase (Widget)
Generate a self-submitting HTML form that redirects the user to the WayForPay checkout page.
use AratKruglik\WayForPay\Facades\WayForPay; use AratKruglik\WayForPay\Domain\Transaction; use AratKruglik\WayForPay\Domain\Product; use AratKruglik\WayForPay\Domain\Client; $client = new Client( nameFirst: 'John', nameLast: 'Doe', email: 'john@example.com', phone: '+380501234567' ); $transaction = new Transaction( orderReference: 'ORDER_' . time(), amount: 100.50, currency: 'UAH', orderDate: time(), client: $client, paymentSystems: 'card;googlePay;applePay' ); $transaction->addProduct(new Product('T-Shirt', 100.50, 1)); $html = WayForPay::purchase( $transaction, returnUrl: 'https://myshop.com/payment/success', serviceUrl: 'https://myshop.com/api/wayforpay/callback' ); return response($html);
Custom Form Rendering
For SPA or custom frontend integration, use getPurchaseFormData to get raw form fields:
$formData = WayForPay::getPurchaseFormData($transaction, $returnUrl, $serviceUrl); return response()->json([ 'form_action' => 'https://secure.wayforpay.com/pay', 'form_data' => $formData, ]);
Submit the form programmatically on the client side:
const form = document.createElement('form'); form.method = 'POST'; form.action = data.form_action; Object.entries(data.form_data).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach(item => { const input = document.createElement('input'); input.type = 'hidden'; input.name = `${key}[]`; input.value = item; form.appendChild(input); }); } else { const input = document.createElement('input'); input.type = 'hidden'; input.name = key; input.value = value; form.appendChild(input); } }); document.body.appendChild(form); form.submit();
2. Invoices
Generate a payment link to send via email or messenger.
$response = WayForPay::createInvoice($transaction, returnUrl: 'https://myshop.com/success'); $invoiceUrl = $response['invoiceUrl']; WayForPay::removeInvoice('ORDER_123');
3. Direct Charge (Host-to-Host)
Warning: Requires PCI DSS compliance when handling raw card data server-side.
use AratKruglik\WayForPay\Domain\Card; use AratKruglik\WayForPay\Enums\ReasonCode; $card = new Card( cardNumber: '4111111111111111', expMonth: '12', expYear: '25', cvv: '123', holderName: 'JOHN DOE' ); $response = WayForPay::charge($transaction, $card); $code = ReasonCode::tryFrom((int) $response['reasonCode']); if ($code?->isSuccess()) { // Payment successful }
4. Recurring Payments
Create a subscription by passing regular payment parameters during the initial purchase:
$transaction = new Transaction( orderReference: 'SUB_123', amount: 100.00, currency: 'UAH', orderDate: time(), regularMode: 'monthly', regularAmount: 100.00, dateNext: '25.05.2025', dateEnd: '25.05.2026' ); $html = WayForPay::purchase($transaction);
Manage existing subscriptions:
WayForPay::suspendRecurring('SUB_123'); WayForPay::resumeRecurring('SUB_123'); WayForPay::removeRecurring('SUB_123');
5. Refunds
WayForPay::refund('ORDER_123', 50.00, 'UAH', 'Customer return');
6. Holds (Two-Phase Payments)
Settle a previously authorized hold:
WayForPay::settle('ORDER_123', 100.50, 'UAH');
7. P2P Credit (Payouts)
Send funds from the merchant account to a recipient card:
WayForPay::p2pCredit( orderReference: 'PAYOUT_001', amount: 500.00, currency: 'UAH', cardBeneficiary: '4111111111111111' );
8. P2P Account Transfer
Transfer funds to a bank account (UAH only):
use AratKruglik\WayForPay\Domain\AccountTransfer; $transfer = new AccountTransfer( orderReference: 'TRANSFER_001', amount: 1500.00, currency: 'UAH', iban: 'UA213223130000026007233566001', okpo: '12345678', accountName: 'FOP Ivanov I.I.', description: 'Payout for services', serviceUrl: 'https://myshop.com/api/wayforpay/callback', recipientEmail: 'recipient@example.com' ); $response = WayForPay::p2pAccount($transfer);
9. Card Verification
Verify a card by blocking a small amount that is automatically reversed:
$url = WayForPay::verifyCard('VERIFY_ORDER_001'); return redirect($url);
10. Check Status
$status = WayForPay::checkStatus('ORDER_123'); // $status['transactionStatus']
Webhooks
The package handles signature verification automatically.
Option A: Built-in controller with event dispatching
Register the route in routes/api.php:
Route::post('wayforpay/callback', \AratKruglik\WayForPay\Http\Controllers\WebhookController::class);
Listen for the event:
use AratKruglik\WayForPay\Events\WayForPayCallbackReceived; Event::listen(WayForPayCallbackReceived::class, function ($event) { $data = $event->data; if ($data['transactionStatus'] === 'Approved') { // Update order status } });
Option B: Manual handling in a custom controller
use AratKruglik\WayForPay\Services\WayForPayService; use AratKruglik\WayForPay\Exceptions\WayForPayException; public function handle(Request $request, WayForPayService $service) { try { $response = $service->handleWebhook($request->all()); // Process order logic... return response()->json($response); } catch (WayForPayException $e) { return response()->json(['status' => 'error'], 400); } }
Marketplace Integration (MMS API)
The MMS (Merchant Management System) API enables marketplace platforms to programmatically manage sub-merchants and partners, configure compensation (payout) methods, and query balances.
Available via the Mms facade or constructor injection:
use AratKruglik\WayForPay\Facades\Mms; // Facade Mms::addPartner($partner); // Constructor injection use AratKruglik\WayForPay\Contracts\MmsServiceInterface; class PartnerController extends Controller { public function __construct( private readonly MmsServiceInterface $mms ) {} }
Partner Management
Register a new partner
use AratKruglik\WayForPay\Facades\Mms; use AratKruglik\WayForPay\Domain\Partner; use AratKruglik\WayForPay\Domain\CompensationCard; use AratKruglik\WayForPay\Domain\CompensationAccount; // Option 1: Compensation via card $partner = new Partner( partnerCode: 'PARTNER_001', site: 'https://partner-shop.com', phone: '+380501234567', email: 'partner@example.com', description: 'Partner shop description', compensationCard: new CompensationCard( cardNumber: '4111111111111111', expMonth: '12', expYear: '25', cvv: '123', holderName: 'PARTNER NAME' ) ); // Option 2: Compensation via bank account $partner = new Partner( partnerCode: 'PARTNER_002', site: 'https://partner-shop.com', phone: '+380501234567', email: 'partner@example.com', compensationAccount: new CompensationAccount( iban: 'UA213223130000026007233566001', okpo: '12345678', name: 'FOP Partner Name' ) ); // Option 3: Compensation via tokenized card $partner = new Partner( partnerCode: 'PARTNER_003', site: 'https://partner-shop.com', phone: '+380501234567', email: 'partner@example.com', compensationCardToken: 'card_token_from_wayforpay' ); $response = Mms::addPartner($partner);
Query partner info
$info = Mms::partnerInfo('PARTNER_001');
Update partner details
Mms::updatePartner('PARTNER_001', [ 'phone' => '+380509876543', 'email' => 'new-email@example.com', 'compensationCardToken' => 'new_token', ]);
Merchant Management
Register a sub-merchant
use AratKruglik\WayForPay\Facades\Mms; use AratKruglik\WayForPay\Domain\Merchant; use AratKruglik\WayForPay\Domain\CompensationCard; $merchant = new Merchant( site: 'https://sub-merchant.com', phone: '+380501234567', email: 'merchant@example.com', description: 'Sub-merchant description', compensationCard: new CompensationCard( cardNumber: '4111111111111111' ) ); $response = Mms::addMerchant($merchant);
Query merchant info
Requires the sub-merchant's account ID and secret key:
$info = Mms::merchantInfo('sub_merchant_account', 'sub_merchant_secret_key');
Balance and Reporting
use AratKruglik\WayForPay\Facades\Mms; $balance = Mms::merchantBalance(); // With date filter (dd.mm.yyyy format) $balance = Mms::merchantBalance('01.01.2026');
Testing
vendor/bin/pest
License
The MIT License (MIT). See License File for details.