PHP Payment Guide
PHP · Razorpay · Gujarati Guide

Complete PHP Payment Gateway Integration

દરેક સ્ટેપ ગુજરાતીમાં સમજાવેલો, સાથે સંપૂર્ણ કોડ. Order creation થી signature verification, webhook, refund અને live deployment સુધીની સંપૂર્ણ ગાઈડ — બધું આ એક જ પેજમાં.

📌 ભાગ 1: પેમેન્ટ ગેટવે એટલે શું? (What is a Payment Gateway?)

ગુજરાતી સમજૂતી: પેમેન્ટ ગેટવે એ એક એવી સર્વિસ છે જે તમારી વેબસાઈટ અને બેંક/કાર્ડ નેટવર્ક વચ્ચે કડી (bridge) નું કામ કરે છે. જ્યારે કસ્ટમર તમારી સાઈટ પર કાર્ડ અથવા UPI થી પેમેન્ટ કરે છે, ત્યારે ગેટવે એ પેમેન્ટને સિક્યોર રીતે process કરે છે અને તમને result (success/failure) પાછું આપે છે.

મુખ્ય કારણો કેમ ડાયરેક્ટ કાર્ડ ડિટેલ્સ પોતાની સાઈટ પર store ન કરવી: 1. PCI-DSS Compliance — કાર્ડ ડેટા સ્ટોર કરવા માટે legal/security certification જોઈએ, જે નાની વેબસાઈટ માટે costly અને complex છે. 2. Gateway (Razorpay/Stripe/PayPal) આ સિક્યુરિટીનું ધ્યાન રાખે છે — તમારે ફક્ત integration કરવાનું છે. 3. જો કાર્ડ ડેટા લીક થાય તો legal liability તમારા પર આવે — gateway use કરવાથી એ risk ટળે છે.

Flow સમજો (5 સ્ટેપ્સ):

Customer                  Your Server              Payment Gateway
   |                           |                          |
   |--1. Checkout button------>|                          |
   |                           |--2. Create Order-------->|
   |                           |<--Order ID----------------|
   |<--3. Show payment widget--|                          |
   |--4. Enters card/UPI------------------------------->  |
   |<-------------------------------------------------- Payment ID + Signature
   |--5. Send Payment ID+Sig-->|                          |
   |                           |--6. Verify Signature---->|
   |                           |<--Confirmed---------------|
   |<--7. Success Page---------|                          |

મહત્વનો નિયમ: Step 6 (server-side verification) ક્યારેય skip ન કરવો. જો તમે ફક્ત browser પર trust કરો (JavaScript success callback), તો કોઈ પણ વ્યક્તિ browser console માંથી fake "success" call કરી શકે અને ફ્રીમાં order મેળવી શકે. Verification હંમેશા server પર જ થવું જોઈએ.


📌 ભાગ 2: કયું Gateway પસંદ કરવું?

Gateway ક્યારે વાપરવું નોંધ
Razorpay India-based વેબસાઈટ, UPI/Cards/Netbanking આ ગાઈડમાં મુખ્ય example
Stripe International, Cards/Wallets Section 12 માં અલગથી આપેલ
PayPal Global, PayPal balance + Cards Section 13 માં અલગથી આપેલ

ભારતીય ગ્રાહકો (UPI/Cards) માટે Razorpay સૌથી સરળ છે, એટલે આ ગાઈડ Razorpay ને મુખ્ય ઉદાહરણ તરીકે વાપરે છે.


📌 ભાગ 3: જરૂરી વસ્તુઓ (Prerequisites)

ગુજરાતી નોંધ: શરૂઆતમાં હંમેશા Test Mode માં કામ કરો. Test mode માં real પૈસા move નથી થતા, પણ આખો flow ચકાસી શકાય છે. જ્યારે બધું બરાબર કામ કરે ત્યારે જ Live keys પર switch કરવું.


📌 ભાગ 4: Project Setup

Bash
mkdir payment-integration
cd payment-integration
composer require razorpay/razorpay

Folder Structure:

payment-integration/
├── config.php          -> API keys
├── db.php              -> Database connection
├── create_order.php    -> Order બનાવે (server-side)
├── checkout.php        -> Frontend payment page
├── verify_payment.php  -> Signature verify કરે
├── webhook.php         -> Gateway તરફથી server-to-server confirmation
├── success.php         -> Payment confirmation page
├── refund.php          -> Refund process કરવા માટે
└── vendor/             -> Composer libraries (auto-generated)

📌 ભાગ 4B: સાવ શરૂઆતથી — Beginner Step-by-Step Setup Guide

પહેલા સમજો: તમારી પાસે 2 વિકલ્પ છે

