<?php
// Enable error reporting for debugging only (disable in production)
error_reporting(E_ALL);
ini_set('display_errors', 1);
// Include database configuration
require_once '../admin/database/db_config.php';
// Razorpay webhook secret (set this in your Razorpay dashboard)
$webhook_secret = "YOUR_WEBHOOK_SECRET";
// Get the JSON post data
$input = file_get_contents('php://input');
$data = json_decode($input, true);
// Log webhook data for debugging
error_log("Razorpay Webhook: " . $input);
// Verify webhook signature
$signature = isset($_SERVER['HTTP_X_RAZORPAY_SIGNATURE']) ? $_SERVER['HTTP_X_RAZORPAY_SIGNATURE'] : '';
$computed_signature = hash_hmac('sha256', $input, $webhook_secret);
// Check if signatures match
if (hash_equals($computed_signature, $signature)) {
$event = $data['event'];
// Handle different webhook events
switch ($event) {
case 'payment.authorized':
handlePaymentAuthorized($data['payload']['payment']['entity'], $conn);
break;
case 'payment.captured':
handlePaymentCaptured($data['payload']['payment']['entity'], $conn);
break;
case 'payment.failed':
handlePaymentFailed($data['payload']['payment']['entity'], $conn);
break;
case 'refund.created':
handleRefundCreated($data['payload']['refund']['entity'], $conn);
break;
default:
// Log unhandled event
error_log("Unhandled Razorpay webhook event: " . $event);
break;
}
// Respond with 200 OK to acknowledge receipt
http_response_code(200);
echo json_encode(['status' => 'received']);
} else {
// Invalid signature
error_log("Invalid Razorpay webhook signature. Expected: " . $computed_signature . ", Received: " . $signature);
http_response_code(400);
echo json_encode(['status' => 'invalid signature']);
}
/**
* Handle payment.authorized event
*/
function handlePaymentAuthorized($payment, $conn) {
// Extract payment details
$paymentId = $payment['id'];
$orderId = $payment['order_id'];
$amount = $payment['amount'] / 100; // Convert from paise to rupees
// Log the authorization
error_log("Payment authorized: " . $paymentId . " for order " . $orderId . " amount: " . $amount);
// You can update your database here if needed
// This is optional as the payment isn't captured yet
}
/**
* Handle payment.captured event
*/
function handlePaymentCaptured($payment, $conn) {
// Extract payment details
$paymentId = $payment['id'];
$orderId = $payment['order_id'];
$amount = $payment['amount'] / 100; // Convert from paise to rupees
$notes = $payment['notes'] ?? [];
// Get payment plan from order notes (if available)
$payment_plan = isset($notes['payment_plan']) ? $notes['payment_plan'] : 'full';
// Log the capture
error_log("Payment captured: " . $paymentId . " for order " . $orderId . " amount: " . $amount);
// Check if this payment is already recorded
$check_query = "SELECT id FROM payments WHERE transaction_id = ?";
$stmt = $conn->prepare($check_query);
if (!$stmt) {
error_log("Failed to prepare payment check statement: " . $conn->error);
return;
}
$stmt->bind_param("s", $paymentId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
// Payment already recorded, just update status
$update_query = "UPDATE payments SET status = 'completed' WHERE transaction_id = ?";
$stmt = $conn->prepare($update_query);
if (!$stmt) {
error_log("Failed to prepare payment update statement: " . $conn->error);
return;
}
$stmt->bind_param("s", $paymentId);
if (!$stmt->execute()) {
error_log("Failed to update payment status: " . $stmt->error);
}
} else {
// This is a new payment, but we don't have user information from webhook alone
// Log this for manual verification
error_log("New payment captured via webhook but user information is not available: " . $paymentId);
}
}
/**
* Handle payment.failed event
*/
function handlePaymentFailed($payment, $conn) {
// Extract payment details
$paymentId = $payment['id'];
$orderId = $payment['order_id'];
$errorCode = $payment['error_code'] ?? 'unknown';
$errorDescription = $payment['error_description'] ?? 'No error description';
// Log the failure
error_log("Payment failed: " . $paymentId . " for order " . $orderId . ". Error: " . $errorCode . " - " . $errorDescription);
// Find any pending payment records for this order and update them
$update_query = "UPDATE payments SET status = 'failed', payment_details = JSON_SET(payment_details, '$.error_code', ?, '$.error_description', ?) WHERE transaction_id = ?";
$stmt = $conn->prepare($update_query);
if (!$stmt) {
error_log("Failed to prepare payment update statement: " . $conn->error);
return;
}
$stmt->bind_param("sss", $errorCode, $errorDescription, $paymentId);
if (!$stmt->execute()) {
error_log("Failed to update payment status: " . $stmt->error);
}
}
/**
* Handle refund.created event
*/
function handleRefundCreated($refund, $conn) {
// Extract refund details
$refundId = $refund['id'];
$paymentId = $refund['payment_id'];
$amount = $refund['amount'] / 100; // Convert from paise to rupees
$notes = $refund['notes'] ?? [];
$reason = $refund['notes']['reason'] ?? 'No reason provided';
// Log the refund
error_log("Refund created: " . $refundId . " for payment " . $paymentId . " amount: " . $amount);
// Update the payment status
$update_query = "UPDATE payments SET status = 'refunded', payment_details = JSON_SET(payment_details, '$.refund_id', ?, '$.refund_amount', ?, '$.refund_reason', ?) WHERE transaction_id = ?";
$stmt = $conn->prepare($update_query);
if (!$stmt) {
error_log("Failed to prepare payment update statement: " . $conn->error);
return;
}
$stmt->bind_param("sdss", $refundId, $amount, $reason, $paymentId);
if (!$stmt->execute()) {
error_log("Failed to update payment status: " . $stmt->error);
}
}