sarfarazstark/laravel-payu

Laravel PayU Payment Gateway Integration Package

v2.0.2 2025-06-01 18:03 UTC

This package is auto-updated.

Last update: 2025-06-01 18:06:03 UTC


README

A comprehensive Laravel package for integrating PayU payment gateway with complete database transaction tracking, webhook handling, and advanced payment management features.

โœจ Features

  • ๐Ÿš€ Complete PayU API Integration - All PayU APIs wrapped in a Laravel-friendly interface
  • ๐Ÿ’พ Database Transaction Tracking - Automatic logging of payments, refunds, and webhooks
  • ๐Ÿ” Enhanced Data Integrity - Database enums for status fields and categorized data
  • ๐ŸŽฏ Eloquent Models - Rich models with relationships and helper methods
  • ๐Ÿงช Comprehensive Testing - 28+ unit tests covering all functionality
  • ๐Ÿ“ Webhook Management - Automatic webhook processing and verification
  • ๐Ÿ›ก๏ธ Security First - Hash verification and secure configuration management
  • ๐Ÿ“Š Analytics Ready - Query scopes and aggregation methods for reporting

๐Ÿ“ฆ Installation

1. Install via Composer

composer require sarfarazstark/laravel-payu

2. Publish Configuration, Migrations & Models

# Publish configuration file
php artisan vendor:publish --tag=payu-config

# Publish migration files
php artisan vendor:publish --tag=payu-migrations

# Publish Eloquent models (optional)
php artisan vendor:publish --tag=payu-models

# Run migrations to create database tables
php artisan migrate

๐Ÿ’ก Note: Publishing models is optional. You can work with the PayU transactions, refunds, and webhooks using the package's built-in models (SarfarazStark\LaravelPayU\Models\*). However, if you want to customize the models or add additional relationships in your application, publish them to your app/Models directory.

3. Working with Published Models

Once you publish the models with php artisan vendor:publish --tag=payu-models, you'll have three Eloquent models in your app/Models directory:

PayUTransaction Model

  • Location: app/Models/PayUTransaction.php
  • Features: Complete transaction tracking with relationships and helper methods
  • Constants: Status and payment mode constants for easy reference

PayURefund Model

  • Location: app/Models/PayURefund.php
  • Features: Refund management with status tracking and relationships
  • Constants: Refund status and type constants

PayUWebhook Model

  • Location: app/Models/PayUWebhook.php
  • Features: Webhook processing and verification with event tracking
  • Constants: Event type and status constants

Example usage with published models:

use App\Models\PayUTransaction;
use App\Models\PayURefund;
use App\Models\PayUWebhook;

// Using published models works exactly the same as package models
$transaction = PayUTransaction::where('txnid', 'TXN123')->first();
$refunds = PayURefund::successful()->get();
$webhooks = PayUWebhook::verified()->recent()->get();

3. Environment Configuration

Add your PayU credentials to .env:

PAYU_KEY=your_payu_merchant_key
PAYU_SALT=your_payu_salt_key
PAYU_ENV_PROD=false
PAYU_SUCCESS_URL=https://your-site.com/payu/success
PAYU_FAILURE_URL=https://your-site.com/payu/failure

โš ๏ธ Important: The PAYU_SUCCESS_URL and PAYU_FAILURE_URL are required. If not set in your .env file, you must pass surl and furl parameters when calling payment methods, otherwise an InvalidArgumentException will be thrown.

๐Ÿ—„๏ธ Database Schema

The package creates three optimized database tables:

PayU Transactions (payu_transactions)

  • Status Enum: pending, success, failure, cancelled, failed
  • Payment Mode Enum: CC, DC, NB, UPI, EMI, WALLET, CASH
  • Complete transaction details with customer information
  • Indexed for performance on status, email, and payment dates

PayU Refunds (payu_refunds)

  • Status Enum: pending, success, failed, cancelled, processing
  • Type Enum: refund, cancel, chargeback
  • Linked to transactions with refund tracking