વિકલ્પએટલે શું
A) પોતાના Computer પર Test કરવું (ભલામણ કરેલ)પહેલા laptop/PC પર આખું બનાવીને ચકાસો, પછી live site પર mૂકો
B) સીધા Live Website પર કામ કરવુંHosting panel અથવા SSH access જોઈએ

મોટાભાગના beginners એ પહેલા A કરવું જોઈએ. નીચે એ જ સ્ટેપ-બાય-સ્ટેપ સમજાવેલ છે.

Step 1: "Terminal" એટલે શું?

mkdir, cd, composer require જેવા commands એક terminal (Windows પર "Command Prompt", Mac પર "Terminal") માં ટાઈપ કરવાના હોય છે. આ ફક્ત icons પર click કરવાને બદલે text લખીને computer ને સૂચના આપવાની રીત છે.

Step 2: PHP ચલાવવા માટે "Local Server" જોઈએ

PHP file ને ડબલ-ક્લિક કરીને ચલાવી શકાતી નથી — તેના માટે ખાસ સોફ્ટવેર જોઈએ. Beginners માટે સૌથી સહેલું ટૂલ XAMPP છે (free, Windows/Mac/Linux બધા પર ચાલે છે).

  1. https://www.apachefriends.org પર જાઓ
  2. તમારા OS માટે XAMPP download કરો
  3. Install કરો (બધે default options રાખીને "Next" ક્લિક કરતા જાઓ)
  4. XAMPP Control Panel ખોલો → "Apache" અને "MySQL" ની બાજુમાં Start ક્લિક કરો

જ્યારે Apache અને MySQL બંને "Running" (લીલા રંગમાં) દેખાય, ત્યારે તમારું local server તૈયાર છે.

Step 3: Composer Install કરવું

Composer એ એક ટૂલ છે જે Razorpay જેવી PHP libraries આપોઆપ download કરી આપે છે.

  1. https://getcomposer.org/download/ પર જાઓ
  2. Windows: Composer-Setup.exe download કરીને run કરો. તે પૂછશે "where is your php.exe" — ત્યાં C:\xampp\php\php.exe આપો. Installer પૂરું કરો.
  3. Mac/Linux: Terminal ખોલીને આ commands ચલાવો:
Bash
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
sudo mv composer.phar /usr/local/bin/composer

Composer બરાબર install થયું કે નહીં ચકાસવા માટે terminal માં ટાઈપ કરો:

Bash
composer --version

જો Composer version 2.x.x જેવું કંઈક દેખાય, તો install બરાબર થયું છે.

સામાન્ય ભૂલ: "composer is not recognized as a command" — આનો અર્થ એ કે install પૂરું નથી થયું, અથવા install પછી terminal/computer restart કરવું જરૂરી છે.

Step 4: હવે ખરેખર Project બનાવો (htdocs ની અંદર)

Terminal ખોલો અને પહેલા htdocs ફોલ્ડરની અંદર જાઓ:

Bash
# Windows
cd C:\xampp\htdocs

# Mac
cd /Applications/XAMPP/htdocs

હવે તેની અંદર તમારો project folder બનાવો:

Bash
mkdir payment-integration
cd payment-integration
composer require razorpay/razorpay

આ command ચલાવવાથી શું થાય છે:

  1. mkdir payment-integrationhtdocs ની અંદર payment-integration નામનું નવું ફોલ્ડર બનાવે છે
  2. cd payment-integration → તમારું terminal એ ફોલ્ડરની "અંદર" જાય છે, જેથી પછીના commands તેની અંદર જ ચાલે
  3. composer require razorpay/razorpay → Razorpay ની PHP library download કરે છે અને બનાવે છે:
    • vendor/ ફોલ્ડર (library નો actual code)
    • composer.json ફાઈલ (તમારો project કઈ libraries વાપરે છે એ record કરે છે)
    • composer.lock ફાઈલ (exact versions lock કરે છે)

આ પૂરું થયા પછી, payment-integration ફોલ્ડરની અંદર આવું દેખાવું જોઈએ:

payment-integration/
├── composer.json
├── composer.lock
└── vendor/
    ├── autoload.php   ← આ ફાઈલ ખૂબ મહત્વની છે
    └── razorpay/

vendor/autoload.php ફાઈલ જ છે જેને આખા ગાઈડમાં દરેક PHP ફાઈલની શરૂઆતમાં require 'vendor/autoload.php'; તરીકે વાપરેલ છે — તેના લીધે જ use Razorpay\Api\Api; જેવી line manual include કર્યા વગર ચાલે છે.

Step 5: આ જ ફોલ્ડરમાં તમારી PHP ફાઈલો ઉમેરો

હવે, એ જ payment-integration ફોલ્ડરની અંદર (જ્યાં vendor છે ત્યાં જ), ઉપરના ભાગોમાં આપેલી ફાઈલો બનાવો: config.php, db.php, create_order.php, checkout.php વગેરે.

