Skip to content

Commit

Permalink
Merge pull request #158 from razorpay/webhook-cron
Browse files Browse the repository at this point in the history
Webhook cron
  • Loading branch information
abdulwahidsharief authored Mar 11, 2024
2 parents 9521364 + 7dd1cdf commit 8ab693d
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 51 deletions.
12 changes: 12 additions & 0 deletions admin/model/extension/payment/razorpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ public function createTables()
PRIMARY KEY (`entity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
);

$this->db->query(
"CREATE TABLE IF NOT EXISTS `".DB_PREFIX."rzp_webhook_triggers` (
`entity_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`rzp_order_id` varchar(25) NOT NULL,
`rzp_webhook_data` text,
`rzp_webhook_notified_at` varchar(30),
`rzp_update_order_cron_status` int(11) DEFAULT 0,
PRIMARY KEY (`entity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;"
);
}

public function dropTables()
Expand Down
131 changes: 80 additions & 51 deletions catalog/controller/extension/payment/razorpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class ControllerExtensionPaymentRazorpay extends Controller
const SUBSCRIPTION_RESUMED = 'subscription.resumed';
const SUBSCRIPTION_CANCELLED = 'subscription.cancelled';
const SUBSCRIPTION_CHARGED = 'subscription.charged';
const WEBHOOK_WAIT_TIME = 30;
const WEBHOOK_WAIT_TIME = 120;
const HTTP_CONFLICT_STATUS = 409;
const CURRENCY_NOT_ALLOWED = [
'KWD',
Expand All @@ -43,6 +43,7 @@ public function index()
$data['is_recurring'] = "false";

$this->load->model('checkout/order');
$this->load->model('extension/payment/razorpay');

$order_info = $this->model_checkout_order->getOrder($this->session->data['order_id']);

Expand All @@ -55,11 +56,9 @@ public function index()

try
{
if ($this->cart->hasRecurringProducts() and
$this->config->get('payment_razorpay_subscription_status'))
if ($this->cart->hasRecurringProducts() and
$this->config->get('payment_razorpay_subscription_status'))
{
$this->load->model('extension/payment/razorpay');

//validate for non-subscription product and if recurring is product for more than 1
$this->validate_non_recurring_products();

Expand Down Expand Up @@ -107,7 +106,6 @@ public function index()

$this->model_extension_payment_razorpay->createOCRecurring($recurringData);


$this->log->write("RZP subscriptionID (:" . $subscription_order['id'] . ") created for Opencart OrderID (:" . $this->session->data['order_id'] . ")");
}

Expand All @@ -132,6 +130,7 @@ public function index()
$this->session->data["razorpay_order_amount"] = $order_data["amount"];
$this->session->data["razorpay_order_id_" . $this->session->data['order_id']] = $razorpay_order['id'];
$data['razorpay_order_id'] = $this->session->data["razorpay_order_id_" . $this->session->data['order_id']];
$this->model_extension_payment_razorpay->addOrderForWebhook($this->session->data['order_id'], $razorpay_order['id'], 0);

$this->log->write("RZP orderID (:" . $razorpay_order['id'] . ") created for Opencart OrderID (:" . $this->session->data['order_id'] . ")");
}
Expand Down Expand Up @@ -335,6 +334,7 @@ public function callback()
$order_info['order_status_id'] === '0')
{
$this->model_checkout_order->addOrderHistory($merchant_order_id, $this->config->get('payment_razorpay_order_status_id'), 'Payment Successful. Razorpay Payment Id:' . $razorpay_payment_id, true);
$this->model_extension_payment_razorpay->updateOrderForWebhook($merchant_order_id, $razorpay_order_id, $this->config->get('payment_razorpay_order_status_id'));
}
$this->response->redirect($this->url->link('checkout/success', '', true));
}
Expand Down Expand Up @@ -385,6 +385,7 @@ public function webhook()
}

$this->load->model('checkout/order');
$this->load->model('extension/payment/razorpay');
$enabled = $this->config->get('payment_razorpay_webhook_status');

if (($enabled === '1') and
Expand All @@ -403,27 +404,40 @@ public function webhook()
return;
}

switch ($data['event'])
if (in_array($data['event'], [self::ORDER_PAID, self::PAYMENT_AUTHORIZED]) === true)
{
case self::PAYMENT_AUTHORIZED:
return $this->paymentAuthorized($data);

case self::PAYMENT_FAILED:
return $this->paymentFailed($data);

case self::ORDER_PAID:
return $this->orderPaid($data);

case self::SUBSCRIPTION_PAUSED:
case self::SUBSCRIPTION_RESUMED:
case self::SUBSCRIPTION_CANCELLED:
return $this->updateOcSubscriptionStatus($data);

case self::SUBSCRIPTION_CHARGED:
return $this->processSubscriptionCharged($data);
$webhookFilteredData = [
"id" => $data['payload']['payment']['entity']['id'],
"event" => $data['event'],
"opencart_order_id" => $data['payload']['payment']['entity']['notes']['opencart_order_id']
];

default:
return;
if ($data['event'] === self::ORDER_PAID)
{
$webhookFilteredData['invoice_id'] = $data['payload']['payment']['entity']['invoice_id'];
sleep(3);
}
$this->model_extension_payment_razorpay->addWebhookEvent(
$data['payload']['payment']['entity']['notes']['opencart_order_id'],
$data['payload']['payment']['entity']['order_id'],
$webhookFilteredData
);
}
else
{
switch ($data['event'])
{
case self::PAYMENT_FAILED:
return $this->paymentFailed($data);
case self::SUBSCRIPTION_PAUSED:
case self::SUBSCRIPTION_RESUMED:
case self::SUBSCRIPTION_CANCELLED:
return $this->updateOcSubscriptionStatus($data);
case self::SUBSCRIPTION_CHARGED:
return $this->processSubscriptionCharged($data);
default:
return;
}
}
}
}
Expand All @@ -435,18 +449,10 @@ public function webhook()
*/
protected function orderPaid(array $data)
{
$payment_created_time = $data['payload']['payment']['entity']['created_at'];

if (time() < ($payment_created_time + self::WEBHOOK_WAIT_TIME))
{
header('Status: 409 Webhook conflicts due to early execution.', true, self::HTTP_CONFLICT_STATUS);
return;
}

// Do not process if order is subscription type
if (isset($post['payload']['payment']['entity']['invoice_id']) === true)
if (isset($data['invoice_id']) === true)
{
$rzpInvoiceId = $post['payload']['payment']['entity']['invoice_id'];
$rzpInvoiceId = $data['invoice_id'];
$invoice = $this->api->invoice->fetch($rzpInvoiceId);
if (isset($invoice->subscription_id))
{
Expand All @@ -455,8 +461,8 @@ protected function orderPaid(array $data)
}

// reference_no (opencart_order_id) should be passed in payload
$merchant_order_id = $data['payload']['payment']['entity']['notes']['opencart_order_id'];
$razorpay_payment_id = $data['payload']['payment']['entity']['id'];
$merchant_order_id = $data['opencart_order_id'];
$razorpay_payment_id = $data['id'];

if (isset($merchant_order_id) === true)
{
Expand Down Expand Up @@ -489,18 +495,10 @@ protected function paymentAuthorized(array $data)
{
return;
}
//verify if we need to consume it as late authorized
$payment_created_time = $data['payload']['payment']['entity']['created_at'];

if (time() < ($payment_created_time + self::WEBHOOK_WAIT_TIME))
{
header('Status: 409 Webhook conflicts due to early execution.', true, self::HTTP_CONFLICT_STATUS);
return;
}

// reference_no (opencart_order_id) should be passed in payload
$merchant_order_id = $data['payload']['payment']['entity']['notes']['opencart_order_id'];
$razorpay_payment_id = $data['payload']['payment']['entity']['id'];
$merchant_order_id = $data['opencart_order_id'];
$razorpay_payment_id = $data['id'];

//update the order
if (isset($merchant_order_id) === true)
Expand All @@ -526,7 +524,6 @@ protected function paymentAuthorized(array $data)
'currency' => $order_info['currency_code']
));
}

//update the order status in store
$this->model_checkout_order->addOrderHistory($merchant_order_id, $this->config->get('payment_razorpay_order_status_id'), 'Payment Successful. Razorpay Payment Id:' . $razorpay_payment_id);
$this->log->write("order:$merchant_order_id updated by razorpay payment.authorized event");
Expand Down Expand Up @@ -914,7 +911,7 @@ public function pause()
$subscription_id = 0;
}

try
try
{
$subscriptionData = $this->api->subscription->fetch($subscription_id)->pause(array('pause_at' => 'now'));
$this->load->model('extension/payment/razorpay');
Expand Down Expand Up @@ -1089,7 +1086,7 @@ protected function processSubscriptionCharged($data)
if ($subscription['paid_count'] == 1)
{
if (in_array($rzpSubscription['status'], ['created', 'authenticated']) and
$rzpSubscription['paid_count'] == 0)
$rzpSubscription['paid_count'] == 0)
{
$this->model_extension_payment_razorpay->updateSubscription($subscription, $subscriptionId);
$this->model_extension_payment_razorpay->updateOCRecurringStatus($merchant_order_id, 1);
Expand All @@ -1113,9 +1110,41 @@ protected function processSubscriptionCharged($data)

$this->model_checkout_order->addOrderHistory($merchant_order_id, $this->config->get('payment_razorpay_order_status_id'), trim("Subscription charged Successfully. Razorpay Payment Id:" . $paymentId));
$this->log->write("Subscription charged webhook event finished for Opencart OrderID (:" . $merchant_order_id . ")");

return;
}
}
}

/**
* executing payment.authorized and order.paid webhook event using cron
*/
public function rzpWebhookCron()
{
$this->load->model('checkout/order');
$this->load->model('extension/payment/razorpay');

$webhookEvents = $this->model_extension_payment_razorpay->getWebhookEvents(self::WEBHOOK_WAIT_TIME);
foreach ($webhookEvents as $row)
{
$events = json_decode($row['rzp_webhook_data']);
$rzpOrderId = $row['rzp_order_id'];
foreach ($events as $event)
{
$event = (array) $event;
switch ($event['event'])
{
case self::PAYMENT_AUTHORIZED:
$this->paymentAuthorized($event);
break;
case self::ORDER_PAID:
$this->orderPaid($event);
break;
default:
return;
}
$this->model_extension_payment_razorpay->updateOrderForWebhook($event['opencart_order_id'], $rzpOrderId, 2);
}
}
}
}
48 changes: 48 additions & 0 deletions catalog/model/extension/payment/razorpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,52 @@ public function addOCRecurringTransaction($orderRecurringId, $subscriptionId, $a
$this->rzpPdo->bindParam(':amount', (float)$amount);
$this->rzpPdo->execute();
}

public function addOrderForWebhook($orderId, $rzpOrderId, $razorpayOrderStatusCron = 0)
{
$this->rzpPdo->prepare("INSERT INTO `" . DB_PREFIX . "rzp_webhook_triggers` SET order_id=:order_id, rzp_order_id=:rzp_order_id, rzp_webhook_data=:rzp_webhook_data, rzp_update_order_cron_status=:rzp_update_order_cron_status");
$this->rzpPdo->bindParam(':order_id', (int)$orderId);
$this->rzpPdo->bindParam(':rzp_order_id', $this->db->escape($rzpOrderId));
$this->rzpPdo->bindParam(':rzp_webhook_data', '[]');
$this->rzpPdo->bindParam(':rzp_update_order_cron_status', $razorpayOrderStatusCron);
$this->rzpPdo->execute();
}

public function updateOrderForWebhook($orderId, $rzpOrderId, $rzpUpdateOrderCronStatus)
{
$this->rzpPdo->prepare("UPDATE " . DB_PREFIX . "rzp_webhook_triggers SET rzp_update_order_cron_status=:rzp_update_order_cron_status WHERE order_id=:order_id AND rzp_order_id=:rzp_order_id");
$this->rzpPdo->bindParam(':rzp_update_order_cron_status', $rzpUpdateOrderCronStatus);
$this->rzpPdo->bindParam(':order_id', (int)$orderId);
$this->rzpPdo->bindParam(':rzp_order_id', $rzpOrderId);
$this->rzpPdo->execute();
}

public function addWebhookEvent($orderId, $rzpOrderId, $webhookFilteredData)
{
$this->rzpPdo->prepare("SELECT rzp_webhook_data FROM " . DB_PREFIX . "rzp_webhook_triggers WHERE order_id=:orderId AND rzp_order_id=:rzp_order_id");
$this->rzpPdo->bindParam(':orderId', (int)$orderId);
$this->rzpPdo->bindParam(':rzp_order_id', $rzpOrderId);
$query = $this->rzpPdo->execute();
$webhookEvents = $query->row;

$rzpWebhookData = (array) json_decode($webhookEvents['rzp_webhook_data']);

$rzpWebhookData[] = $webhookFilteredData;

$this->rzpPdo->prepare("UPDATE " . DB_PREFIX . "rzp_webhook_triggers SET rzp_webhook_data=:rzp_webhook_data, rzp_webhook_notified_at=:rzp_webhook_notified_at WHERE order_id=:order_id AND rzp_order_id=:rzp_order_id");
$this->rzpPdo->bindParam(':rzp_webhook_data', json_encode($rzpWebhookData));
$this->rzpPdo->bindParam(':rzp_webhook_notified_at', time());
$this->rzpPdo->bindParam(':order_id', (int)$orderId);
$this->rzpPdo->bindParam(':rzp_order_id', $rzpOrderId);
$this->rzpPdo->execute();
}

public function getWebhookEvents($webhookWaitTime)
{
$this->rzpPdo->prepare("SELECT rzp_order_id, rzp_webhook_data FROM " . DB_PREFIX . "rzp_webhook_triggers WHERE rzp_webhook_notified_at<:webhook_wait_time AND rzp_update_order_cron_status=:rzp_update_order_cron_status");
$this->rzpPdo->bindParam(':webhook_wait_time', (string)(time() - $webhookWaitTime));
$this->rzpPdo->bindParam(':rzp_update_order_cron_status', 0);
$query = $this->rzpPdo->execute();
return $query->rows;
}
}

0 comments on commit 8ab693d

Please sign in to comment.