PayU Webhooks (payu_webhooks)

  • Event Type Enum: 9 different payment/refund event types
  • Status Enum: received, processed, failed, ignored
  • Automatic webhook verification and processing logs

๐Ÿš€ Quick Start Guide

Basic Payment Integration

<?php

use SarfarazStark\LaravelPayU\Facades\PayU;
use SarfarazStark\LaravelPayU\Models\PayUTransaction;

class PaymentController extends Controller
{
    public function initiatePayment(Request $request)
    {
        // Generate unique transaction ID
        $txnid = 'TXN' . time() . rand(1000, 9999);

        // Prepare payment parameters
        $params = [
            'txnid' => $txnid,
            'amount' => $request->amount,
            'productinfo' => $request->product_name,
            'firstname' => $request->first_name,
            'lastname' => $request->last_name,
            'email' => $request->email,
            'phone' => $request->phone,
            'address1' => $request->address,
            'city' => $request->city,
            'state' => $request->state,
            'country' => 'India',
            'zipcode' => $request->zipcode,
            'udf1' => $request->user_id, // Custom field for user tracking
            // Optional: Override default URLs from config
            // 'surl' => route('payment.success'),
            // 'furl' => route('payment.failure'),
        ];

        // Save transaction to database
        PayUTransaction::create([
            'txnid' => $txnid,
            'amount' => $request->amount,
            'productinfo' => $request->product_name,
            'firstname' => $request->first_name,
            'lastname' => $request->last_name,
            'email' => $request->email,
            'phone' => $request->phone,
            'status' => PayUTransaction::STATUS_PENDING,
            'payment_initiated_at' => now(),
        ]);

        // Generate payment form
        $paymentForm = PayU::showPaymentForm($params);

        return view('payment.form', compact('paymentForm'));
    }
}

Payment Response Handling

public function paymentSuccess(Request $request)
{
    // Verify hash for security
    if (!PayU::verifyHash($request->all())) {
        return redirect()->route('payment.failure')
            ->with('error', 'Payment verification failed');
    }

    // Update transaction in database
    $transaction = PayUTransaction::where('txnid', $request->txnid)->first();

    if ($transaction) {
        $transaction->update([
            'status' => PayUTransaction::STATUS_SUCCESS,
            'mihpayid' => $request->mihpayid,
            'payment_mode' => $request->mode,
            'bankcode' => $request->bankcode,
            'bank_ref_num' => $request->bank_ref_num,
            'payment_date' => now(),
            'response_data' => $request->all(),
        ]);

        return view('payment.success', compact('transaction'));
    }

    return redirect()->route('payment.failure');
}

public function paymentFailure(Request $request)
{
    $transaction = PayUTransaction::where('txnid', $request->txnid)->first();

    if ($transaction) {
        $transaction->update([
            'status' => PayUTransaction::STATUS_FAILED,
            'error' => $request->error,
            'error_message' => $request->error_Message,
            'response_data' => $request->all(),
        ]);
    }

    return view('payment.failure', compact('transaction'));
}

๐Ÿ”ง Advanced Usage

Working with Eloquent Models

Transaction Management

use SarfarazStark\LaravelPayU\Models\PayUTransaction;

// Get all successful transactions for a user
$userTransactions = PayUTransaction::successful()
    ->where('email', 'user@example.com')
    ->with('refunds', 'webhooks')
    ->orderBy('created_at', 'desc')
    ->get();

// Check transaction status with helper methods
$transaction = PayUTransaction::find(1);

if ($transaction->isSuccessful()) {
    echo "Payment completed successfully";
} elseif ($transaction->isPending()) {
    echo "Payment is still processing";
} elseif ($transaction->isFailed()) {
    echo "Payment failed: " . $transaction->error_message;
}

// Get transactions by payment mode
$creditCardTransactions = PayUTransaction::where('payment_mode', PayUTransaction::MODE_CC)->get();
$upiTransactions = PayUTransaction::where('payment_mode', PayUTransaction::MODE_UPI)->get();

