--- a/woo-razorpay/includes/api/api.php
+++ b/woo-razorpay/includes/api/api.php
@@ -36,7 +36,7 @@
array(
'methods' => 'POST',
'callback' => 'getCouponList',
- 'permission_callback' => 'checkAuthCredentials',
+ 'permission_callback' => 'checkHmacSignature',
)
);
--- a/woo-razorpay/includes/api/auth.php
+++ b/woo-razorpay/includes/api/auth.php
@@ -4,9 +4,57 @@
* custom auth to secure 1cc APIs
*/
+require_once __DIR__ . '/../../razorpay-sdk/Razorpay.php';
+use RazorpayApiErrors;
+
function checkAuthCredentials()
{
return true;
}
+/**
+ * Validate HMAC signature using Razorpay Webhook Secret.
+ * Expects header 'X-Razorpay-Signature' computed over raw request body with HMAC-SHA256.
+ *
+ * @param WP_REST_Request $request
+ * @return bool|WP_Error
+ */
+function checkHmacSignature($request)
+{
+ $signature = '';
+ if (isset($_SERVER['HTTP_X_RAZORPAY_SIGNATURE']))
+ {
+ $signature = sanitize_text_field($_SERVER['HTTP_X_RAZORPAY_SIGNATURE']);
+ }
+
+ if (empty($signature))
+ {
+ return new WP_Error('rest_forbidden', __('Signature missing'), array('status' => 403));
+ }
+
+ $payload = file_get_contents('php://input');
+
+ // Retrieve 1CC signing HMAC secret saved at plugin load time
+ $secret = get_option('rzp1cc_hmac_secret');
+
+ if (empty($secret))
+ {
+ return new WP_Error('rest_forbidden', __('Secret not configured'), array('status' => 403));
+ }
+
+ // Verify using Razorpay SDK (same as webhook)
+ try
+ {
+ $rzp = new WC_Razorpay(false);
+ $api = $rzp->getRazorpayApiInstance();
+ $api->utility->verifySignature($payload, $signature, $secret);
+ }
+ catch (ErrorsSignatureVerificationError $e)
+ {
+ return new WP_Error('rest_forbidden', __('Invalid signature'), array('status' => 403));
+ }
+
+ return true;
+}
+
?>
--- a/woo-razorpay/includes/api/cart.php
+++ b/woo-razorpay/includes/api/cart.php
@@ -205,7 +205,18 @@
$type = "e-commerce";
$productDetails = $product->get_data();
+ $sku = $product->get_sku();
+ $productImage = $product->get_image_id()?? null;
+ // If variation exists, use its image and SKU instead of parent product
+ if (!empty($item['variation_id'])) {
+ $variation_id = (int) $item['variation_id'];
+ $variation = wc_get_product( $variation_id );
+ if ( $variation && $variation->is_type( 'variation' ) ) {
+ $productImage = $variation->get_image_id();
+ $sku = $variation->get_sku() ?: $sku;
+ }
+ }
// check product type for gift card plugin
if(is_plugin_active('pw-woocommerce-gift-cards/pw-gift-cards.php') || is_plugin_active('yith-woocommerce-gift-cards/init.php')){
if($product->is_type('variation')){
@@ -225,13 +236,12 @@
}
$data[$i]['type'] = $type;
- $data[$i]['sku'] = $product->get_sku();
+ $data[$i]['sku'] = $sku;
$data[$i]['quantity'] = $item['quantity'];
$data[$i]['name'] = mb_substr($product->get_title(), 0, 125, "UTF-8");
$data[$i]['description'] = mb_substr($product->get_title(), 0, 250,"UTF-8");
- $productImage = $product->get_image_id()?? null;
$data[$i]['product_id'] = $item['product_id'];
- $data[$i]['image_url'] = $productImage? wp_get_attachment_url( $productImage ) : null;
+ $data[$i]['image_url'] = $productImage? wp_get_attachment_url( $productImage ) : null;
$data[$i]['product_url'] = $product->get_permalink();
$data[$i]['price'] = (empty($productDetails['price']) === false) ? (int)$productDetails['price'] * 100 / $item['quantity'] : 0;
$data[$i]['variant_id'] = $item['variation_id'];
--- a/woo-razorpay/includes/api/coupon-apply.php
+++ b/woo-razorpay/includes/api/coupon-apply.php
@@ -3,6 +3,7 @@
/**
* for coupon related API
*/
+require_once __DIR__.'/../support/smart-coupons.php';
function applyCouponOnCart(WP_REST_Request $request)
{
@@ -46,12 +47,22 @@
// initializes the session
initCustomerSessionAndCart();
- // Set current user for smart coupon plugin
if (is_plugin_active('wt-smart-coupons-for-woocommerce/wt-smart-coupon.php')) {
+ // Set current user for smart coupon plugin
if (empty($email) === false) {
$user = get_user_by('email', $email);
wp_set_current_user($user->id);
}
+
+ // Apply Smart Coupon if allowed for selected payment method (e.g., Razorpay)
+ try
+ {
+ smartCouponPaymentRestriction($couponCode);
+ }
+ catch ( Throwable $e )
+ {
+ rzpLogError('Smart Coupon restricted by payment method : ' . $e->getMessage());
+ }
}
// check for individual specific coupons
--- a/woo-razorpay/includes/api/coupon-get.php
+++ b/woo-razorpay/includes/api/coupon-get.php
@@ -50,6 +50,16 @@
return new WP_REST_Response($response, $statusCode);
}
+ // Allow listing coupons only if order still requires payment
+ if ($order->needs_payment() === false)
+ {
+ $response['failure_reason'] = 'Order not eligible for coupons';
+ $response['failure_code'] = 'INVALID_STATE';
+ $statusCode = 403;
+
+ return new WP_REST_Response($response, $statusCode);
+ }
+
$amount = floatval($order->get_total());
$email = sanitize_text_field($request->get_params()['email']);
$contact = sanitize_text_field($request->get_params()['contact']);
--- a/woo-razorpay/includes/razorpay-webhook.php
+++ b/woo-razorpay/includes/razorpay-webhook.php
@@ -153,7 +153,7 @@
case self::PAYMENT_AUTHORIZED:
$webhookFilteredData = [
'invoice_id' => $data['payload']['payment']['entity']['invoice_id'],
- 'woocommerce_order_id' => $data['payload']['payment']['entity']['notes']['woocommerce_order_id'],
+ 'woocommerce_order_id' => $data['payload']['payment']['entity']['notes']['woocommerce_order_id'],
'razorpay_payment_id' => $data['payload']['payment']['entity']['id'],
'event' => $data['event']
];
@@ -295,20 +295,20 @@
return;
}
- //
- // Order entity should be sent as part of the webhook payload
- //
+ if (empty($data['woocommerce_order_id'])) {
+ rzpLogInfo("woocommerce_order_id not found in data:" . json_encode($data));
+ return;
+ }
+
$orderId = $data['woocommerce_order_id'];
- rzpLogInfo("Woocommerce orderId: $orderId, webhook process intitiated for payment authorized event by cron");
+ rzpLogInfo("Woocommerce orderId: $orderId, webhook process initiated for payment authorized event by cron");
- if (!empty($orderId)) {
- $order = $this->checkIsObject($orderId);
- if ($order === false)
- {
- return;
- }
+ $order = $this->checkIsObject($orderId);
+ if ($order === false)
+ {
+ return;
}
$orderStatus = $order->get_status();
--- a/woo-razorpay/includes/support/smart-coupons.php
+++ b/woo-razorpay/includes/support/smart-coupons.php
@@ -0,0 +1,26 @@
+<?php
+//Support for smart coupon plugin - restricted by payment method options
+function smartCouponPaymentRestriction($couponCode){
+ $coupon = new WC_Coupon($couponCode);
+
+ // Get payment methods meta
+ $methodsMeta = get_post_meta( $coupon->get_id(), '_wt_sc_payment_methods', true );
+
+ // Normalize to array (handle both array and comma-separated string cases)
+ // - If it's already an array, use it as-is.
+ // - If it's a non-empty comma-separated string, split by commas and remove extra spaces or empty values.
+ // - Otherwise, use an empty array.
+ $methods = is_array($methodsMeta)
+ ? $methodsMeta
+ : (is_string($methodsMeta) && $methodsMeta !== ''
+ ? array_filter(preg_split('/s*,s*/', $methodsMeta))
+ : array());
+
+ // Normalize case (lowercase all methods)
+ $methods = array_map( 'strtolower', $methods );
+
+ // Check if Razorpay is allowed
+ if ( in_array( 'razorpay', $methods, true ) && function_exists( 'WC' ) && WC()->session ) {
+ WC()->session->set( 'chosen_payment_method', 'razorpay' );
+ }
+}
No newline at end of file
--- a/woo-razorpay/tests/phpunit/tests/test-instrumentation.php
+++ b/woo-razorpay/tests/phpunit/tests/test-instrumentation.php
@@ -256,7 +256,7 @@
$this->assertSame('WordPress', $response['platform']);
$this->assertNotNull($response['platform_version']);
$this->assertNotNull($response['woocommerce_version']);
- $this->assertSame('1 Razorpay: Signup for FREE PG', $response['plugin_name']);
+ $this->assertSame('1 Razorpay', $response['plugin_name']);
$this->assertNotNull($response['plugin_version']);
$this->assertNotNull($response['unique_id']);
$this->assertNotNull($response['event_timestamp']);
--- a/woo-razorpay/tests/phpunit/tests/test-metadata.php
+++ b/woo-razorpay/tests/phpunit/tests/test-metadata.php
@@ -6,7 +6,7 @@
{
$pluginData = get_plugin_data(PLUGIN_DIR . '/woo-razorpay.php');
- $this->assertSame('1 Razorpay: Signup for FREE PG', $pluginData['Name']);
+ $this->assertSame('1 Razorpay', $pluginData['Name']);
$version = $pluginData['Version'];
$v = explode(".", $version);
--- a/woo-razorpay/woo-razorpay.php
+++ b/woo-razorpay/woo-razorpay.php
@@ -1,10 +1,10 @@
<?php
/*
- * Plugin Name: 1 Razorpay: Signup for FREE PG
+ * Plugin Name: 1 Razorpay
* Plugin URI: https://razorpay.com
* Description: Razorpay Payment Gateway Integration for WooCommerce.Razorpay Welcome Back Offer: New to Razorpay? Sign up to enjoy FREE payments* of INR 2 lakh till March 31st! Transact before January 10th to grab the offer.
- * Version: 4.7.8
- * Stable tag: 4.7.8
+ * Version: 4.7.9
+ * Stable tag: 4.7.9
* Author: Team Razorpay
* WC tested up to: 10.3.4
* Author URI: https://razorpay.com
@@ -34,6 +34,7 @@
require_once __DIR__.'/includes/cron/cron.php';
require_once __DIR__.'/includes/cron/plugin-fetch.php';
require_once ABSPATH . '/wp-admin/includes/upgrade.php';
+require_once __DIR__.'/includes/support/smart-coupons.php';
use RazorpayApiApi;
use RazorpayApiErrors;
@@ -81,6 +82,80 @@
}
}
+/**
+ * Ensure 1CC signing secret is present and in sync with current keys.
+ * - If rzp1cc_hmac_secret is missing or key_id changed, generate and register it via internal API.
+ */
+add_action('plugins_loaded', 'rzpWcEnsure1ccSecret', 20);
+
+function rzpWcEnsure1ccSecret()
+{
+ // Only act when Magic Checkout is enabled
+ if (!(function_exists('is1ccEnabled') && is1ccEnabled()))
+ {
+ return;
+ }
+
+ // Refresh secret if key_id changed since last run OR if secret is missing
+ $settings = get_option('woocommerce_razorpay_settings');
+ $settings = (is_array($settings)) ? $settings : array();
+ $currentKeyId = isset($settings['key_id']) ? $settings['key_id'] : '';
+ $lastKeyId = get_option('rzp_wc_last_key_id');
+ $existingSecret = get_option('rzp1cc_hmac_secret');
+
+ $hasKeyChanged = (!empty($currentKeyId) && $currentKeyId !== $lastKeyId);
+ $isSecretMissing = empty($existingSecret);
+
+ if ($hasKeyChanged || $isSecretMissing)
+ {
+ // Short-lived lock to avoid concurrent duplicate registrations across rapid requests
+ $lockKey = 'rzp_wc_ensure_1cc_secret_lock';
+ if (get_transient($lockKey))
+ {
+ return;
+ }
+ // hold for 30s to coalesce bursts; will be deleted on completion
+ set_transient($lockKey, 1, 30);
+
+ $secretUpdated = false;
+ try
+ {
+ // Validate credentials once to avoid internal re-fetches
+ $currentKeySec = isset($settings['key_secret']) ? $settings['key_secret'] : '';
+ if (empty($currentKeyId) || empty($currentKeySec))
+ {
+ // Release the lock before exiting early
+ delete_transient($lockKey);
+ return;
+ }
+
+ $rzp = new WC_Razorpay(false);
+ $newSecret = $rzp->registerRzp1ccSigningSecret($currentKeyId, $currentKeySec);
+ if ($newSecret)
+ {
+ update_option('rzp1cc_hmac_secret', $newSecret, false);
+ $secretUpdated = true;
+ rzpLogInfo("1cc hmac secret is Added/Updated");
+ }
+ }
+ catch (Exception $e)
+ {
+ rzpLogError("Refresh 1cc hmac secret on key change failed: ". $e->getMessage());
+ // Release the lock on failure as well
+ delete_transient($lockKey);
+ return;
+ }
+
+ if ($hasKeyChanged && $secretUpdated === true)
+ {
+ // Persist the observed key to detect future changes (add or update; do not autoload)
+ update_option('rzp_wc_last_key_id', $currentKeyId, 'no');
+ }
+ // Release the lock
+ delete_transient($lockKey);
+ }
+}
+
function woocommerce_razorpay_init()
{
add_action("woocommerce_update_options_advanced", function()
@@ -891,6 +966,43 @@
return $secret;
}
+ /**
+ * Register 1CC signing secret with Razorpay internal API using private auth.
+ * Caller MUST pass credentials; this method will not fetch from settings.
+ *
+ * @param string $keyId
+ * @param string $keySec
+ * @return string|false The registered secret on success, false otherwise
+ */
+ public function registerRzp1ccSigningSecret($keyId, $keySec)
+ {
+ // Always generate a new secret for registration
+ $secret = $this->generateSecret();
+
+ $payload = array(
+ 'key_id' => $keyId,
+ 'platform' => 'woocommerce',
+ 'secret' => $secret,
+ );
+
+ try
+ {
+ $api = $this->getRazorpayApiInstance($keyId, $keySec);
+ $response = $api->request->request('POST', 'magic/merchant/auth/secret', $payload);
+
+ if (is_array($response) && isset($response['success']) && $response['success'] === true)
+ {
+ return $secret;
+ }
+ return false;
+ }
+ catch (Exception $e)
+ {
+ rzpLogError("Register 1cc signing secret failed: " . $e->getMessage());
+ return false;
+ }
+ }
+
// showing notice : status of 1cc active / inactive message in admin dashboard
function addAdminCheckoutSettingsAlert() {
$enable_1cc = $this->getSetting('enable_1cc');
@@ -2983,6 +3095,18 @@
$order_item->set_total($total - $discount_total);
$order_item->save();
}
+
+ // Apply Smart Coupon if allowed for selected payment method (e.g., Razorpay)
+ if (is_plugin_active('wt-smart-coupons-for-woocommerce/wt-smart-coupon.php')) {
+ try
+ {
+ smartCouponPaymentRestriction($couponKey);
+ }
+ catch ( Throwable $e )
+ {
+ rzpLogError('Smart Coupon restricted by payment method : ' . $e->getMessage());
+ }
+ }
// TODO: Test if individual use coupon fails by hardcoding here
$isApplied = $order->apply_coupon($couponKey);
$order->save();