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)
- PHP 7.4 અથવા તેથી ઉપર (PHP 8.x recommended)
- Composer (PHP નું package manager) ઈન્સ્ટોલ કરેલું
- Razorpay એકાઉન્ટ — https://dashboard.razorpay.com/signup પર free માં બનાવો
- Test API Keys: Dashboard → Settings → API Keys → Generate Test Key
- Live વેબસાઈટ પર HTTPS ફરજિયાત છે (SSL certificate વગર real payment કામ નહીં કરે)
- MySQL/MariaDB ડેટાબેઝ
ગુજરાતી નોંધ: શરૂઆતમાં હંમેશા Test Mode માં કામ કરો. Test mode માં real પૈસા move નથી થતા, પણ આખો flow ચકાસી શકાય છે. જ્યારે બધું બરાબર કામ કરે ત્યારે જ Live keys પર switch કરવું.
📌 ભાગ 4: Project Setup
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 ને સૂચના આપવાની રીત છે.
- Windows:
Windows keyદબાવો →cmdટાઈપ કરો → Enter દબાવો → એક કાળી window ખૂલશે, એ જ તમારું terminal છે. - Mac:
Cmd + Spaceદબાવો →Terminalટાઈપ કરો → Enter દબાવો.
Step 2: PHP ચલાવવા માટે "Local Server" જોઈએ
PHP file ને ડબલ-ક્લિક કરીને ચલાવી શકાતી નથી — તેના માટે ખાસ સોફ્ટવેર જોઈએ. Beginners માટે સૌથી સહેલું ટૂલ XAMPP છે (free, Windows/Mac/Linux બધા પર ચાલે છે).
- https://www.apachefriends.org પર જાઓ
- તમારા OS માટે XAMPP download કરો
- Install કરો (બધે default options રાખીને "Next" ક્લિક કરતા જાઓ)
- XAMPP Control Panel ખોલો → "Apache" અને "MySQL" ની બાજુમાં Start ક્લિક કરો
જ્યારે Apache અને MySQL બંને "Running" (લીલા રંગમાં) દેખાય, ત્યારે તમારું local server તૈયાર છે.
Step 3: Composer Install કરવું
Composer એ એક ટૂલ છે જે Razorpay જેવી PHP libraries આપોઆપ download કરી આપે છે.
- https://getcomposer.org/download/ પર જાઓ
- Windows:
Composer-Setup.exedownload કરીને run કરો. તે પૂછશે "where is your php.exe" — ત્યાંC:\xampp\php\php.exeઆપો. Installer પૂરું કરો. - Mac/Linux: Terminal ખોલીને આ commands ચલાવો:
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 માં ટાઈપ કરો:
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 ફોલ્ડરની અંદર જાઓ:
# Windows
cd C:\xampp\htdocs
# Mac
cd /Applications/XAMPP/htdocs
હવે તેની અંદર તમારો project folder બનાવો:
mkdir payment-integration
cd payment-integration
composer require razorpay/razorpay
આ command ચલાવવાથી શું થાય છે:
mkdir payment-integration→htdocsની અંદરpayment-integrationનામનું નવું ફોલ્ડર બનાવે છેcd payment-integration→ તમારું terminal એ ફોલ્ડરની "અંદર" જાય છે, જેથી પછીના commands તેની અંદર જ ચાલે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 દેખાય તો:
- XAMPP Control Panel માં Apache "Running" (લીલું) છે કે નહીં ચકાસો
- Folder ખરેખર
htdocsની અંદર જ છે કે નહીં ચકાસો - Error નું exact લખાણ વાંચો — PHP સામાન્ય રીતે problem કઈ ફાઈલ અને કઈ line માં છે એ જણાવે છે
Step 7: Database (MySQL) Setup
- Browser માં
http://localhost/phpmyadminપર જાઓ (XAMPP સાથે free આવે છે) - ડાબી બાજુ New ક્લિક કરો, database નું નામ આપો (દા.ત.
payment_db), Create ક્લિક કરો - SQL tab ક્લિક કરો, ઉપરના ભાગ 5 નો
CREATE TABLE orders (...)code paste કરો, Go ક્લિક કરો - તમારી
db.phpફાઈલમાં$dbname,$user,$passupdate કરો — XAMPP ના default MySQL માટે username સામાન્ય રીતેrootહોય છે, password ખાલી (કંઈ નહીં) હોય છે
Step 8: Local પર કામ કર્યા પછી — ખરેખરી Live Website પર કેવી રીતે મૂકવું
આ સાવ અલગ સ્ટેપ છે, જે local testing પૂરું થયા પછી જ કરવાનું છે.
Option A — Shared Hosting (Hostinger, GoDaddy, Bluehost જેવા) — સૌથી common, terminal access નથી હોતું:
- તમારા પોતાના computer પર Step 4 નો
vendor/ફોલ્ડર પહેલેથી જ છે — live server પર composer ચલાવવાની જરૂર જ નથી - તમારો આખો
payment-integrationફોલ્ડર zip કરો - Hosting ના cPanel માં login કરો → File Manager ખોલો →
public_htmlમાં જાઓ (આhtdocsની live version છે) - Zip file upload કરો, પછી right-click → Extract કરો
db.phpમાં તમારા live database ના credentials update કરો (hosting ના cPanel → MySQL Databases માં મળે છે)https://yourdomain.com/payment-integration/checkout.phpપર જઈને ચકાસો
Option B — VPS અથવા SSH/terminal access વાળું hosting:
- SSH દ્વારા connect કરો:
ssh username@yourserver.com - તમારા website folder માં જાઓ (સામાન્ય રીતે
/var/www/htmlઅથવા એના જેવું) - એ જ
mkdir,cd,composer requirecommands સીધા 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 મળે.
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
// 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
// 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
// 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 માં જ થાય છે.
<!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
// 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
// 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
// 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
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 (મુખ્ય તફાવતો)
composer require stripe/stripe-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
- Razorpay Dashboard માં KYC/Business verification પૂરું કરો
- Test keys ને Live keys થી replace કરો (environment variables દ્વારા)
- Webhook URL ને production domain પર update કરો
- ₹1 જેવું નાનું real transaction કરીને આખો flow ચકાસો
- 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 પર ક્યારેય આધાર ના રાખવો.