// Analytics and reporting
$monthlyRevenue = PayUTransaction::successful()
    ->whereMonth('created_at', now()->month)
    ->sum('amount');

$paymentModeStats = PayUTransaction::successful()
    ->selectRaw('payment_mode, COUNT(*) as count, SUM(amount) as total')
    ->groupBy('payment_mode')
    ->get();

Refund Management

use SarfarazStark\LaravelPayU\Models\PayURefund;

// Check if transaction can be refunded
$transaction = PayUTransaction::find(1);

if ($transaction->canBeRefunded()) {
    $maxRefundable = $transaction->getRemainingRefundableAmount();

    // Create refund record
    $refund = PayURefund::create([
        'refund_id' => PayURefund::generateRefundId(),
        'txnid' => $transaction->txnid,
        'amount' => 50.00,
        'status' => PayURefund::STATUS_PENDING,
        'type' => PayURefund::TYPE_REFUND,
        'reason' => 'Customer requested refund',
        'refund_requested_at' => now(),
    ]);

    // Process refund via PayU API
    $refundResponse = PayU::cancelRefundTransaction([
        'txnid' => $transaction->txnid,
        'refund_amount' => 50.00,
        'reason' => 'Customer request'
    ]);

    // Update refund status based on API response
    if ($refundResponse['status'] === 'success') {
        $refund->markAsSuccessful($refundResponse['refund_id']);
    } else {
        $refund->markAsFailed($refundResponse['error']);
    }
}

// Get all refunds for a transaction
$refunds = $transaction->refunds()->successful()->get();
$totalRefunded = $transaction->getTotalRefundedAttribute();

Webhook Handling

use SarfarazStark\LaravelPayU\Models\PayUWebhook;

public function handleWebhook(Request $request)
{
    // Create webhook record
    $webhook = PayUWebhook::create([
        'webhook_id' => PayUWebhook::generateWebhookId(),
        'txnid' => $request->txnid,
        'event_type' => $request->event_type,
        'status' => PayUWebhook::STATUS_RECEIVED,
        'payload' => $request->all(),
        'headers' => $request->headers->all(),
        'received_at' => now(),
    ]);

    try {
        // Verify webhook authenticity
        if ($this->verifyWebhookSignature($request)) {
            $webhook->update(['verified' => true]);

            // Process webhook based on event type
            switch ($webhook->event_type) {
                case PayUWebhook::EVENT_PAYMENT_SUCCESS:
                    $this->handlePaymentSuccess($webhook);
                    break;
                case PayUWebhook::EVENT_REFUND_SUCCESS:
                    $this->handleRefundSuccess($webhook);
                    break;
                // Handle other event types...
            }

            $webhook->markAsProcessed();
        } else {
            $webhook->markAsIgnored('Invalid signature');
        }
    } catch (Exception $e) {
        $webhook->markAsFailed($e->getMessage());
    }

    return response()->json(['status' => 'ok']);
}

// Helper methods for webhook event handling
private function handlePaymentSuccess(PayUWebhook $webhook)
{
    $transaction = $webhook->transaction;

    if ($transaction && $transaction->status === PayUTransaction::STATUS_PENDING) {
        $transaction->update([
            'status' => PayUTransaction::STATUS_SUCCESS,
            'payment_date' => now(),
        ]);

        // Trigger success events, send emails, etc.
        event(new PaymentSuccessEvent($transaction));
    }
}

Using Available Enum Constants

// Transaction Status Constants
PayUTransaction::STATUS_PENDING     // 'pending'
PayUTransaction::STATUS_SUCCESS     // 'success'
PayUTransaction::STATUS_FAILURE     // 'failure'
PayUTransaction::STATUS_CANCELLED   // 'cancelled'
PayUTransaction::STATUS_FAILED      // 'failed'