payment-integration/
├── vendor/
├── composer.json
├── config.php
├── db.php
├── create_order.php
├── checkout.php
├── verify_payment.php
├── webhook.php
└── success.php

Step 6: Browser માં ચકાસો

XAMPP ચાલુ છે એટલે browser ખોલીને આ URL પર જાઓ:

http://localhost/payment-integration/checkout.php

જો બધું બરાબર setup થયું હોય, તો checkout page ખૂલવું જોઈએ.

જો blank page અથવા error દેખાય તો:

Step 7: Database (MySQL) Setup

  1. Browser માં http://localhost/phpmyadmin પર જાઓ (XAMPP સાથે free આવે છે)
  2. ડાબી બાજુ New ક્લિક કરો, database નું નામ આપો (દા.ત. payment_db), Create ક્લિક કરો
  3. SQL tab ક્લિક કરો, ઉપરના ભાગ 5 નો CREATE TABLE orders (...) code paste કરો, Go ક્લિક કરો
  4. તમારી db.php ફાઈલમાં $dbname, $user, $pass update કરો — XAMPP ના default MySQL માટે username સામાન્ય રીતે root હોય છે, password ખાલી (કંઈ નહીં) હોય છે

Step 8: Local પર કામ કર્યા પછી — ખરેખરી Live Website પર કેવી રીતે મૂકવું

આ સાવ અલગ સ્ટેપ છે, જે local testing પૂરું થયા પછી જ કરવાનું છે.

Option A — Shared Hosting (Hostinger, GoDaddy, Bluehost જેવા) — સૌથી common, terminal access નથી હોતું:

  1. તમારા પોતાના computer પર Step 4 નો vendor/ ફોલ્ડર પહેલેથી જ છે — live server પર composer ચલાવવાની જરૂર જ નથી
  2. તમારો આખો payment-integration ફોલ્ડર zip કરો
  3. Hosting ના cPanel માં login કરો → File Manager ખોલો → public_html માં જાઓ (આ htdocs ની live version છે)
  4. Zip file upload કરો, પછી right-click → Extract કરો
  5. db.php માં તમારા live database ના credentials update કરો (hosting ના cPanel → MySQL Databases માં મળે છે)
  6. https://yourdomain.com/payment-integration/checkout.php પર જઈને ચકાસો

Option B — VPS અથવા SSH/terminal access વાળું hosting:

  1. SSH દ્વારા connect કરો: ssh username@yourserver.com
  2. તમારા website folder માં જાઓ (સામાન્ય રીતે /var/www/html અથવા એના જેવું)
  3. એ જ mkdir, cd, composer require commands સીધા server પર જ ચલાવો

Step 9: Test Keys થી Live Keys પર Switch કરવું

Local પર rzp_test_... keys થી બધું બરાબર ચાલે પછી જ, Razorpay Dashboard માં login કરીને KYC પૂરું કરો, rzp_live_... keys મેળવો, અને ફક્ત live સાઈટ પરની config.php માં જ એ બદલો — ક્યારેય local testing માં live keys વાપરવી નહીં.


📌 ભાગ 5: Database Table બનાવવું

ગુજરાતી સમજૂતી: દરેક ઓર્ડરની વિગત (amount, status, payment id) ડેટાબેઝમાં store કરવી જરૂરી છે, જેથી પછીથી transaction history જોઈ શકાય અને refund/dispute વખતે reference મળે.

SQL
CREATE TABLE orders (
    id INT AUTO_INCREMENT PRIMARY KEY,
    razorpay_order_id VARCHAR(100) NOT NULL,
    razorpay_payment_id VARCHAR(100) DEFAULT NULL,
    amount DECIMAL(10,2) NOT NULL,
    currency VARCHAR(10) DEFAULT 'INR',
    status ENUM('created','paid','failed','refunded') DEFAULT 'created',
    customer_email VARCHAR(150),
    customer_name VARCHAR(150),
    customer_phone VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

-- Webhook events log (for debugging & audit trail)
CREATE TABLE webhook_logs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    event_type VARCHAR(100),
    payload TEXT,
    received_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

કેમ updated_at અને webhook_logs ઉમેર્યા? - updated_at — છેલ્લે status ક્યારે બદલાયો એ ટ્રેક કરવા. - webhook_logs — જો કંઈ ખોટું થાય (payment mismatch), તો debug કરવા માટે raw payload history રાખવી ઉપયોગી છે.


📌 ભાગ 6: config.php — API Keys Setup

ગુજરાતી ચેતવણી: API keys ક્યારેય સીધા code માં hardcode કરીને GitHub પર push ન કરવી. Production માં environment variables વાપરવા.

PHP
<?php
// config.php

// Test keys અહીં મૂકો (Razorpay Dashboard > Settings > API Keys)
define('RAZORPAY_KEY_ID', getenv('RAZORPAY_KEY_ID') ?: 'rzp_test_XXXXXXXXXXXX');
define('RAZORPAY_KEY_SECRET', getenv('RAZORPAY_KEY_SECRET') ?: 'YOUR_TEST_SECRET_KEY');
define('RAZORPAY_WEBHOOK_SECRET', getenv('RAZORPAY_WEBHOOK_SECRET') ?: 'YOUR_WEBHOOK_SECRET');

// Error reporting (development માં true, production માં false રાખવું)
define('APP_DEBUG', true);

if (APP_DEBUG) {
    error_reporting(E_ALL);
    ini_set('display_errors', 1);
} else {
    error_reporting(0);
    ini_set('display_errors', 0);
}

db.php — Database Connection (PDO with error handling)

PHP
<?php
// db.php
$host = 'localhost';
$dbname = 'your_database';
$user = 'your_db_user';
$pass = 'your_db_password';

try {
    $pdo = new PDO(
        "mysql:host=$host;dbname=$dbname;charset=utf8mb4",
        $user,
        $pass,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false, // real prepared statements, SQL injection સામે વધુ સુરક્ષિત
        ]
    );
} catch (PDOException $e) {
    // ગુજરાતી નોંધ: production માં raw error message user ને ના બતાવવો
    error_log('DB Connection Error: ' . $e->getMessage());
    die('Database connection failed. Please try again later.');
}

📌 ભાગ 7: create_order.php — Server પર Order બનાવવો

ગુજરાતી સમજૂતી — ખૂબ મહત્વનો મુદ્દો: Amount ક્યારેય frontend (JavaScript/HTML form) માંથી સીધું ના લેવું. જો તમે $_POST['amount'] સીધું use કરો, તો કોઈ પણ user browser DevTools ખોલીને amount ₹499 ને બદલે ₹1 મોકલી શકે છે! Amount હંમેશા તમારા database/cart logic માંથી server-side જ fetch કરવું.

PHP
<?php
// create_order.php
require 'vendor/autoload.php';
require 'config.php';
require 'db.php';

use Razorpay\Api\Api;

header('Content-Type: application/json');

// ✅ સાચી રીત: amount ને product/cart ID પરથી server-side lookup કરો
// ઉદાહરણ તરીકે અહીં એક product ID hardcode કરેલો છે, real app માં DB query કરો:
$productId = $_POST['product_id'] ?? null;

// Example lookup (વાસ્તવિક app માં તમારા products table માંથી ભાવ મેળવો)
$priceList = [
    'P001' => 499.00,
    'P002' => 999.00,
];

if (!$productId || !isset($priceList[$productId])) {
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => 'Invalid product']);
    exit;
}

$amountInRupees = $priceList[$productId];
$amountInPaise = (int) round($amountInRupees * 100); // Razorpay પૈસા (paise) માં amount લે છે

$customerEmail = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) ?: 'guest@example.com';
$customerName  = htmlspecialchars($_POST['name'] ?? 'Guest', ENT_QUOTES);
$customerPhone = htmlspecialchars($_POST['phone'] ?? '', ENT_QUOTES);

try {
    $api = new Api(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET);

    $razorpayOrder = $api->order->create([
        'receipt'         => 'rcpt_' . time() . '_' . rand(1000, 9999),
        'amount'          => $amountInPaise,
        'currency'        => 'INR',
        'payment_capture' => 1, // 1 = auto-capture (પૈસા આપોઆપ capture થાય)
        'notes'           => [
            'product_id' => $productId,
        ],
    ]);

    $stmt = $pdo->prepare(
        "INSERT INTO orders (razorpay_order_id, amount, currency, customer_email, customer_name, customer_phone)
         VALUES (?, ?, 'INR', ?, ?, ?)"
    );
    $stmt->execute([$razorpayOrder['id'], $amountInRupees, $customerEmail, $customerName, $customerPhone]);

    echo json_encode([
        'success'  => true,
        'order_id' => $razorpayOrder['id'],
        'amount'   => $amountInPaise,
        'key'      => RAZORPAY_KEY_ID,
        'name'     => $customerName,
        'email'    => $customerEmail,
    ]);

} catch (Exception $e) {
    error_log('Order creation failed: ' . $e->getMessage());
    http_response_code(500);
    echo json_encode(['success' => false, 'error' => 'Could not create order. Please try again.']);
}

નોંધ કરવા જેવા મુદ્દા: - payment_capture => 1 એટલે payment authorize થતાં જ automatically capture (finalize) થઈ જાય. જો 0 રાખો તો તમારે manually API call કરીને capture કરવું પડે (દા.ત. inventory check પછી જ capture કરવો હોય તો). - notes field માં તમે custom metadata (product_id, user_id વગેરે) attach કરી શકો — આ પછીથી dashboard/webhook માં દેખાય છે. - Error details ને error_log() માં લોગ કરો, પણ user ને generic message જ બતાવો (security best practice).