// Payment Mode Constants
PayUTransaction::MODE_CC           // 'CC' (Credit Card)
PayUTransaction::MODE_DC           // 'DC' (Debit Card)
PayUTransaction::MODE_NB           // 'NB' (Net Banking)
PayUTransaction::MODE_UPI          // 'UPI'
PayUTransaction::MODE_EMI          // 'EMI'
PayUTransaction::MODE_WALLET       // 'WALLET'
PayUTransaction::MODE_CASH         // 'CASH'

// Refund Status Constants
PayURefund::STATUS_PENDING         // 'pending'
PayURefund::STATUS_SUCCESS         // 'success'
PayURefund::STATUS_FAILED          // 'failed'
PayURefund::STATUS_CANCELLED       // 'cancelled'
PayURefund::STATUS_PROCESSING      // 'processing'

// Refund Type Constants
PayURefund::TYPE_REFUND           // 'refund'
PayURefund::TYPE_CANCEL           // 'cancel'
PayURefund::TYPE_CHARGEBACK       // 'chargeback'

// Get all available values
$statuses = PayUTransaction::getStatuses();        // Array of all status options
$paymentModes = PayUTransaction::getPaymentModes(); // Array of all payment modes
$refundTypes = PayURefund::getTypes();             // Array of all refund types

๐Ÿ”Œ Complete PayU API Integration

Payment Operations

// Verify payment status
$verification = PayU::verifyPayment(['txnid' => 'TXN123456']);

// Get transaction details
$details = PayU::getTransaction(['txnid' => 'TXN123456']);

// Check payment status
$status = PayU::checkPayment(['txnid' => 'TXN123456']);

// Get checkout page details
$checkoutDetails = PayU::getCheckoutDetails(['var1' => 'value']);

Refund Operations

// Cancel/Refund transaction
$refund = PayU::cancelRefundTransaction([
    'txnid' => 'TXN123456',
    'refund_amount' => 100.00,
    'reason' => 'Customer request'
]);

// Check refund status
$refundStatus = PayU::checkRefundStatus(['request_id' => 'REF123']);

EMI & Card Operations

// Check EMI eligible bins
$emiBins = PayU::checkEmiEligibleBins(['bin' => '512345']);

// Get EMI amount details
$emiAmount = PayU::getEmiAmount([
    'amount' => 10000,
    'bank' => 'HDFC',
    'tenure' => 6
]);

// Get card bin details
$binDetails = PayU::getBinDetails(['cardnum' => '512345']);

UPI & Banking Operations

// Validate UPI ID
$upiValidation = PayU::validateUpi(['vpa' => 'user@paytm']);

// Check netbanking status
$netbankingStatus = PayU::getNetbankingStatus();

// Get issuing bank status
$bankStatus = PayU::getIssuingBankStatus();

Invoice Operations

// Create payment invoice
$invoice = PayU::createPaymentInvoice([
    'txnid' => 'INV123456',
    'amount' => 1000,
    'email' => 'customer@example.com',
    'phone' => '9999999999',
    'productinfo' => 'Service Invoice',
    'firstname' => 'John Doe'
]);

// Expire payment invoice
$expiry = PayU::expirePaymentInvoice(['txnid' => 'INV123456']);

๐Ÿ“Š Reporting & Analytics

Transaction Analytics