📌 ભાગ 8: checkout.php — Frontend Payment Page

ગુજરાતી સમજૂતી: આ page પર Razorpay નું JavaScript library (checkout.js) load થાય છે, જે એક secure popup/widget ખોલે છે જ્યાં ગ્રાહક કાર્ડ/UPI details enter કરે છે. તમારી સાઈટ ક્યારેય કાર્ડ number સીધું જોતી કે store કરતી નથી — એ બધું Razorpay ના secure iframe માં જ થાય છે.

PHP
<!DOCTYPE html>
<html lang="gu">
<head>
    <meta charset="UTF-8">
    <title>Checkout - Payment Page</title>
</head>
<body>

    <h2>તમારું પેમેન્ટ પૂર્ણ કરો</h2>

    <form id="payment-form">
        <input type="hidden" id="product_id" value="P001">
        <input type="text" id="name" placeholder="પૂરું નામ" required><br><br>
        <input type="email" id="email" placeholder="ઈમેલ" required><br><br>
        <input type="tel" id="phone" placeholder="ફોન નંબર" required><br><br>
        <button type="submit">Pay ₹499</button>
    </form>

    <p id="status-msg"></p>

    <script src="https://checkout.razorpay.com/v1/checkout.js"></script>
    <script>
    document.getElementById('payment-form').addEventListener('submit', async function (e) {
        e.preventDefault();

        const statusMsg = document.getElementById('status-msg');
        statusMsg.textContent = 'ઓર્ડર બનાવી રહ્યા છીએ...';

        const name = document.getElementById('name').value;
        const email = document.getElementById('email').value;
        const phone = document.getElementById('phone').value;
        const productId = document.getElementById('product_id').value;

        try {
            // Step 1: Server પર order બનાવો
            const res = await fetch('create_order.php', {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: `name=${encodeURIComponent(name)}&email=${encodeURIComponent(email)}&phone=${encodeURIComponent(phone)}&product_id=${encodeURIComponent(productId)}`
            });
            const data = await res.json();

            if (!data.success) {
                statusMsg.textContent = 'Error: ' + data.error;
                return;
            }

            statusMsg.textContent = '';

            // Step 2: Razorpay widget ખોલો
            const options = {
                key: data.key,
                amount: data.amount,
                currency: 'INR',
                name: 'Your Company Name',
                description: 'Order Payment',
                order_id: data.order_id,
                handler: function (response) {
                    statusMsg.textContent = 'Payment verify કરી રહ્યા છીએ...';

                    // Step 3: Payment details server ને મોકલો - verification માટે
                    fetch('verify_payment.php', {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify(response)
                    })
                    .then(r => r.json())
                    .then(result => {
                        if (result.success) {
                            window.location.href = 'success.php?order_id=' + data.order_id;
                        } else {
                            statusMsg.textContent = 'Payment verification નિષ્ફળ ગયું: ' + result.error;
                        }
                    })
                    .catch(() => {
                        statusMsg.textContent = 'Network error. Please contact support with your payment ID: ' + response.razorpay_payment_id;
                    });
                },
                prefill: { name: name, email: email, contact: phone },
                theme: { color: '#3399cc' },
                modal: {
                    ondismiss: function () {
                        statusMsg.textContent = 'તમે payment window બંધ કરી દીધી.';
                    }
                }
            };

            const rzp = new Razorpay(options);

            rzp.on('payment.failed', function (response) {
                statusMsg.textContent = 'Payment નિષ્ફળ: ' + response.error.description;
            });

            rzp.open();

        } catch (err) {
            statusMsg.textContent = 'કંઈક ખોટું થયું. ફરી પ્રયત્ન કરો.';
        }
    });
    </script>
</body>
</html>

મહત્વનું: handler function માં network error પકડ્યો છે — જો internet drop થાય પણ payment succeed થઈ ગયું હોય, તો user ને payment ID બતાવેલ છે જેથી support ને contact કરી શકે. આ જ કારણથી webhook જરૂરી છે — browser fail થાય તો પણ webhook દ્વારા server ને ખબર પડી જાય કે payment થયું છે.


📌 ભાગ 9: verify_payment.php — સૌથી મહત્વનું Security Step

ગુજરાતી ઊંડાણપૂર્વક સમજૂતી:

Razorpay payment પછી ત્રણ વસ્તુ frontend ને પાછી આપે છે: 1. razorpay_order_id 2. razorpay_payment_id 3. razorpay_signature

Signature એ HMAC-SHA256(order_id + "|" + payment_id, your_secret_key) વાપરીને બનેલી હોય છે. તમારો server આ જ ફોર્મ્યુલા વાપરીને પોતાની signature બનાવે છે અને બંને સરખાવે છે (hash_equals). જો બંને મેચ થાય, તો જ ખાતરી થાય કે payment ખરેખર Razorpay તરફથી જ આવેલું છે (કોઈ fake request નથી).

આ signature check કેમ જરૂરી છે? કોઈ પણ વ્યક્તિ browser console ખોલીને directly verify_payment.php ને fake data સાથે call કરી શકે (દા.ત. fake payment_id મોકલીને). પણ એ ખોટી signature બનાવી શકતો નથી કારણ કે એની પાસે તમારો RAZORPAY_KEY_SECRET નથી. એટલે signature mismatch થાય અને request reject થાય.

PHP
<?php
// verify_payment.php
require 'vendor/autoload.php';
require 'config.php';
require 'db.php';

use Razorpay\Api\Api;
use Razorpay\Api\Errors\SignatureVerificationError;

header('Content-Type: application/json');

$input = json_decode(file_get_contents('php://input'), true);

$razorpayOrderId   = $input['razorpay_order_id'] ?? '';
$razorpayPaymentId = $input['razorpay_payment_id'] ?? '';
$razorpaySignature = $input['razorpay_signature'] ?? '';

if (!$razorpayOrderId || !$razorpayPaymentId || !$razorpaySignature) {
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => 'Missing payment fields']);
    exit;
}