// Daily transaction summary
$dailyStats = PayUTransaction::selectRaw('
    DATE(created_at) as date,
    COUNT(*) as total_transactions,
    SUM(CASE WHEN status = "success" THEN 1 ELSE 0 END) as successful,
    SUM(CASE WHEN status = "success" THEN amount ELSE 0 END) as revenue,
    AVG(CASE WHEN status = "success" THEN amount ELSE NULL END) as avg_amount
')
->groupBy('date')
->orderBy('date', 'desc')
->limit(30)
->get();

// Payment mode performance
$modePerformance = PayUTransaction::selectRaw('
    payment_mode,
    COUNT(*) as transactions,
    SUM(amount) as total_amount,
    AVG(amount) as avg_amount,
    SUM(CASE WHEN status = "success" THEN 1 ELSE 0 END) / COUNT(*) * 100 as success_rate
')
->whereNotNull('payment_mode')
->groupBy('payment_mode')
->get();

// Failed transaction analysis
$failureReasons = PayUTransaction::failed()
    ->selectRaw('error, COUNT(*) as count')
    ->groupBy('error')
    ->orderBy('count', 'desc')
    ->get();

Refund Analytics

// Monthly refund trends
$refundTrends = PayURefund::selectRaw('
    YEAR(created_at) as year,
    MONTH(created_at) as month,
    COUNT(*) as refund_requests,
    SUM(CASE WHEN status = "success" THEN amount ELSE 0 END) as refunded_amount,
    AVG(TIMESTAMPDIFF(HOUR, refund_requested_at, refund_processed_at)) as avg_processing_hours
')
->groupBy('year', 'month')
->orderBy('year', 'desc')
->orderBy('month', 'desc')
->get();

๐Ÿงช Testing

The package includes comprehensive unit tests covering all functionality:

# Run all tests
./vendor/bin/phpunit

# Run with coverage report
./vendor/bin/phpunit --coverage-html coverage

# Run specific test methods
./vendor/bin/phpunit --filter testVerifyPayment

Writing Custom Tests

use SarfarazStark\LaravelPayU\Models\PayUTransaction;
use Tests\TestCase;

class PaymentTest extends TestCase
{
    public function testTransactionCreation()
    {
        $transaction = PayUTransaction::create([
            'txnid' => 'TEST123',
            'amount' => 100.00,
            'status' => PayUTransaction::STATUS_PENDING,
            // ... other fields
        ]);

        $this->assertDatabaseHas('payu_transactions', [
            'txnid' => 'TEST123',
            'status' => PayUTransaction::STATUS_PENDING
        ]);
    }
}

๐Ÿ”’ Security Best Practices

Hash Verification

// Always verify hash in success/failure callbacks
if (!PayU::verifyHash($request->all())) {
    // Handle invalid hash - possible tampering
    return response()->json(['error' => 'Invalid hash'], 400);
}

Environment Configuration

// Use different credentials for staging/production
// .env.staging
PAYU_ENV_PROD=false
PAYU_KEY=test_key
PAYU_SALT=test_salt

// .env.production
PAYU_ENV_PROD=true
PAYU_KEY=live_key
PAYU_SALT=live_salt

Database Security

// Use fillable arrays to prevent mass assignment
// Models already include proper $fillable arrays

// Use enum constraints for data integrity
// Database migrations include enum constraints for status fields

๐Ÿš€ Production Deployment

Performance Optimization

// Add database indexes for better query performance
// Indexes are already included in migrations for:
// - Transaction status and email lookups
// - Webhook event type filtering
// - Payment date range queries

// Use eager loading to prevent N+1 queries
$transactions = PayUTransaction::with(['refunds', 'webhooks'])
    ->successful()
    ->paginate(50);

Monitoring & Logging

// Log webhook processing for debugging
Log::info('PayU webhook received', [
    'webhook_id' => $webhook->webhook_id,
    'event_type' => $webhook->event_type,
    'txnid' => $webhook->txnid
]);

// Monitor failed transactions
$recentFailures = PayUTransaction::failed()
    ->where('created_at', '>=', now()->subHours(24))
    ->count();

if ($recentFailures > 10) {
    // Alert administrators
    Mail::to('admin@yoursite.com')->send(new HighFailureRateAlert($recentFailures));
}

๐Ÿค Contributing

We welcome contributions! Please see our contributing guidelines for details.

Development Setup

# Clone the repository
git clone https://github.com/sarfarazstark/laravel-payu.git

# Install dependencies
composer install

# Run tests
./vendor/bin/phpunit

# Check code style
./vendor/bin/php-cs-fixer fix --dry-run

๐Ÿ“š API Documentation

For detailed API documentation and parameter requirements, see the doc/ folder:

๐Ÿ“„ License

This package is open-sourced software licensed under the MIT License.

๐Ÿ’ฌ Support

Made with โค๏ธ by Sarfaraz Stark