try {
    $api = new Api(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET);

    $attributes = [
        'razorpay_order_id'   => $razorpayOrderId,
        'razorpay_payment_id' => $razorpayPaymentId,
        'razorpay_signature'  => $razorpaySignature,
    ];

    // આ line signature ચકાસે છે - mismatch હોય તો exception throw કરે
    $api->utility->verifyPaymentSignature($attributes);

    // Extra safety: order DB માં exist કરે છે અને amount મેચ થાય છે કે નહીં ચકાસો
    $stmt = $pdo->prepare("SELECT * FROM orders WHERE razorpay_order_id = ?");
    $stmt->execute([$razorpayOrderId]);
    $order = $stmt->fetch();

    if (!$order) {
        throw new Exception('Order not found in our records');
    }

    if ($order['status'] === 'paid') {
        // પહેલેથી જ paid છે - duplicate call, idempotent રીતે success return કરો
        echo json_encode(['success' => true, 'message' => 'Already verified']);
        exit;
    }

    // બધુ બરાબર - status update કરો
    $stmt = $pdo->prepare(
        "UPDATE orders SET status = 'paid', razorpay_payment_id = ? WHERE razorpay_order_id = ?"
    );
    $stmt->execute([$razorpayPaymentId, $razorpayOrderId]);

    echo json_encode(['success' => true]);

} catch (SignatureVerificationError $e) {
    error_log('Signature verification failed for order ' . $razorpayOrderId . ': ' . $e->getMessage());

    $stmt = $pdo->prepare("UPDATE orders SET status = 'failed' WHERE razorpay_order_id = ?");
    $stmt->execute([$razorpayOrderId]);

    http_response_code(400);
    echo json_encode(['success' => false, 'error' => 'Payment verification failed']);

} catch (Exception $e) {
    error_log('Verification error: ' . $e->getMessage());
    http_response_code(400);
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

ગુજરાતીમાં નોંધ: if ($order['status'] === 'paid') ચેક idempotency માટે છે — એટલે કે જો user accidentally submit button 2 વાર દબાવે અથવા network retry થાય, તો order 2 વાર process ના થાય અને એક જ result મળે.

📌 ભાગ 10: webhook.php — Server-to-Server Confirmation (સૌથી Reliable રીત)

ગુજરાતી ઊંડાણપૂર્વક સમજૂતી: Webhook એટલે શું? જ્યારે payment succeed/fail થાય, ત્યારે Razorpay પોતે સીધું જ તમારા server ને (browser વગર) એક HTTP request મોકલે છે, જેમાં payment ની સંપૂર્ણ વિગત હોય છે. આ browser પર આધાર રાખતું નથી — એટલે જો ગ્રાહકનું internet connection payment પછી તરત જ કપાઈ જાય, browser tab બંધ થઈ જાય, અથવા JavaScript error આવે, તો પણ webhook દ્વારા તમારા server ને payment status ની ખાતરી મળી જ જાય છે.

Webhook Setup કરવાની રીત: 1. Razorpay Dashboard → Settings → Webhooks → Add New Webhook 2. URL આપો: https://yourdomain.com/webhook.php 3. Events select કરો: payment.captured, payment.failed, refund.processed 4. Webhook Secret generate થશે — એ config.php માં RAZORPAY_WEBHOOK_SECRET તરીકે save કરો

PHP
<?php
// webhook.php
require 'config.php';
require 'db.php';

$webhookBody      = file_get_contents('php://input');
$webhookSignature = $_SERVER['HTTP_X_RAZORPAY_SIGNATURE'] ?? '';

// Signature ચકાસો - ખાતરી કરો કે request ખરેખર Razorpay તરફથી જ છે
$expectedSignature = hash_hmac('sha256', $webhookBody, RAZORPAY_WEBHOOK_SECRET);

if (!hash_equals($expectedSignature, $webhookSignature)) {
    http_response_code(400);
    error_log('Webhook signature mismatch - possible fake request');
    exit('Invalid signature');
}

$event = json_decode($webhookBody, true);

// Audit trail માટે દરેક webhook event log કરો
$logStmt = $pdo->prepare("INSERT INTO webhook_logs (event_type, payload) VALUES (?, ?)");
$logStmt->execute([$event['event'] ?? 'unknown', $webhookBody]);

switch ($event['event'] ?? '') {

    case 'payment.captured':
        $orderId   = $event['payload']['payment']['entity']['order_id'];
        $paymentId = $event['payload']['payment']['entity']['id'];

        $stmt = $pdo->prepare(
            "UPDATE orders SET status = 'paid', razorpay_payment_id = ?
             WHERE razorpay_order_id = ? AND status != 'paid'"
        );
        $stmt->execute([$paymentId, $orderId]);
        break;

    case 'payment.failed':
        $orderId = $event['payload']['payment']['entity']['order_id'];

        $stmt = $pdo->prepare(
            "UPDATE orders SET status = 'failed' WHERE razorpay_order_id = ? AND status != 'paid'"
        );
        $stmt->execute([$orderId]);
        break;

    case 'refund.processed':
        $paymentId = $event['payload']['refund']['entity']['payment_id'];

        $stmt = $pdo->prepare(
            "UPDATE orders SET status = 'refunded' WHERE razorpay_payment_id = ?"
        );
        $stmt->execute([$paymentId]);
        break;
}

http_response_code(200);
echo 'OK'; // Razorpay ને 200 status ના મળે તો એ webhook retry કરે છે

ગુજરાતી નોંધ: AND status != 'paid' ઉમેરવાનું કારણ — જો verify_payment.php એ પહેલેથી status update કરી દીધો હોય, તો webhook ફરીથી same update ના કરે (duplicate processing ટાળવા).


📌 ભાગ 11: refund.php — Refund Process કરવો

ગુજરાતી સમજૂતી: ક્યારેક ગ્રાહકને પૈસા પાછા આપવા પડે (order cancel, product return વગેરે). Razorpay API દ્વારા આ પ્રોગ્રામેટિકલી કરી શકાય છે.

PHP
<?php
// refund.php (Admin-only script - authentication જરૂરી, નીચે નોંધ જુઓ)
require 'vendor/autoload.php';
require 'config.php';
require 'db.php';

use Razorpay\Api\Api;

header('Content-Type: application/json');

// ⚠️ મહત્વનું: આ script ને admin authentication વગર ક્યારેય expose ના કરવી!
// ઉદાહરણ તરીકે session check:
// if (!isset($_SESSION['admin_id'])) { http_response_code(403); exit; }

$orderId = $_POST['order_id'] ?? '';

$stmt = $pdo->prepare("SELECT * FROM orders WHERE razorpay_order_id = ? AND status = 'paid'");
$stmt->execute([$orderId]);
$order = $stmt->fetch();

if (!$order) {
    http_response_code(404);
    echo json_encode(['success' => false, 'error' => 'Paid order not found']);
    exit;
}

try {
    $api = new Api(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET);

    $refund = $api->payment->fetch($order['razorpay_payment_id'])->refund([
        'amount' => (int) round($order['amount'] * 100), // full refund; partial માટે ઓછી amount આપો
        'notes'  => ['reason' => 'Customer requested refund'],
    ]);

    $stmt = $pdo->prepare("UPDATE orders SET status = 'refunded' WHERE razorpay_order_id = ?");
    $stmt->execute([$orderId]);

    echo json_encode(['success' => true, 'refund_id' => $refund['id']]);

} catch (Exception $e) {
    error_log('Refund failed: ' . $e->getMessage());
    http_response_code(500);
    echo json_encode(['success' => false, 'error' => 'Refund could not be processed']);
}

📌 ભાગ 12: success.php — Confirmation Page

PHP
<?php
require 'db.php';
$orderId = $_GET['order_id'] ?? '';

$stmt = $pdo->prepare("SELECT * FROM orders WHERE razorpay_order_id = ?");
$stmt->execute([$orderId]);
$order = $stmt->fetch();
?>
<!DOCTYPE html>
<html lang="gu">
<body>
<?php if ($order && $order['status'] === 'paid'): ?>
    <h2>પેમેન્ટ સફળ થયું! (Payment Successful!)</h2>
    <p>Order ID: <?= htmlspecialchars($order['razorpay_order_id']) ?></p>
    <p>રકમ (Amount): ₹<?= htmlspecialchars($order['amount']) ?></p>
    <p>Payment ID: <?= htmlspecialchars($order['razorpay_payment_id']) ?></p>
<?php else: ?>
    <h2>પેમેન્ટ confirm થઈ શક્યું નથી. Support નો સંપર્ક કરો.</h2>
<?php endif; ?>
</body>
</html>

📌 ભાગ 13: Testing (Test Mode માં ચકાસવું)

Test Card: 4111 1111 1111 1111 — future કોઈપણ expiry date, કોઈપણ 3-digit CVV

ગુજરાતી નોંધ: Test mode માં real પૈસા કપાતા નથી. Razorpay ના official docs માંથી latest test card/UPI list check કરો, કારણ કે આ periodically update થાય છે.

Testing checklist: - [ ] Successful payment flow (checkout → success page) - [ ] Failed payment (wrong CVV વાપરીને ચકાસો) - [ ] Payment window close કરવું (ondismiss handler ચકાસો) - [ ] Webhook receive થાય છે કે નહીં (Razorpay dashboard માં webhook logs જુઓ) - [ ] Duplicate submission (idempotency ચકાસો - એક જ order 2 વાર submit કરીને જુઓ) - [ ] Refund flow


📌 ભાગ 14: Security Checklist (Live પર જતા પહેલા)

# Check ગુજરાતી સમજૂતી
1 Live API keys env variables માં Keys code માં hardcode ના કરવી, GitHub પર push ના કરવી
2 HTTPS ફરજિયાત SSL વગર live payment કામ નહીં કરે અને data unsecure રહેશે
3 Server-side signature verification Frontend JS પર ક્યારેય trust ના કરવો
4 Webhook setup અને test Browser fail થાય તો પણ payment confirm થવો જોઈએ
5 Amount server-side lookup Frontend થી amount ક્યારેય trust ના કરવો
6 Rate limiting create_order.php ને bot abuse થી બચાવો
7 Input sanitization Name/email/phone હંમેશા sanitize કરો
8 Error logging vs display User ને generic error, developer ને detailed log
9 Idempotency check Duplicate payment processing ટાળો
10 Admin scripts protect કરો refund.php જેવી scripts authentication વગર expose ના કરવી

📌 ભાગ 15: Stripe Version (મુખ્ય તફાવતો)

Bash
composer require stripe/stripe-php
PHP
// create_order.php (Stripe version)
\Stripe\Stripe::setApiKey(STRIPE_SECRET_KEY);

$paymentIntent = \Stripe\PaymentIntent::create([
    'amount'   => $amountInPaise, // smallest unit, દા.ત. cents
    'currency' => 'usd',
]);

echo json_encode(['client_secret' => $paymentIntent->client_secret]);

ગુજરાતી નોંધ: Stripe માં frontend પર Stripe.js + Elements વપરાય છે (Razorpay ના checkout.js ને બદલે), અને verification manual signature check ને બદલે Stripe Webhook (payment_intent.succeeded event) દ્વારા automatically થાય છે.


📌 ભાગ 16: PayPal Version (મુખ્ય તફાવતો)

ગુજરાતી સમજૂતી: PayPal માં "PayPal Checkout SDK" નું JS button વપરાય છે. Flow આ પ્રમાણે છે: 1. Server-side order create (/v2/checkout/orders API call) 2. Frontend પર PayPal button render 3. User approve કરે એટલે server-side "capture" call 4. PayPal Webhook (PAYMENT.CAPTURE.COMPLETED) દ્વારા final confirmation


📌 ભાગ 17: Going Live Checklist

  1. Razorpay Dashboard માં KYC/Business verification પૂરું કરો
  2. Test keys ને Live keys થી replace કરો (environment variables દ્વારા)
  3. Webhook URL ને production domain પર update કરો
  4. ₹1 જેવું નાનું real transaction કરીને આખો flow ચકાસો
  5. Server logs monitor કરવાનું setup કરો (error_log ફાઈલ અથવા logging service)

🎯 સંક્ષિપ્તમાં Flow (Summary)

Checkout Button Click
    → create_order.php (server-side order + amount lookup)
    → Razorpay Checkout Widget ખૂલે
    → Customer pays
    → handler() browser માં payment_id + signature મેળવે
    → verify_payment.php (signature verify + DB update)
    → Webhook (payment.captured) — independent confirmation
    → success.php

યાદ રાખો: Verification + Webhook બંને એકસાથે વાપરવાથી જ ખરેખર secure અને reliable payment system બને છે — એકલા frontend callback પર ક્યારેય આધાર ના રાખવો.