Razorpay for WooCommerce - Version 1.6.0

Version Description

  • Adds Razorpay Subscriptions plugin support.
  • Code cleanup.
Download this release

Release Info

Developer razorpay
Plugin Icon 128x128 Razorpay for WooCommerce
Version 1.6.0
Comparing to
See all releases

Code changes from version 1.6.0-beta to 1.6.0

.editorconfig ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ; This file is for unifying the coding style for different editors and IDEs.
2
+ ; More information at http://EditorConfig.org
3
+
4
+ root = true
5
+ ; Use 2 spaces for indentation in all files
6
+
7
+ [*.php]
8
+ end_of_line = lf
9
+ charset = utf-8
10
+ trim_trailing_whitespace = true
11
+ indent_style = space
12
+ indent_size = 4
13
+ insert_final_newline = true
images/logo.png CHANGED
Binary file
includes/Errors/ErrorCode.php CHANGED
@@ -15,10 +15,4 @@ class ErrorCode extends ApiErrors\ErrorCode
15
  const WOOCS_MISSING_ERROR_MESSAGE = 'The WooCommerce Currency Switcher plugin is missing.';
16
  const INVALID_CURRENCY_ERROR_MESSAGE = 'The selected currency is invalid.';
17
  const WOOCS_CURRENCY_MISSING_ERROR_MESSAGE = 'Woocommerce Currency Switcher plugin is not configured with INR correctly';
18
-
19
- // Subscription related errors
20
- const API_SUBSCRIPTION_CREATION_FAILED = 'Razorpay API subscription creation failed';
21
- const API_SUBSCRIPTION_CANCELLATION_FAILED = 'Razorpay API subscription cancellation failed';
22
- const API_PLAN_CREATION_FAILED = 'Razorpay API plan creation failed';
23
- const API_CUSTOMER_CREATION_FAILED = 'Razorpay API customer creation failed';
24
  }
15
  const WOOCS_MISSING_ERROR_MESSAGE = 'The WooCommerce Currency Switcher plugin is missing.';
16
  const INVALID_CURRENCY_ERROR_MESSAGE = 'The selected currency is invalid.';
17
  const WOOCS_CURRENCY_MISSING_ERROR_MESSAGE = 'Woocommerce Currency Switcher plugin is not configured with INR correctly';
 
 
 
 
 
 
18
  }
includes/razorpay-subscriptions.php DELETED
@@ -1,334 +0,0 @@
1
- <?php
2
-
3
- require_once __DIR__.'/../razorpay-sdk/Razorpay.php';
4
-
5
- use Razorpay\Api\Api;
6
- use Razorpay\Api\Errors;
7
- use Razorpay\Woocommerce\Errors as WooErrors;
8
-
9
- class RZP_Subscriptions
10
- {
11
- protected $razorpay;
12
-
13
- const RAZORPAY_PLAN_ID = 'razorpay_wc_plan_id';
14
- const INR = 'INR';
15
-
16
- public function __construct($keyId, $keySecret)
17
- {
18
- $this->api = new Api($keyId, $keySecret);
19
-
20
- $this->razorpay = new WC_Razorpay();
21
- }
22
-
23
- public function createSubscription($orderId)
24
- {
25
- global $woocommerce;
26
-
27
- $subscriptionData = $this->getSubscriptionCreateData($orderId);
28
-
29
- try
30
- {
31
- $subscription = $this->api->subscription->create($subscriptionData);
32
- }
33
- catch (Exception $e)
34
- {
35
- $message = $e->getMessage();
36
-
37
- throw new Errors\Error(
38
- $message,
39
- WooErrors\ErrorCode::API_SUBSCRIPTION_CREATION_FAILED,
40
- 400
41
- );
42
- }
43
-
44
- // Setting the subscription id as the session variable
45
- $sessionKey = $this->getSubscriptionSessionKey($orderId);
46
- $woocommerce->session->set($sessionKey, $subscription['id']);
47
-
48
- return $subscription['id'];
49
- }
50
-
51
- public function cancelSubscription($subscriptionId)
52
- {
53
- try
54
- {
55
- $subscription = $this->api->subscription->cancel($subscriptionId);
56
- }
57
- catch (Exception $e)
58
- {
59
- $message = $e->getMessage();
60
-
61
- throw new Errors\Error(
62
- $message,
63
- WooErrors\ErrorCode::API_SUBSCRIPTION_CANCELLATION_FAILED,
64
- 400
65
- );
66
- }
67
- }
68
-
69
- protected function getSubscriptionCreateData($orderId)
70
- {
71
- $order = new WC_Order($orderId);
72
-
73
- $product = $this->getProductFromOrder($order);
74
-
75
- $planId = $this->getProductPlanId($product);
76
-
77
- $customerId = $this->getCustomerId($order);
78
-
79
- $length = (int) WC_Subscriptions_Product::get_length($product['product_id']);
80
-
81
- $subscriptionData = array(
82
- 'customer_id' => $customerId,
83
- 'plan_id' => $planId,
84
- 'quantity' => (int) $product['qty'],
85
- 'total_count' => $length,
86
- 'customer_notify' => 0,
87
- 'notes' => array(
88
- 'woocommerce_order_id' => $orderId,
89
- 'woocommerce_product_id' => $product['product_id']
90
- ),
91
- );
92
-
93
- $signUpFee = WC_Subscriptions_Product::get_sign_up_fee($product['product_id']);
94
-
95
- if ($signUpFee)
96
- {
97
- $item = array(
98
- 'amount' => (int) round($signUpFee * 100),
99
- 'currency' => get_woocommerce_currency(),
100
- 'name' => $product['name']
101
- );
102
-
103
- if ($item['currency'] !== self::INR)
104
- {
105
- $this->razorpay->handleCurrencyConversion($item);
106
- }
107
-
108
- $subscriptionData['addons'] = array(array('item' => $item));
109
- }
110
-
111
- return $subscriptionData;
112
- }
113
-
114
- protected function getProductPlanId($product)
115
- {
116
- $currency = get_woocommerce_currency();
117
-
118
- $key = self::RAZORPAY_PLAN_ID . '_'. strtolower($currency);
119
-
120
- $productId = $product['product_id'];
121
-
122
- $metadata = get_post_meta($productId);
123
-
124
- list($planId, $created) = $this->createOrGetPlanId($metadata, $product, $key);
125
-
126
- //
127
- // If new plan was created, we delete the old plan id
128
- // If we created a new planId, we have to store it as post metadata
129
- //
130
- if ($created === true)
131
- {
132
- delete_post_meta($productId, $key);
133
-
134
- add_post_meta($productId, $key, $planId, true);
135
- }
136
-
137
- return $planId;
138
- }
139
-
140
- /**
141
- * Takes in product metadata and product
142
- * Creates or gets created plan
143
- *
144
- * @param $metadata,
145
- * @param $product
146
- *
147
- * @return string $planId
148
- * @return bool $created
149
- */
150
- protected function createOrGetPlanId($metadata, $product, $key)
151
- {
152
- $planArgs = $this->getPlanArguments($product);
153
-
154
- //
155
- // If razorpay_plan_id is set in the metadata,
156
- // we check if the amounts match and return the plan id
157
- //
158
- if (isset($metadata[$key]) === true)
159
- {
160
- $create = false;
161
-
162
- $planId = $metadata[$key][0];
163
-
164
- try
165
- {
166
- $plan = $this->api->plan->fetch($planId);
167
- }
168
- catch (Exception $e)
169
- {
170
- //
171
- // If plan id fetch causes an error, we re-create the plan
172
- //
173
- $create = true;
174
- }
175
-
176
- if (($create === false) and
177
- ($plan['item']['amount'] === $planArgs['item']['amount']))
178
- {
179
- return array($plan['id'], false);
180
- }
181
- }
182
-
183
- //
184
- // By default we create a new plan
185
- // if metadata doesn't have plan id set
186
- //
187
- $planId = $this->createPlan($planArgs);
188
-
189
- return array($planId, true);
190
- }
191
-
192
- protected function createPlan($planArgs)
193
- {
194
- try
195
- {
196
- $plan = $this->api->plan->create($planArgs);
197
- }
198
- catch (Exception $e)
199
- {
200
- $message = $e->getMessage();
201
-
202
- throw new Errors\Error(
203
- $message,
204
- WooErrors\ErrorCode::API_PLAN_CREATION_FAILED,
205
- 400
206
- );
207
- }
208
-
209
- // Storing the plan id as product metadata, unique set to true
210
- return $plan['id'];
211
- }
212
-
213
- protected function getPlanArguments($product)
214
- {
215
- $productId = $product['product_id'];
216
-
217
- $period = WC_Subscriptions_Product::get_period($productId);
218
- $interval = WC_Subscriptions_Product::get_interval($productId);
219
- $recurringFee = WC_Subscriptions_Product::get_price($productId);
220
-
221
- //
222
- // Ad-Hoc code
223
- //
224
- if ($period === 'year')
225
- {
226
- $period = 'month';
227
-
228
- $interval *= 12;
229
- }
230
-
231
- $planArgs = array(
232
- 'period' => $this->getProductPeriod($period),
233
- 'interval' => $interval
234
- );
235
-
236
- $item = array(
237
- 'name' => $product['name'],
238
- 'amount' => (int) round($recurringFee * 100),
239
- 'currency' => get_woocommerce_currency(),
240
- );
241
-
242
- if ($item['currency'] !== self::INR)
243
- {
244
- $this->razorpay->handleCurrencyConversion($item);
245
- }
246
-
247
- $planArgs['item'] = $item;
248
-
249
- return $planArgs;
250
- }
251
-
252
- public function getDisplayAmount($orderId)
253
- {
254
- $order = new WC_Order($orderId);
255
-
256
- $product = $this->getProductFromOrder($order);
257
-
258
- $productId = $product['product_id'];
259
-
260
- $recurringFee = WC_Subscriptions_Product::get_price($productId);
261
-
262
- $signUpFee = WC_Subscriptions_Product::get_sign_up_fee($productId);
263
-
264
- return $recurringFee + $signUpFee;
265
- }
266
-
267
- protected function getProductPeriod($period)
268
- {
269
- $periodMap = array(
270
- 'day' => 'daily',
271
- 'week' => 'weekly',
272
- 'month' => 'monthly',
273
- 'year' => 'yearly'
274
- );
275
-
276
- return $periodMap[$period];
277
- }
278
-
279
- protected function getCustomerId($order)
280
- {
281
- $data = $this->razorpay->getCustomerInfo($order);
282
-
283
- //
284
- // This line of code tells api that if a customer is already created,
285
- // return the created customer instead of throwing an exception
286
- // https://docs.razorpay.com/v1/page/customers-api
287
- //
288
- $data['fail_existing'] = '0';
289
-
290
- try
291
- {
292
- $customer = $this->api->customer->create($data);
293
- }
294
- catch (Exception $e)
295
- {
296
- $message = $e->getMessage();
297
-
298
- throw new Errors\Error(
299
- $message,
300
- WooErrors\ErrorCode::API_CUSTOMER_CREATION_FAILED,
301
- 400
302
- );
303
- }
304
-
305
- return $customer['id'];
306
- }
307
-
308
- public function getProductFromOrder($order)
309
- {
310
- $products = $order->get_items();
311
-
312
- //
313
- // Technically, subscriptions work only if there's one array in the cart
314
- //
315
- if (sizeof($products) > 1)
316
- {
317
- throw new Exception('Currently Razorpay does not support more than'
318
- . ' one product in the cart if one of the products'
319
- . ' is a subscription.');
320
- }
321
-
322
- return array_values($products)[0];
323
- }
324
-
325
- protected function getSubscriptionSessionKey($orderId)
326
- {
327
- return 'razorpay_subscription_id' . $orderId;
328
- }
329
-
330
- protected function getRazorpayApiInstance()
331
- {
332
- return new Api($this->keyId, $this->keySecret);
333
- }
334
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/razorpay-webhook.php CHANGED
@@ -8,18 +8,43 @@ use Razorpay\Api\Errors;
8
 
9
  class RZP_Webhook
10
  {
11
- const RAZORPAY_SUBSCRIPTION_ID = 'razorpay_subscription_id';
12
-
 
 
13
  protected $razorpay;
 
 
 
 
 
14
  protected $api;
15
 
 
 
 
 
 
 
16
  function __construct()
17
  {
18
- $this->razorpay = new WC_Razorpay();
19
 
20
  $this->api = $this->razorpay->getRazorpayApiInstance();
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  public function process()
24
  {
25
  $post = file_get_contents('php://input');
@@ -31,11 +56,14 @@ class RZP_Webhook
31
  return;
32
  }
33
 
34
- if ($this->razorpay->enable_webhook === 'yes' && empty($data['event']) === false)
 
 
 
35
  {
36
- if ((isset($_SERVER['HTTP_X_RAZORPAY_SIGNATURE']) === true))
37
  {
38
- $razorpayWebhookSecret = $this->razorpay->webhook_secret;
39
 
40
  //
41
  // If the webhook secret isn't set on wordpress, return
@@ -56,25 +84,21 @@ class RZP_Webhook
56
  $log = array(
57
  'message' => $e->getMessage(),
58
  'data' => $data,
59
- 'event' => 'razorpay.wc.signature..verify_failed'
60
  );
61
 
62
- write_log($log);
63
  return;
64
  }
65
 
66
  switch ($data['event'])
67
  {
68
- case 'payment.authorized':
69
  return $this->paymentAuthorized($data);
70
 
71
- case 'payment.failed':
72
  return $this->paymentFailed($data);
73
 
74
- // if it is subscription.charged
75
- case 'subscription.charged':
76
- return $this->subscriptionCharged($data);
77
-
78
  default:
79
  return;
80
  }
@@ -82,27 +106,36 @@ class RZP_Webhook
82
  }
83
  }
84
 
 
 
 
 
 
 
 
 
 
85
  /**
86
  * Handling the payment authorized webhook
87
  *
88
- * @param $data
89
  */
90
- protected function paymentAuthorized($data)
91
  {
 
 
 
 
 
 
92
  //
93
  // Order entity should be sent as part of the webhook payload
94
  //
95
  $orderId = $data['payload']['payment']['entity']['notes']['woocommerce_order_id'];
96
 
97
- $paymentId = $data['payload']['payment']['entity']['id'];
98
-
99
- if (isset($data['payload']['payment']['entity']['subscription_id']) === true)
100
- {
101
- return $this->processSubscription($orderId, $paymentId);
102
- }
103
-
104
  $order = new WC_Order($orderId);
105
 
 
106
  if ($order->needs_payment() === false)
107
  {
108
  return;
@@ -117,12 +150,12 @@ class RZP_Webhook
117
  catch (Exception $e)
118
  {
119
  $log = array(
120
- 'message' => $e->getMessage(),
121
- 'data' => $razorpayPaymentId,
122
- 'event' => $data['event']
123
  );
124
 
125
- write_log($log);
126
 
127
  exit;
128
  }
@@ -137,7 +170,7 @@ class RZP_Webhook
137
  $success = true;
138
  }
139
  else if (($payment['status'] === 'authorized') and
140
- ($this->razorpay->payment_action === 'capture'))
141
  {
142
  //
143
  // If the payment is only authorized, we capture it
@@ -150,148 +183,14 @@ class RZP_Webhook
150
 
151
  $this->razorpay->updateOrder($order, $success, $errorMessage, $razorpayPaymentId, true);
152
 
 
153
  exit;
154
  }
155
 
156
- /**
157
- * Currently we handle only subscription failures using this webhook
158
- *
159
- * @param $data
160
- */
161
- protected function paymentFailed($data)
162
- {
163
- //
164
- // Order entity should be sent as part of the webhook payload
165
- //
166
- $orderId = $data['payload']['payment']['entity']['notes']['woocommerce_order_id'];
167
-
168
- $paymentId = $data['payload']['payment']['entity']['id'];
169
-
170
- if (isset($data['payload']['payment']['subscription_id']) === true)
171
- {
172
- $this->processSubscription($orderId, $paymentId, false);
173
- }
174
-
175
- exit;
176
- }
177
-
178
- /**
179
- * Handling the subscription charged webhook
180
- *
181
- * @param $data
182
- */
183
- protected function subscriptionCharged($data)
184
- {
185
- //
186
- // Order entity should be sent as part of the webhook payload
187
- //
188
- $orderId = $data['payload']['subscription']['entity']['notes']['woocommerce_order_id'];
189
-
190
- $this->processSubscription($orderId);
191
-
192
- exit;
193
- }
194
-
195
- /**
196
- * Helper method used to handle all subscription processing
197
- *
198
- * @param $orderId
199
- * @param $paymentId
200
- * @param $success
201
- */
202
- protected function processSubscription($orderId, $paymentId, $success = true)
203
- {
204
- //
205
- // If success is false, automatically process subscription failure
206
- //
207
- if ($success === false)
208
- {
209
- return $this->processSubscriptionFailed($orderId);
210
- }
211
-
212
- $subscriptionId = get_post_meta($orderId, self::RAZORPAY_SUBSCRIPTION_ID)[0];
213
-
214
- $api = $this->razorpay->getRazorpayApiInstance();
215
-
216
- try
217
- {
218
- $subscription = $api->subscription->fetch($subscriptionId);
219
- }
220
- catch (Exception $e)
221
- {
222
- $message = $e->getMessage();
223
- return 'RAZORPAY ERROR: Subscription fetch failed with the message \'' . $message . '\'';
224
- }
225
-
226
- $this->processSubscriptionSuccess($orderId, $subscription, $paymentId);
227
-
228
- exit;
229
- }
230
-
231
- /**
232
- * In the case of successful payment, we mark the subscription successful
233
- *
234
- * @param $wcSubscription
235
- * @param $subscription
236
- */
237
- protected function processSubscriptionSuccess($orderId, $subscription, $paymentId)
238
- {
239
- //
240
- // This method is used to process the subscription's recurring payment
241
- //
242
- $wcSubscription = wcs_get_subscriptions_for_order($orderId);
243
-
244
- $wcSubscriptionId = array_keys($wcSubscription)[0];
245
-
246
- //
247
- // We will only process one subscription per order
248
- //
249
- $wcSubscription = array_values($wcSubscription)[0];
250
-
251
- if (count($wcSubscription) > 1)
252
- {
253
- $log = array(
254
- 'Error' => 'There are more than one subscription products in this order'
255
- );
256
-
257
- write_log($log);
258
-
259
- exit;
260
- }
261
-
262
- $paymentCount = $wcSubscription->get_completed_payment_count();
263
-
264
- //
265
- // The subscription is completely paid for
266
- //
267
- if ($paymentCount === $subscription->total_count)
268
- {
269
- return;
270
- }
271
- else if ($paymentCount + 1 === $subscription->paid_count)
272
- {
273
- //
274
- // If subscription has been paid for on razorpay's end, we need to mark the
275
- // subscription payment to be successful on woocommerce's end
276
- //
277
- WC_Subscriptions_Manager::prepare_renewal($wcSubscriptionId);
278
-
279
- $wcSubscription->payment_complete($paymentId);
280
- }
281
- }
282
-
283
- /**
284
- * In the case of payment failure, we mark the subscription as failed
285
- *
286
- * @param $orderId
287
- */
288
- protected function processSubscriptionFailed($orderId)
289
- {
290
- WC_Subscriptions_Manager::process_subscription_payment_failure_on_order($orderId);
291
- }
292
-
293
  /**
294
  * Returns the order amount, rounded as integer
 
 
295
  */
296
  public function getOrderAmountAsInteger($order)
297
  {
8
 
9
  class RZP_Webhook
10
  {
11
+ /**
12
+ * Instance of the razorpay payments class
13
+ * @var WC_Razorpay
14
+ */
15
  protected $razorpay;
16
+
17
+ /**
18
+ * API client instance to communicate with Razorpay API
19
+ * @var Razorpay\Api\Api
20
+ */
21
  protected $api;
22
 
23
+ /**
24
+ * Event constants
25
+ */
26
+ const PAYMENT_AUTHORIZED = 'payment.authorized';
27
+ const PAYMENT_FAILED = 'payment.failed';
28
+
29
  function __construct()
30
  {
31
+ $this->razorpay = new WC_Razorpay(false);
32
 
33
  $this->api = $this->razorpay->getRazorpayApiInstance();
34
  }
35
 
36
+ /**
37
+ * Process a Razorpay Webhook. We exit in the following cases:
38
+ * - Successful processed
39
+ * - Exception while fetching the payment
40
+ *
41
+ * It passes on the webhook in the following cases:
42
+ * - invoice_id set in payment.authorized
43
+ * - Invalid JSON
44
+ * - Signature mismatch
45
+ * - Secret isn't setup
46
+ * - Event not recognized
47
+ */
48
  public function process()
49
  {
50
  $post = file_get_contents('php://input');
56
  return;
57
  }
58
 
59
+ $enabled = $this->razorpay->getSetting('enable_webhook');
60
+
61
+ if (($enabled === 'yes') and
62
+ (empty($data['event']) === false))
63
  {
64
+ if (isset($_SERVER['HTTP_X_RAZORPAY_SIGNATURE']) === true)
65
  {
66
+ $razorpayWebhookSecret = $this->razorpay->getSetting('webhook_secret');
67
 
68
  //
69
  // If the webhook secret isn't set on wordpress, return
84
  $log = array(
85
  'message' => $e->getMessage(),
86
  'data' => $data,
87
+ 'event' => 'razorpay.wc.signature.verify_failed'
88
  );
89
 
90
+ error_log(json_encode($log));
91
  return;
92
  }
93
 
94
  switch ($data['event'])
95
  {
96
+ case self::PAYMENT_AUTHORIZED:
97
  return $this->paymentAuthorized($data);
98
 
99
+ case self::PAYMENT_FAILED:
100
  return $this->paymentFailed($data);
101
 
 
 
 
 
102
  default:
103
  return;
104
  }
106
  }
107
  }
108
 
109
+ /**
110
+ * Does nothing for the main payments flow currently
111
+ * @param array $data Webook Data
112
+ */
113
+ protected function paymentFailed(array $data)
114
+ {
115
+ return;
116
+ }
117
+
118
  /**
119
  * Handling the payment authorized webhook
120
  *
121
+ * @param array $data Webook Data
122
  */
123
+ protected function paymentAuthorized(array $data)
124
  {
125
+ // We don't process subscription/invoice payments here
126
+ if (isset($data['payload']['payment']['entity']['invoice_id']) === true)
127
+ {
128
+ return;
129
+ }
130
+
131
  //
132
  // Order entity should be sent as part of the webhook payload
133
  //
134
  $orderId = $data['payload']['payment']['entity']['notes']['woocommerce_order_id'];
135
 
 
 
 
 
 
 
 
136
  $order = new WC_Order($orderId);
137
 
138
+ // If it is already marked as paid, ignore the event
139
  if ($order->needs_payment() === false)
140
  {
141
  return;
150
  catch (Exception $e)
151
  {
152
  $log = array(
153
+ 'message' => $e->getMessage(),
154
+ 'payment_id' => $razorpayPaymentId,
155
+ 'event' => $data['event']
156
  );
157
 
158
+ error_log(json_encode($log));
159
 
160
  exit;
161
  }
170
  $success = true;
171
  }
172
  else if (($payment['status'] === 'authorized') and
173
+ ($this->razorpay->getSetting('payment_action') === WC_Razorpay::CAPTURE))
174
  {
175
  //
176
  // If the payment is only authorized, we capture it
183
 
184
  $this->razorpay->updateOrder($order, $success, $errorMessage, $razorpayPaymentId, true);
185
 
186
+ // Graceful exit since payment is now processed.
187
  exit;
188
  }
189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  /**
191
  * Returns the order amount, rounded as integer
192
+ * @param WC_Order $order WooCommerce Order instance
193
+ * @return int Order Amount
194
  */
195
  public function getOrderAmountAsInteger($order)
196
  {
razorpay-payments.php CHANGED
@@ -1,10 +1,10 @@
1
  <?php
2
  /*
3
- Plugin Name: WooCommerce Razorpay Payments
4
  Plugin URI: https://razorpay.com
5
  Description: Razorpay Payment Gateway Integration for WooCommerce
6
- Version: 1.6.0-beta
7
- Stable tag: 1.5.3
8
  Author: Razorpay
9
  Author URI: https://razorpay.com
10
  */
@@ -17,14 +17,13 @@ if ( ! defined( 'ABSPATH' ) )
17
  require_once __DIR__.'/includes/razorpay-webhook.php';
18
  require_once __DIR__.'/includes/Errors/ErrorCode.php';
19
  require_once __DIR__.'/razorpay-sdk/Razorpay.php';
20
- require_once __DIR__.'/includes/razorpay-subscriptions.php';
21
 
22
  use Razorpay\Api\Api;
23
  use Razorpay\Api\Errors;
24
  use Razorpay\Woocommerce\Errors as WooErrors;
25
 
26
  add_action('plugins_loaded', 'woocommerce_razorpay_init', 0);
27
- add_action('admin_post_nopriv_rzp_wc_webhook', 'razorpay_webhook_init');
28
 
29
  function woocommerce_razorpay_init()
30
  {
@@ -39,62 +38,104 @@ function woocommerce_razorpay_init()
39
  const SESSION_KEY = 'razorpay_wc_order_id';
40
  const RAZORPAY_PAYMENT_ID = 'razorpay_payment_id';
41
  const RAZORPAY_ORDER_ID = 'razorpay_order_id';
42
- const RAZORPAY_SUBSCRIPTION_ID = 'razorpay_subscription_id';
43
 
44
  const INR = 'INR';
45
  const CAPTURE = 'capture';
46
  const AUTHORIZE = 'authorize';
47
  const WC_ORDER_ID = 'woocommerce_order_id';
48
 
49
- public function __construct()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  {
51
- $this->id = 'razorpay';
52
- $this->method_title = 'Razorpay';
53
  $this->icon = plugins_url('images/logo.png' , __FILE__);
54
- $this->has_fields = false;
55
 
56
  $this->init_form_fields();
57
  $this->init_settings();
58
- $this->title = $this->settings['title'];
59
- $this->description = $this->settings['description'];
60
- $this->key_id = $this->settings['key_id'];
61
- $this->key_secret = $this->settings['key_secret'];
62
- $this->payment_action = $this->settings['payment_action'];
63
-
64
- if (isset($this->settings['enable_webhook']) === true)
65
- {
66
- $this->enable_webhook = $this->settings['enable_webhook'];
67
- }
68
- else
69
- {
70
- $this->enable_webhook = 'yes';
71
- }
72
 
73
- if (isset($this->settings['webhook_secret']) === true)
 
 
74
  {
75
- $this->webhook_secret = $this->settings['webhook_secret'];
76
  }
77
- else
78
- {
79
- $this->webhook_secret = '';
80
- }
81
-
82
- $this->supports = array(
83
- 'products',
84
- 'refunds',
85
- 'subscriptions',
86
- 'subscription_reactivation',
87
- 'subscription_suspension',
88
- 'subscription_cancellation',
89
- );
90
 
91
- $this->msg['message'] = '';
92
- $this->msg['class'] = '';
 
93
 
94
- add_action('woocommerce_subscription_status_cancelled', array(&$this, 'subscription_cancelled'));
95
 
96
- add_action('init', array(&$this, 'check_razorpay_response'));
97
- add_action('woocommerce_api_' . strtolower(get_class($this)), array($this, 'check_razorpay_response'));
98
 
99
  $cb = array($this, 'process_admin_options');
100
 
@@ -106,47 +147,45 @@ function woocommerce_razorpay_init()
106
  {
107
  add_action('woocommerce_update_options_payment_gateways', $cb);
108
  }
109
-
110
- add_action('woocommerce_receipt_razorpay', array($this, 'receipt_page'));
111
  }
112
 
113
- function init_form_fields()
114
  {
115
  $webhookUrl = esc_url(admin_url('admin-post.php')) . '?action=rzp_wc_webhook';
116
 
117
- $this->form_fields = array(
118
  'enabled' => array(
119
- 'title' => __('Enable/Disable', 'razorpay'),
120
  'type' => 'checkbox',
121
- 'label' => __('Enable Razorpay Payment Module.', 'razorpay'),
122
  'default' => 'yes'
123
  ),
124
  'title' => array(
125
- 'title' => __('Title', 'razorpay'),
126
  'type'=> 'text',
127
- 'description' => __('This controls the title which the user sees during checkout.', 'razorpay'),
128
- 'default' => __('Credit Card/Debit Card/NetBanking', 'razorpay')
129
  ),
130
  'description' => array(
131
- 'title' => __('Description', 'razorpay'),
132
  'type' => 'textarea',
133
- 'description' => __('This controls the description which the user sees during checkout.', 'razorpay'),
134
- 'default' => __('Pay securely by Credit or Debit card or internet banking through Razorpay.', 'razorpay')
135
  ),
136
  'key_id' => array(
137
- 'title' => __('Key ID', 'razorpay'),
138
  'type' => 'text',
139
- 'description' => __('The key Id and key secret can be generated from "API Keys" section of Razorpay Dashboard. Use test or live for test or live mode.', 'razorpay')
140
  ),
141
  'key_secret' => array(
142
- 'title' => __('Key Secret', 'razorpay'),
143
  'type' => 'text',
144
- 'description' => __('The key Id and key secret can be generated from "API Keys" section of Razorpay Dashboard. Use test or live for test or live mode.', 'razorpay')
145
  ),
146
  'payment_action' => array(
147
- 'title' => __('Payment Action', 'razorpay'),
148
  'type' => 'select',
149
- 'description' => __('Payment action on order compelete', 'razorpay'),
150
  'default' => self::CAPTURE,
151
  'options' => array(
152
  self::AUTHORIZE => 'Authorize',
@@ -154,24 +193,33 @@ function woocommerce_razorpay_init()
154
  )
155
  ),
156
  'enable_webhook' => array(
157
- 'title' => __('Enable Webhook', 'razorpay'),
158
  'type' => 'checkbox',
159
  'description' => "<span>$webhookUrl</span><br/><br/>Instructions and guide to <a href='https://github.com/razorpay/razorpay-woocommerce/wiki/Razorpay-Woocommerce-Webhooks'>Razorpay webhooks</a>",
160
- 'label' => __('Enable Razorpay Webhook <a href="https://dashboard.razorpay.com/#/app/webhooks">here</a> with the URL listed below.', 'razorpay'),
161
  'default' => 'no'
162
  ),
163
  'webhook_secret' => array(
164
- 'title' => __('Webhook Secret', 'razorpay'),
165
  'type' => 'text',
166
- 'description' => __('Webhook secret is used for webhook signature verification. This has to match the one added <a href="https://dashboard.razorpay.com/#/app/webhooks">here</a>', 'razorpay'),
167
  'default' => ''
168
  ),
169
  );
 
 
 
 
 
 
 
 
170
  }
 
171
  public function admin_options()
172
  {
173
- echo '<h3>'.__('Razorpay Payment Gateway', 'razorpay') . '</h3>';
174
- echo '<p>'.__('Razorpay is an online payment gateway for India with transparent pricing, seamless integration and great support') . '</p>';
175
  echo '<table class="form-table">';
176
 
177
  // Generate the HTML For the settings form.
@@ -184,35 +232,34 @@ function woocommerce_razorpay_init()
184
  **/
185
  function payment_fields()
186
  {
187
- if($this->description)
188
- {
189
- echo wpautop(wptexturize($this->description));
190
- }
191
  }
192
 
193
  /**
194
  * Receipt Page
 
195
  **/
196
- function receipt_page($order)
197
  {
198
- echo $this->generate_razorpay_form($order);
199
  }
200
 
 
 
 
 
 
201
  protected function getOrderSessionKey($orderId)
202
  {
203
  return self::RAZORPAY_ORDER_ID . $orderId;
204
  }
205
 
206
- protected function getSubscriptionSessionKey($orderId)
207
- {
208
- return self::RAZORPAY_SUBSCRIPTION_ID . $orderId;
209
- }
210
-
211
  /**
212
  * Given a order Id, find the associated
213
  * Razorpay Order from the session and verify
214
  * that is is still correct. If not found
215
  * (or incorrect), create a new Razorpay Order
 
216
  * @param string $orderId Order Id
217
  * @return mixed Razorpay Order Id or Exception
218
  */
@@ -268,114 +315,132 @@ function woocommerce_razorpay_init()
268
  }
269
 
270
  /**
271
- * Generate razorpay button link
272
- **/
273
- public function generate_razorpay_form($orderId)
 
274
  {
275
- $order = new WC_Order($orderId);
 
276
 
277
- $subscriptionId = null;
278
- $razorpayOrderId = null;
 
 
 
 
 
 
 
279
 
280
- if (wcs_order_contains_subscription($order) === true)
281
  {
282
- $this->subscriptions = new RZP_Subscriptions($this->key_id, $this->key_secret);
283
-
284
- try
285
- {
286
- $subscriptionId = $this->subscriptions->createSubscription($orderId);
287
- }
288
- catch (Exception $e)
289
- {
290
- $message = $e->getMessage();
291
- return 'RAZORPAY ERROR: Subscription creation failed with the following message \'' . $message . '\'';
292
- }
293
  }
294
- else
295
  {
296
- $razorpayOrderId = $this->createOrGetRazorpayOrderId($orderId);
 
 
297
  }
298
 
299
- $redirectUrl = get_site_url() . '/?wc-api=' . get_class($this);
 
 
 
 
 
 
 
 
 
 
 
300
 
301
- if (($razorpayOrderId === null) and
302
- (wcs_order_contains_subscription($orderId) === false))
303
  {
304
- return 'RAZORPAY ERROR: Api could not be reached';
305
  }
306
- else if($razorpayOrderId instanceof Exception)
307
  {
308
- $message = $razorpayOrderId->getMessage();
309
- return 'RAZORPAY ERROR: Order creation failed with the message \'' . $message . '\'';
310
  }
311
 
312
- $checkoutArgs = $this->getCheckoutArguments($order, $razorpayOrderId, $subscriptionId);
313
 
314
- $html = '<p>'.__('Thank you for your order, please click the button below to pay with Razorpay.', 'razorpay').'</p>';
315
- $html .= $this->generateOrderForm($redirectUrl, $checkoutArgs);
 
316
 
317
  return $html;
318
  }
319
 
320
  /**
321
- * Returns array of checkout params
 
 
322
  */
323
- protected function getCheckoutArguments($order, $razorpayOrderId, $subscriptionId)
324
  {
325
- $callbackUrl = get_site_url() . '/?wc-api=' . get_class($this);
326
 
327
  $orderId = $this->getOrderId($order);
328
 
329
  $productinfo = "Order $orderId";
330
 
331
- $currency = null;
332
-
333
- $args = array(
334
- 'key' => $this->key_id,
335
- 'name' => get_bloginfo('name'),
336
- 'currency' => 'INR',
337
- 'description' => $productinfo,
338
- 'notes' => array(
339
- 'woocommerce_order_id' => $orderId
340
- ),
341
- 'callback_url' => $callbackUrl,
342
- 'prefill' => $this->getCustomerInfo($order)
343
  );
 
344
 
 
 
 
 
 
 
345
  if (version_compare(WOOCOMMERCE_VERSION, '2.7.0', '>='))
346
  {
347
- $currency = $order->get_currency();
348
- }
349
- else
350
- {
351
- $currency = $order->get_order_currency();
352
  }
353
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  if ($currency !== self::INR)
355
  {
356
  $args['display_currency'] = $currency;
357
- $args['display_amount'] = (int) round($order->get_total());
358
  }
359
 
360
- // order_id and subscription_id both cannot be set at the same time
361
- if (empty($razorpayOrderId) === false)
362
- {
363
- $args['order_id'] = $razorpayOrderId;
364
- }
365
- else if (empty($subscriptionId) === false)
366
- {
367
- $args['recurring'] = 1;
368
- $args['subscription_id'] = $subscriptionId;
369
-
370
- if ($currency !== self::INR)
371
- {
372
- $args['display_amount'] = $this->subscriptions->getDisplayAmount($orderId);
373
- }
374
- }
375
- else
376
- {
377
- throw new Exception('Both orderId and subscriptionId are null!');
378
- }
379
 
380
  return $args;
381
  }
@@ -418,6 +483,7 @@ function woocommerce_razorpay_init()
418
  catch (Exception $e)
419
  {
420
  $message = $e->getMessage();
 
421
  return 'RAZORPAY ERROR: Order creation failed with the message \'' . $message . '\'';
422
  }
423
 
@@ -431,9 +497,10 @@ function woocommerce_razorpay_init()
431
  /**
432
  * Convert the currency to INR using rates fetched from Woocommerce Currency Switcher plugin
433
  *
434
- * @param Array $data
435
- *
436
- * @return Array
 
437
  *
438
  **/
439
  protected function convertCurrency(& $data)
@@ -481,7 +548,7 @@ function woocommerce_razorpay_init()
481
  catch (Exception $e)
482
  {
483
  $message = $e->getMessage();
484
- return 'RAZORPAY ERROR: Order fetch failed with the message \'' . $message . '\'';
485
  }
486
 
487
  $orderCreationData = $this->getOrderCreationData($orderId);
@@ -506,20 +573,15 @@ function woocommerce_razorpay_init()
506
  return true;
507
  }
508
 
509
- function getOrderCreationData($orderId)
510
  {
511
  $order = new WC_Order($orderId);
512
 
513
- if (!isset($this->payment_action))
514
- {
515
- $this->payment_action = self::CAPTURE;
516
- }
517
-
518
  $data = array(
519
  'receipt' => $orderId,
520
  'amount' => (int) round($order->get_total() * 100),
521
  'currency' => get_woocommerce_currency(),
522
- 'payment_capture' => ($this->payment_action === self::AUTHORIZE) ? 0 : 1,
523
  'notes' => array(
524
  self::WC_ORDER_ID => (string) $orderId,
525
  ),
@@ -567,8 +629,9 @@ function woocommerce_razorpay_init()
567
  /**
568
  * Generates the order form
569
  **/
570
- function generateOrderForm($redirectUrl, $data)
571
  {
 
572
  $this->enqueueCheckoutScripts($data);
573
 
574
  return <<<EOT
@@ -649,10 +712,8 @@ EOT;
649
  {
650
  return new WP_Error('error', __($e->getMessage(), 'woocommerce'));
651
  }
652
-
653
  }
654
 
655
-
656
  /**
657
  * Process the payment and return the result
658
  **/
@@ -691,7 +752,7 @@ EOT;
691
 
692
  public function getRazorpayApiInstance()
693
  {
694
- return new Api($this->key_id, $this->key_secret);
695
  }
696
 
697
  /**
@@ -758,28 +819,14 @@ EOT;
758
  $api = $this->getRazorpayApiInstance();
759
 
760
  $attributes = array(
761
- self::RAZORPAY_PAYMENT_ID => $_POST['razorpay_payment_id'],
762
- 'razorpay_signature' => $_POST['razorpay_signature'],
763
  );
764
 
765
- if (wcs_order_contains_subscription($orderId) === true)
766
- {
767
- $sessionKey = $this->getSubscriptionSessionKey($orderId);
768
- $attributes[self::RAZORPAY_SUBSCRIPTION_ID] = $woocommerce->session->get($sessionKey);
769
- }
770
- else
771
- {
772
- $sessionKey = $this->getOrderSessionKey($orderId);
773
- $attributes[self::RAZORPAY_ORDER_ID] = $woocommerce->session->get($sessionKey);
774
- }
775
 
776
  $api->utility->verifyPaymentSignature($attributes);
777
-
778
- // Once the signature passes, save the subscription id as order metadata
779
- if (wcs_order_contains_subscription($orderId) === true)
780
- {
781
- add_post_meta($orderId, self::RAZORPAY_SUBSCRIPTION_ID, $attributes[self::RAZORPAY_SUBSCRIPTION_ID]);
782
- }
783
  }
784
 
785
  protected function getErrorMessage($orderId)
@@ -787,7 +834,7 @@ EOT;
787
  // We don't have a proper order id
788
  if ($orderId !== null)
789
  {
790
- $message = "An error occured while processing this payment";
791
  }
792
  if (isset($_POST['error']) === true)
793
  {
@@ -865,17 +912,6 @@ EOT;
865
  $this->msg['message'] = $this->getErrorMessage($orderId);
866
  }
867
 
868
- public function subscription_cancelled($subscription)
869
- {
870
- $orderIds = array_keys($subscription->get_related_orders());
871
-
872
- $parentOrderId = $orderIds[0];
873
-
874
- $subscriptionId = get_post_meta($parentOrderId, self::RAZORPAY_SUBSCRIPTION_ID)[0];
875
-
876
- $this->subscriptions->cancelSubscription($subscriptionId);
877
- }
878
-
879
  /**
880
  * Add a woocommerce notification message
881
  *
@@ -919,6 +955,7 @@ EOT;
919
  add_filter('woocommerce_payment_gateways', 'woocommerce_add_razorpay_gateway' );
920
  }
921
 
 
922
  function razorpay_webhook_init()
923
  {
924
  $rzpWebhook = new RZP_Webhook();
1
  <?php
2
  /*
3
+ Plugin Name: Razorpay for WooCommerce
4
  Plugin URI: https://razorpay.com
5
  Description: Razorpay Payment Gateway Integration for WooCommerce
6
+ Version: 1.6.0
7
+ Stable tag: 1.6.0
8
  Author: Razorpay
9
  Author URI: https://razorpay.com
10
  */
17
  require_once __DIR__.'/includes/razorpay-webhook.php';
18
  require_once __DIR__.'/includes/Errors/ErrorCode.php';
19
  require_once __DIR__.'/razorpay-sdk/Razorpay.php';
 
20
 
21
  use Razorpay\Api\Api;
22
  use Razorpay\Api\Errors;
23
  use Razorpay\Woocommerce\Errors as WooErrors;
24
 
25
  add_action('plugins_loaded', 'woocommerce_razorpay_init', 0);
26
+ add_action('admin_post_nopriv_rzp_wc_webhook', 'razorpay_webhook_init', 10);
27
 
28
  function woocommerce_razorpay_init()
29
  {
38
  const SESSION_KEY = 'razorpay_wc_order_id';
39
  const RAZORPAY_PAYMENT_ID = 'razorpay_payment_id';
40
  const RAZORPAY_ORDER_ID = 'razorpay_order_id';
41
+ const RAZORPAY_SIGNATURE = 'razorpay_signature';
42
 
43
  const INR = 'INR';
44
  const CAPTURE = 'capture';
45
  const AUTHORIZE = 'authorize';
46
  const WC_ORDER_ID = 'woocommerce_order_id';
47
 
48
+ const DEFAULT_LABEL = 'Credit Card/Debit Card/NetBanking';
49
+ const DEFAULT_DESCRIPTION = 'Pay securely by Credit or Debit card or Internet Banking through Razorpay.';
50
+
51
+ protected $visibleSettings = array(
52
+ 'enabled',
53
+ 'title',
54
+ 'description',
55
+ 'key_id',
56
+ 'key_secret',
57
+ 'payment_action',
58
+ 'enable_webhook',
59
+ 'webhook_secret',
60
+ );
61
+
62
+ public $form_fields = array();
63
+
64
+ public $supports = array(
65
+ 'products',
66
+ 'refunds'
67
+ );
68
+
69
+ /**
70
+ * Can be set to true if you want payment fields
71
+ * to show on the checkout (if doing a direct integration).
72
+ * @var boolean
73
+ */
74
+ public $has_fields = false;
75
+
76
+ /**
77
+ * Unique ID for the gateway
78
+ * @var string
79
+ */
80
+ public $id = 'razorpay';
81
+
82
+ /**
83
+ * Title of the payment method shown on the admin page.
84
+ * @var string
85
+ */
86
+ public $method_title = 'Razorpay';
87
+
88
+ /**
89
+ * Icon URL, set in constructor
90
+ * @var string
91
+ */
92
+ public $icon;
93
+
94
+ /**
95
+ * TODO: Remove usage of $this->msg
96
+ */
97
+ protected $msg = array(
98
+ 'message' => '',
99
+ 'class' => '',
100
+ );
101
+
102
+ /**
103
+ * Return Wordpress plugin settings
104
+ * @param string $key setting key
105
+ * @return mixed setting value
106
+ */
107
+ public function getSetting($key)
108
+ {
109
+ return $this->settings[$key];
110
+ }
111
+
112
+ /**
113
+ * @param boolean $hooks Whether or not to
114
+ * setup the hooks on
115
+ * calling the constructor
116
+ */
117
+ public function __construct($hooks = true)
118
  {
 
 
119
  $this->icon = plugins_url('images/logo.png' , __FILE__);
 
120
 
121
  $this->init_form_fields();
122
  $this->init_settings();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ // TODO: This is hacky, find a better way to do this
125
+ // See mergeSettingsWithParentPlugin() in subscriptions for more details.
126
+ if ($hooks)
127
  {
128
+ $this->initHooks();
129
  }
130
+ }
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
+ protected function initHooks()
133
+ {
134
+ add_action('init', array(&$this, 'check_razorpay_response'));
135
 
136
+ add_action('woocommerce_receipt_' . $this->id, array($this, 'receipt_page'));
137
 
138
+ add_action('woocommerce_api_' . $this->id, array($this, 'check_razorpay_response'));
 
139
 
140
  $cb = array($this, 'process_admin_options');
141
 
147
  {
148
  add_action('woocommerce_update_options_payment_gateways', $cb);
149
  }
 
 
150
  }
151
 
152
+ public function init_form_fields()
153
  {
154
  $webhookUrl = esc_url(admin_url('admin-post.php')) . '?action=rzp_wc_webhook';
155
 
156
+ $defaultFormFields = array(
157
  'enabled' => array(
158
+ 'title' => __('Enable/Disable', $this->id),
159
  'type' => 'checkbox',
160
+ 'label' => __('Enable this module?', $this->id),
161
  'default' => 'yes'
162
  ),
163
  'title' => array(
164
+ 'title' => __('Title', $this->id),
165
  'type'=> 'text',
166
+ 'description' => __('This controls the title which the user sees during checkout.', $this->id),
167
+ 'default' => __(static::DEFAULT_LABEL, $this->id)
168
  ),
169
  'description' => array(
170
+ 'title' => __('Description', $this->id),
171
  'type' => 'textarea',
172
+ 'description' => __('This controls the description which the user sees during checkout.', $this->id),
173
+ 'default' => __(static::DEFAULT_DESCRIPTION, $this->id)
174
  ),
175
  'key_id' => array(
176
+ 'title' => __('Key ID', $this->id),
177
  'type' => 'text',
178
+ 'description' => __('The key Id and key secret can be generated from "API Keys" section of Razorpay Dashboard. Use test or live for test or live mode.', $this->id)
179
  ),
180
  'key_secret' => array(
181
+ 'title' => __('Key Secret', $this->id),
182
  'type' => 'text',
183
+ 'description' => __('The key Id and key secret can be generated from "API Keys" section of Razorpay Dashboard. Use test or live for test or live mode.', $this->id)
184
  ),
185
  'payment_action' => array(
186
+ 'title' => __('Payment Action', $this->id),
187
  'type' => 'select',
188
+ 'description' => __('Payment action on order compelete', $this->id),
189
  'default' => self::CAPTURE,
190
  'options' => array(
191
  self::AUTHORIZE => 'Authorize',
193
  )
194
  ),
195
  'enable_webhook' => array(
196
+ 'title' => __('Enable Webhook', $this->id),
197
  'type' => 'checkbox',
198
  'description' => "<span>$webhookUrl</span><br/><br/>Instructions and guide to <a href='https://github.com/razorpay/razorpay-woocommerce/wiki/Razorpay-Woocommerce-Webhooks'>Razorpay webhooks</a>",
199
+ 'label' => __('Enable Razorpay Webhook <a href="https://dashboard.razorpay.com/#/app/webhooks">here</a> with the URL listed below.', $this->id),
200
  'default' => 'no'
201
  ),
202
  'webhook_secret' => array(
203
+ 'title' => __('Webhook Secret', $this->id),
204
  'type' => 'text',
205
+ 'description' => __('Webhook secret is used for webhook signature verification. This has to match the one added <a href="https://dashboard.razorpay.com/#/app/webhooks">here</a>', $this->id),
206
  'default' => ''
207
  ),
208
  );
209
+
210
+ foreach ($defaultFormFields as $key => $value)
211
+ {
212
+ if (in_array($key, $this->visibleSettings, true))
213
+ {
214
+ $this->form_fields[$key] = $value;
215
+ }
216
+ }
217
  }
218
+
219
  public function admin_options()
220
  {
221
+ echo '<h3>'.__('Razorpay Payment Gateway', $this->id) . '</h3>';
222
+ echo '<p>'.__('Allows payments by Credit/Debit Cards, NetBanking, UPI, and multiple Wallets') . '</p>';
223
  echo '<table class="form-table">';
224
 
225
  // Generate the HTML For the settings form.
232
  **/
233
  function payment_fields()
234
  {
235
+ echo wpautop(wptexturize($this->getSetting('description')));
 
 
 
236
  }
237
 
238
  /**
239
  * Receipt Page
240
+ * @param string $orderId WC Order Id
241
  **/
242
+ function receipt_page($orderId)
243
  {
244
+ echo $this->generate_razorpay_form($orderId);
245
  }
246
 
247
+ /**
248
+ * Returns key to use in session for storing Razorpay order Id
249
+ * @param string $orderId Razorpay Order Id
250
+ * @return string Session Key
251
+ */
252
  protected function getOrderSessionKey($orderId)
253
  {
254
  return self::RAZORPAY_ORDER_ID . $orderId;
255
  }
256
 
 
 
 
 
 
257
  /**
258
  * Given a order Id, find the associated
259
  * Razorpay Order from the session and verify
260
  * that is is still correct. If not found
261
  * (or incorrect), create a new Razorpay Order
262
+ *
263
  * @param string $orderId Order Id
264
  * @return mixed Razorpay Order Id or Exception
265
  */
315
  }
316
 
317
  /**
318
+ * Returns redirect URL post payment processing
319
+ * @return string redirect URL
320
+ */
321
+ private function getRedirectUrl()
322
  {
323
+ return get_site_url() . '/wc-api/' . $this->id;
324
+ }
325
 
326
+ /**
327
+ * Specific payment parameters to be passed to checkout
328
+ * for payment processing
329
+ * @param string $orderId WC Order Id
330
+ * @return array payment params
331
+ */
332
+ protected function getRazorpayPaymentParams($orderId)
333
+ {
334
+ $razorpayOrderId = $this->createOrGetRazorpayOrderId($orderId);
335
 
336
+ if ($razorpayOrderId === null)
337
  {
338
+ throw new Exception('RAZORPAY ERROR: Razorpay API could not be reached');
 
 
 
 
 
 
 
 
 
 
339
  }
340
+ else if ($razorpayOrderId instanceof Exception)
341
  {
342
+ $message = $razorpayOrderId->getMessage();
343
+
344
+ throw new Exception("RAZORPAY ERROR: Order creation failed with the message: '$message'.");
345
  }
346
 
347
+ return [
348
+ 'order_id' => $razorpayOrderId
349
+ ];
350
+ }
351
+
352
+ /**
353
+ * Generate razorpay button link
354
+ * @param string $orderId WC Order Id
355
+ **/
356
+ public function generate_razorpay_form($orderId)
357
+ {
358
+ $order = new WC_Order($orderId);
359
 
360
+ try
 
361
  {
362
+ $params = $this->getRazorpayPaymentParams($orderId);
363
  }
364
+ catch (Exception $e)
365
  {
366
+ return $e->getMessage();
 
367
  }
368
 
369
+ $checkoutArgs = $this->getCheckoutArguments($order, $params);
370
 
371
+ $html = '<p>'.__('Thank you for your order, please click the button below to pay with Razorpay.', $this->id).'</p>';
372
+
373
+ $html .= $this->generateOrderForm($checkoutArgs);
374
 
375
  return $html;
376
  }
377
 
378
  /**
379
+ * default parameters passed to checkout
380
+ * @param WC_Order $order WC Order
381
+ * @return array checkout params
382
  */
383
+ private function getDefaultCheckoutArguments($order)
384
  {
385
+ $callbackUrl = $this->getRedirectUrl();
386
 
387
  $orderId = $this->getOrderId($order);
388
 
389
  $productinfo = "Order $orderId";
390
 
391
+ return array(
392
+ 'key' => $this->getSetting('key_id'),
393
+ 'name' => get_bloginfo('name'),
394
+ 'currency' => self::INR,
395
+ 'description' => $productinfo,
396
+ 'notes' => array(
397
+ 'woocommerce_order_id' => $orderId
398
+ ),
399
+ 'callback_url' => $callbackUrl,
400
+ 'prefill' => $this->getCustomerInfo($order),
 
 
401
  );
402
+ }
403
 
404
+ /**
405
+ * @param WC_Order $order
406
+ * @return string currency
407
+ */
408
+ private function getOrderCurrency($order)
409
+ {
410
  if (version_compare(WOOCOMMERCE_VERSION, '2.7.0', '>='))
411
  {
412
+ return $order->get_currency();
 
 
 
 
413
  }
414
 
415
+ return $order->get_order_currency();
416
+ }
417
+
418
+ /**
419
+ * Returns display amount to use for non-INR payments
420
+ * @param WC_Order $order WC Order
421
+ * @return int order total to be displayed
422
+ */
423
+ protected function getDisplayAmount($order)
424
+ {
425
+ return (int) round($order->get_total());
426
+ }
427
+
428
+ /**
429
+ * Returns array of checkout params
430
+ */
431
+ private function getCheckoutArguments($order, $params)
432
+ {
433
+ $args = $this->getDefaultCheckoutArguments($order);
434
+
435
+ $currency = $this->getOrderCurrency($order);
436
+
437
  if ($currency !== self::INR)
438
  {
439
  $args['display_currency'] = $currency;
440
+ $args['display_amount'] = $this->getDisplayAmount($order);
441
  }
442
 
443
+ $args = array_merge($args, $params);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
  return $args;
446
  }
483
  catch (Exception $e)
484
  {
485
  $message = $e->getMessage();
486
+
487
  return 'RAZORPAY ERROR: Order creation failed with the message \'' . $message . '\'';
488
  }
489
 
497
  /**
498
  * Convert the currency to INR using rates fetched from Woocommerce Currency Switcher plugin
499
  *
500
+ * @param array $data Input array to convert currency
501
+ * @param string $data['currency'] Currency
502
+ * @param amount $data['amount'] Input Amount
503
+ * @return array, modified $data with $data['currency'] = 'INR'
504
  *
505
  **/
506
  protected function convertCurrency(& $data)
548
  catch (Exception $e)
549
  {
550
  $message = $e->getMessage();
551
+ return "RAZORPAY ERROR: Order fetch failed with the message '$message'";
552
  }
553
 
554
  $orderCreationData = $this->getOrderCreationData($orderId);
573
  return true;
574
  }
575
 
576
+ private function getOrderCreationData($orderId)
577
  {
578
  $order = new WC_Order($orderId);
579
 
 
 
 
 
 
580
  $data = array(
581
  'receipt' => $orderId,
582
  'amount' => (int) round($order->get_total() * 100),
583
  'currency' => get_woocommerce_currency(),
584
+ 'payment_capture' => ($this->getSetting('payment_action') === self::AUTHORIZE) ? 0 : 1,
585
  'notes' => array(
586
  self::WC_ORDER_ID => (string) $orderId,
587
  ),
629
  /**
630
  * Generates the order form
631
  **/
632
+ function generateOrderForm($data)
633
  {
634
+ $redirectUrl = $this->getRedirectUrl();
635
  $this->enqueueCheckoutScripts($data);
636
 
637
  return <<<EOT
712
  {
713
  return new WP_Error('error', __($e->getMessage(), 'woocommerce'));
714
  }
 
715
  }
716
 
 
717
  /**
718
  * Process the payment and return the result
719
  **/
752
 
753
  public function getRazorpayApiInstance()
754
  {
755
+ return new Api($this->getSetting('key_id'), $this->getSetting('key_secret'));
756
  }
757
 
758
  /**
819
  $api = $this->getRazorpayApiInstance();
820
 
821
  $attributes = array(
822
+ self::RAZORPAY_PAYMENT_ID => $_POST[self::RAZORPAY_PAYMENT_ID],
823
+ self::RAZORPAY_SIGNATURE => $_POST[self::RAZORPAY_SIGNATURE],
824
  );
825
 
826
+ $sessionKey = $this->getOrderSessionKey($orderId);
827
+ $attributes[self::RAZORPAY_ORDER_ID] = $woocommerce->session->get($sessionKey);
 
 
 
 
 
 
 
 
828
 
829
  $api->utility->verifyPaymentSignature($attributes);
 
 
 
 
 
 
830
  }
831
 
832
  protected function getErrorMessage($orderId)
834
  // We don't have a proper order id
835
  if ($orderId !== null)
836
  {
837
+ $message = 'An error occured while processing this payment';
838
  }
839
  if (isset($_POST['error']) === true)
840
  {
912
  $this->msg['message'] = $this->getErrorMessage($orderId);
913
  }
914
 
 
 
 
 
 
 
 
 
 
 
 
915
  /**
916
  * Add a woocommerce notification message
917
  *
955
  add_filter('woocommerce_payment_gateways', 'woocommerce_add_razorpay_gateway' );
956
  }
957
 
958
+ // This is set to a priority of 10
959
  function razorpay_webhook_init()
960
  {
961
  $rzpWebhook = new RZP_Webhook();
razorpay-sdk/Razorpay.php CHANGED
@@ -1,10 +1,23 @@
1
  <?php
2
 
3
- if(class_exists('Requests') === false){
4
- require_once __DIR__.'/libs/Requests-1.6.1/library/Requests.php';
 
 
 
 
 
 
 
5
 
6
- // Register requests autoloader
7
- Requests::register_autoloader();
 
 
 
 
 
 
8
  }
9
 
10
  spl_autoload_register(function ($class)
1
  <?php
2
 
3
+ // Include Requests only if not already defined
4
+ if (class_exists('Requests') === false)
5
+ {
6
+ require_once __DIR__.'/libs/Requests-1.6.1/library/Requests.php';
7
+ }
8
+
9
+ try
10
+ {
11
+ Requests::register_autoloader();
12
 
13
+ if (version_compare(Requests::VERSION, '1.6.0') === -1)
14
+ {
15
+ throw new Exception('Requests class found but did not match');
16
+ }
17
+ }
18
+ catch (\Exception $e)
19
+ {
20
+ throw new Exception('Requests class found but did not match');
21
  }
22
 
23
  spl_autoload_register(function ($class)
razorpay-sdk/src/Addon.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Razorpay\Api;
4
+
5
+ class Addon extends Entity
6
+ {
7
+ // To create an Addon,
8
+ // use the createAddon method of the Subscription class
9
+
10
+ public function fetch($id)
11
+ {
12
+ return parent::fetch($id);
13
+ }
14
+
15
+ public function delete()
16
+ {
17
+ $entityUrl = $this->getEntityUrl();
18
+
19
+ return $this->request('DELETE', $entityUrl . $this->id);
20
+ }
21
+ }
razorpay-sdk/src/Api.php CHANGED
@@ -16,7 +16,7 @@ class Api
16
  */
17
  public static $appsDetails = array();
18
 
19
- const VERSION = '1.2.9';
20
 
21
  /**
22
  * @param string $key
@@ -30,7 +30,6 @@ class Api
30
 
31
  /*
32
  * Set Headers
33
- *
34
  */
35
  public function setHeader($header, $value)
36
  {
16
  */
17
  public static $appsDetails = array();
18
 
19
+ const VERSION = '2.2.0';
20
 
21
  /**
22
  * @param string $key
30
 
31
  /*
32
  * Set Headers
 
33
  */
34
  public function setHeader($header, $value)
35
  {
razorpay-sdk/src/Entity.php CHANGED
@@ -48,10 +48,30 @@ class Entity extends Resource implements ArrayableInterface
48
  $fullClassName = get_class($this);
49
  $pos = strrpos($fullClassName, '\\');
50
  $className = substr($fullClassName, $pos + 1);
51
- $className = lcfirst($className);
52
  return $className.'s/';
53
  }
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  protected function request($method, $relativeUrl, $data = null)
56
  {
57
  $request = new Request();
@@ -67,21 +87,23 @@ class Entity extends Resource implements ArrayableInterface
67
  }
68
  else
69
  {
70
- return static::buildEntity(null, $response);
71
  }
72
  }
73
 
74
- protected static function buildEntity($key, $data)
 
 
 
 
 
 
 
 
75
  {
76
  $entities = static::getDefinedEntitiesArray();
77
 
78
- $arrayableAttributes = static::getArrayableAttributes();
79
-
80
- if (in_array($key, $arrayableAttributes))
81
- {
82
- $entity = $data;
83
- }
84
- else if (isset($data['entity']))
85
  {
86
  if (in_array($data['entity'], $entities))
87
  {
@@ -90,26 +112,17 @@ class Entity extends Resource implements ArrayableInterface
90
  }
91
  else
92
  {
93
- $entity = new self;
94
  }
95
-
96
- $entity->fill($data);
97
  }
98
  else
99
  {
100
- $entity = new self;
101
-
102
- $entity->fill($data);
103
  }
104
 
105
- return $entity;
106
- }
107
 
108
- protected static function getArrayableAttributes()
109
- {
110
- return array(
111
- 'notes'
112
- );
113
  }
114
 
115
  protected static function getDefinedEntitiesArray()
@@ -145,7 +158,29 @@ class Entity extends Resource implements ArrayableInterface
145
  {
146
  if (is_array($value))
147
  {
148
- $value = self::getArrayValue($key, $value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
 
151
  $attributes[$key] = $value;
@@ -154,40 +189,6 @@ class Entity extends Resource implements ArrayableInterface
154
  $this->attributes = $attributes;
155
  }
156
 
157
- protected static function getArrayValue($key, $value)
158
- {
159
- if (static::isAssocArray($value) === false)
160
- {
161
- $value = self::getAssocArrayValue($key, $value);
162
- }
163
- else
164
- {
165
- $value = static::buildEntity($key, $value);
166
- }
167
-
168
- return $value;
169
- }
170
-
171
- protected static function getAssocArrayValue($key, $value)
172
- {
173
- $collection = array();
174
-
175
- foreach ($value as $v)
176
- {
177
- if (is_array($v))
178
- {
179
- $entity = static::buildEntity($key, $v);
180
- array_push($collection, $entity);
181
- }
182
- else
183
- {
184
- array_push($collection, $v);
185
- }
186
- }
187
-
188
- return $collection;
189
- }
190
-
191
  public static function isAssocArray($arr)
192
  {
193
  return array_keys($arr) !== range(0, count($arr) - 1);
48
  $fullClassName = get_class($this);
49
  $pos = strrpos($fullClassName, '\\');
50
  $className = substr($fullClassName, $pos + 1);
51
+ $className = $this->snakeCase($className);
52
  return $className.'s/';
53
  }
54
 
55
+ protected function snakeCase($input)
56
+ {
57
+ $delimiter = '_';
58
+ $output = preg_replace('/\s+/u', '', ucwords($input));
59
+ $output = preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $output);
60
+ $output = strtolower($output);
61
+ return $output;
62
+ }
63
+
64
+ /**
65
+ * Makes a HTTP request using Request class and assuming the API returns
66
+ * formatted entity or collection result, wraps the returned JSON as entity
67
+ * and returns.
68
+ *
69
+ * @param string $method
70
+ * @param string $relativeUrl
71
+ * @param array $data
72
+ *
73
+ * @return Entity
74
+ */
75
  protected function request($method, $relativeUrl, $data = null)
76
  {
77
  $request = new Request();
87
  }
88
  else
89
  {
90
+ return static::buildEntity($response);
91
  }
92
  }
93
 
94
+ /**
95
+ * Given the JSON response of an API call, wraps it to corresponding entity
96
+ * class or a collection and returns the same.
97
+ *
98
+ * @param array $data
99
+ *
100
+ * @return Entity
101
+ */
102
+ protected static function buildEntity($data)
103
  {
104
  $entities = static::getDefinedEntitiesArray();
105
 
106
+ if (isset($data['entity']))
 
 
 
 
 
 
107
  {
108
  if (in_array($data['entity'], $entities))
109
  {
112
  }
113
  else
114
  {
115
+ $entity = new static;
116
  }
 
 
117
  }
118
  else
119
  {
120
+ $entity = new static;
 
 
121
  }
122
 
123
+ $entity->fill($data);
 
124
 
125
+ return $entity;
 
 
 
 
126
  }
127
 
128
  protected static function getDefinedEntitiesArray()
158
  {
159
  if (is_array($value))
160
  {
161
+ if (static::isAssocArray($value) === false)
162
+ {
163
+ $collection = array();
164
+
165
+ foreach ($value as $v)
166
+ {
167
+ if (is_array($v))
168
+ {
169
+ $entity = static::buildEntity($v);
170
+ array_push($collection, $entity);
171
+ }
172
+ else
173
+ {
174
+ array_push($collection, $v);
175
+ }
176
+ }
177
+
178
+ $value = $collection;
179
+ }
180
+ else
181
+ {
182
+ $value = static::buildEntity($value);
183
+ }
184
  }
185
 
186
  $attributes[$key] = $value;
189
  $this->attributes = $attributes;
190
  }
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  public static function isAssocArray($arr)
193
  {
194
  return array_keys($arr) !== range(0, count($arr) - 1);
razorpay-sdk/src/Errors/ErrorCode.php CHANGED
@@ -14,4 +14,4 @@ class ErrorCode
14
 
15
  return defined(get_class() . '::' . $code);
16
  }
17
- }
14
 
15
  return defined(get_class() . '::' . $code);
16
  }
17
+ }
razorpay-sdk/src/Invoice.php CHANGED
@@ -2,23 +2,112 @@
2
 
3
  namespace Razorpay\Api;
4
 
 
 
 
 
 
 
 
5
  class Invoice extends Entity
6
  {
7
  /**
8
- * @param $id Invoice id
 
 
 
 
9
  */
10
- public function fetch($id)
11
  {
12
- return parent::fetch($id);
13
  }
14
 
15
- public function create($attributes = array())
 
 
 
 
 
 
 
16
  {
17
- return parent::create($attributes);
18
  }
19
 
 
 
 
 
 
 
 
20
  public function all($options = array())
21
  {
22
  return parent::all($options);
23
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
2
 
3
  namespace Razorpay\Api;
4
 
5
+ use Requests;
6
+
7
+ /**
8
+ * Invoice entity gets used for both Payment Links and Invoices system.
9
+ * Few of the methods are only meaningful for Invoices system and calling those
10
+ * for against/for a Payment Link would throw Bad request error.
11
+ */
12
  class Invoice extends Entity
13
  {
14
  /**
15
+ * Creates invoice of any type(invoice|link|ecod).
16
+ *
17
+ * @param array $attributes
18
+ *
19
+ * @return Invoice
20
  */
21
+ public function create($attributes = array())
22
  {
23
+ return parent::create($attributes);
24
  }
25
 
26
+ /**
27
+ * Fetches invoice entity with given id
28
+ *
29
+ * @param string $id
30
+ *
31
+ * @return Invoice
32
+ */
33
+ public function fetch($id)
34
  {
35
+ return parent::fetch($id);
36
  }
37
 
38
+ /**
39
+ * Fetches multiple invoices with given query options
40
+ *
41
+ * @param array $options
42
+ *
43
+ * @return Collection
44
+ */
45
  public function all($options = array())
46
  {
47
  return parent::all($options);
48
  }
49
+
50
+ /**
51
+ * Cancels issued invoice
52
+ *
53
+ * @return Invoice
54
+ */
55
+ public function cancel()
56
+ {
57
+ $url = $this->getEntityUrl() . $this->id . '/cancel';
58
+
59
+ return $this->request(Requests::POST, $url);
60
+ }
61
+
62
+ /**
63
+ * Send/re-send notification for invoice by given medium
64
+ *
65
+ * @param $medium - sms|email
66
+ *
67
+ * @return array
68
+ */
69
+ public function notifyBy($medium)
70
+ {
71
+ $url = $this->getEntityUrl() . $this->id . '/notify_by/' . $medium;
72
+
73
+ return (new Request())->request(Requests::POST, $url);
74
+ }
75
+
76
+ /**
77
+ * Patches given invoice with new attributes
78
+ *
79
+ * @param array $attributes
80
+ *
81
+ * @return Invoice
82
+ */
83
+ public function edit($attributes = array())
84
+ {
85
+ $url = $this->getEntityUrl() . $this->id;
86
+
87
+ return $this->request(Requests::PATCH, $url, $attributes);
88
+ }
89
+
90
+ /**
91
+ * Issues drafted invoice
92
+ *
93
+ * @return Invoice
94
+ */
95
+ public function issue()
96
+ {
97
+ $url = $this->getEntityUrl() . $this->id . '/issue';
98
+
99
+ return $this->request(Requests::POST, $url);
100
+ }
101
+
102
+ /**
103
+ * Deletes drafted invoice
104
+ *
105
+ * @return Invoice
106
+ */
107
+ public function delete()
108
+ {
109
+ $url = $this->getEntityUrl() . $this->id;
110
+
111
+ return (new Request())->request(Requests::DELETE, $url);
112
+ }
113
  }
razorpay-sdk/src/Payment.php CHANGED
@@ -39,6 +39,13 @@ class Payment extends Entity
39
  return $this->request('POST', $relativeUrl, $attributes);
40
  }
41
 
 
 
 
 
 
 
 
42
  public function refunds()
43
  {
44
  $refund = new Refund;
@@ -47,4 +54,20 @@ class Payment extends Entity
47
 
48
  return $refund->all($options);
49
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
39
  return $this->request('POST', $relativeUrl, $attributes);
40
  }
41
 
42
+ public function transfer($attributes = array())
43
+ {
44
+ $relativeUrl = $this->getEntityUrl() . $this->id . '/transfers';
45
+
46
+ return $this->request('POST', $relativeUrl, $attributes);
47
+ }
48
+
49
  public function refunds()
50
  {
51
  $refund = new Refund;
54
 
55
  return $refund->all($options);
56
  }
57
+
58
+ public function transfers()
59
+ {
60
+ $transfer = new Transfer();
61
+
62
+ $transfer->payment_id = $this->id;
63
+
64
+ return $transfer->all();
65
+ }
66
+
67
+ public function bankTransfer()
68
+ {
69
+ $relativeUrl = $this->getEntityUrl() . $this->id . '/bank_transfer';
70
+
71
+ return $this->request('GET', $relativeUrl);
72
+ }
73
  }
razorpay-sdk/src/Plan.php CHANGED
@@ -18,4 +18,4 @@ class Plan extends Entity
18
  {
19
  return parent::all($options);
20
  }
21
- }
18
  {
19
  return parent::all($options);
20
  }
21
+ }
razorpay-sdk/src/Request.php CHANGED
@@ -206,4 +206,4 @@ class Request
206
  $this->throwServerError($body, $httpStatusCode);
207
  }
208
  }
209
- }
206
  $this->throwServerError($body, $httpStatusCode);
207
  }
208
  }
209
+ }
razorpay-sdk/src/Subscription.php CHANGED
@@ -19,10 +19,17 @@ class Subscription extends Entity
19
  return parent::all($options);
20
  }
21
 
22
- public function cancel($subscriptionId)
23
  {
24
- $relativeUrl = $this->getEntityUrl() . $subscriptionId . '/cancel';
25
 
26
  return $this->request('POST', $relativeUrl);
27
  }
28
- }
 
 
 
 
 
 
 
19
  return parent::all($options);
20
  }
21
 
22
+ public function cancel()
23
  {
24
+ $relativeUrl = $this->getEntityUrl() . $this->id . '/cancel';
25
 
26
  return $this->request('POST', $relativeUrl);
27
  }
28
+
29
+ public function createAddon($attributes = array())
30
+ {
31
+ $relativeUrl = $this->getEntityUrl() . $this->id . '/addons';
32
+
33
+ return $this->request('POST', $relativeUrl, $attributes);
34
+ }
35
+ }
razorpay-sdk/src/Transfer.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace Razorpay\Api;
3
+
4
+ class Transfer extends Entity
5
+ {
6
+ /**
7
+ * @param $id Transfer ID
8
+ */
9
+ public function fetch($id)
10
+ {
11
+ return parent::fetch($id);
12
+ }
13
+
14
+ public function all($options = array())
15
+ {
16
+ if (isset($this->payment_id) === true)
17
+ {
18
+ $relativeUrl = 'payments/' . $this->payment_id. '/transfers';
19
+
20
+ return $this->request('GET', $relativeUrl, $options);
21
+ }
22
+
23
+ return parent::all($options);
24
+ }
25
+
26
+ /**
27
+ * Create a direct transfer from merchant's account to
28
+ * any of the linked accounts, without linking it to a
29
+ * payment
30
+ */
31
+ public function create($attributes = array())
32
+ {
33
+ return parent::create($attributes);
34
+ }
35
+
36
+ public function edit($attributes = null)
37
+ {
38
+ $entityUrl = $this->getEntityUrl() . $this->id;
39
+
40
+ return $this->request('PATCH', $entityUrl, $attributes);
41
+ }
42
+
43
+ /**
44
+ * Create a reversal for a transfer
45
+ */
46
+ public function reverse($attributes = array())
47
+ {
48
+ $relativeUrl = $this->getEntityUrl() . $this->id . '/reversals';
49
+
50
+ return $this->request('POST', $relativeUrl, $attributes);
51
+ }
52
+
53
+ /**
54
+ * Fetches all reversals
55
+ */
56
+ public function reversals($attributes = array())
57
+ {
58
+ $relativeUrl = $this->getEntityUrl() . $this->id . '/reversals';
59
+
60
+ return $this->request('GET', $relativeUrl, $attributes);
61
+ }
62
+ }
razorpay-sdk/src/Utility.php CHANGED
@@ -8,7 +8,8 @@ class Utility
8
 
9
  public function verifyPaymentSignature($attributes)
10
  {
11
- $expectedSignature = $attributes['razorpay_signature'];
 
12
  $paymentId = $attributes['razorpay_payment_id'];
13
 
14
  if (isset($attributes['razorpay_order_id']) === true)
@@ -21,41 +22,36 @@ class Utility
21
  {
22
  $subscriptionId = $attributes['razorpay_subscription_id'];
23
 
24
- $payload = $paymentId . '|' . $subscriptionId ;
25
  }
26
  else
27
  {
28
- throw new Error('Invalid parameters passed to verifyPaymentSignature:'
29
- . 'At least razorpay_order_id or razorpay_subscription_id should be set.');
30
  }
31
 
32
- return self::verifySignature($payload, $expectedSignature);
 
 
33
  }
34
 
35
- public function verifyWebhookSignature($payload, $expectedSignature, $webhookSecret)
36
  {
37
- return self::verifySignature($payload, $expectedSignature, $webhookSecret);
38
  }
39
 
40
- public function verifySignature($payload, $expectedSignature, $webhookSecret = '')
41
  {
42
- if (empty($webhookSecret) === false)
43
- {
44
- $actualSignature = hash_hmac(self::SHA256, $payload, $webhookSecret);
45
- }
46
- else
47
- {
48
- $actualSignature = hash_hmac(self::SHA256, $payload, Api::getSecret());
49
- }
50
 
51
  // Use lang's built-in hash_equals if exists to mitigate timing attacks
52
  if (function_exists('hash_equals'))
53
  {
54
- $verified = hash_equals($actualSignature, $expectedSignature);
55
  }
56
  else
57
  {
58
- $verified = $this->hashEquals($actualSignature, $expectedSignature);
59
  }
60
 
61
  if ($verified === false)
@@ -65,7 +61,7 @@ class Utility
65
  }
66
  }
67
 
68
- private function hashEquals($actualSignature, $expectedSignature)
69
  {
70
  if (strlen($expectedSignature) === strlen($actualSignature))
71
  {
8
 
9
  public function verifyPaymentSignature($attributes)
10
  {
11
+ $actualSignature = $attributes['razorpay_signature'];
12
+
13
  $paymentId = $attributes['razorpay_payment_id'];
14
 
15
  if (isset($attributes['razorpay_order_id']) === true)
22
  {
23
  $subscriptionId = $attributes['razorpay_subscription_id'];
24
 
25
+ $payload = $paymentId . '|' . $subscriptionId;
26
  }
27
  else
28
  {
29
+ throw new Errors\SignatureVerificationError(
30
+ 'Either razorpay_order_id or razorpay_subscription_id must be present.');
31
  }
32
 
33
+ $secret = Api::getSecret();
34
+
35
+ self::verifySignature($payload, $actualSignature, $secret);
36
  }
37
 
38
+ public function verifyWebhookSignature($payload, $actualSignature, $secret)
39
  {
40
+ self::verifySignature($payload, $actualSignature, $secret);
41
  }
42
 
43
+ public function verifySignature($payload, $actualSignature, $secret)
44
  {
45
+ $expectedSignature = hash_hmac(self::SHA256, $payload, $secret);
 
 
 
 
 
 
 
46
 
47
  // Use lang's built-in hash_equals if exists to mitigate timing attacks
48
  if (function_exists('hash_equals'))
49
  {
50
+ $verified = hash_equals($expectedSignature, $actualSignature);
51
  }
52
  else
53
  {
54
+ $verified = $this->hashEquals($expectedSignature, $actualSignature);
55
  }
56
 
57
  if ($verified === false)
61
  }
62
  }
63
 
64
+ private function hashEquals($expectedSignature, $actualSignature)
65
  {
66
  if (strlen($expectedSignature) === strlen($actualSignature))
67
  {
razorpay-sdk/src/VirtualAccount.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Razorpay\Api;
4
+
5
+ class VirtualAccount extends Entity
6
+ {
7
+ public function create($attributes = array())
8
+ {
9
+ return parent::create($attributes);
10
+ }
11
+
12
+ public function fetch($id)
13
+ {
14
+ return parent::fetch($id);
15
+ }
16
+
17
+ public function all($options = array())
18
+ {
19
+ return parent::all($options);
20
+ }
21
+
22
+ public function close()
23
+ {
24
+ $relativeUrl = $this->getEntityUrl() . $this->id;
25
+
26
+ $data = [
27
+ 'status' => 'closed'
28
+ ];
29
+
30
+ return $this->request('PATCH', $relativeUrl, $data);
31
+ }
32
+
33
+ public function payments()
34
+ {
35
+ $relativeUrl = $this->getEntityUrl() . $this->id . '/payments';
36
+
37
+ return $this->request('GET', $relativeUrl);
38
+ }
39
+ }
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: razorpay
3
  Tags: razorpay, payments, india, woocommerce, ecommerce
4
  Requires at least: 3.9.2
5
  Tested up to: 4.8
6
- Stable tag: 1.5.3
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
@@ -36,8 +36,9 @@ This is compatible with WooCommerce>=2.4, including the new 3.0 release. It has
36
 
37
  == Changelog ==
38
 
39
- = 1.6.0-beta =
40
- * Added support for subscriptions, please contact us at support@razorpay.com to enable it
 
41
 
42
  = 1.5.3 =
43
  * Webhooks are now disabled by default ([#52](https://github.com/razorpay/razorpay-woocommerce/pull/52))
3
  Tags: razorpay, payments, india, woocommerce, ecommerce
4
  Requires at least: 3.9.2
5
  Tested up to: 4.8
6
+ Stable tag: 1.6.0
7
  License: GPLv2 or later
8
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
9
 
36
 
37
  == Changelog ==
38
 
39
+ = 1.6.0 =
40
+ * Adds Razorpay Subscriptions plugin support.
41
+ * Code cleanup.
42
 
43
  = 1.5.3 =
44
  * Webhooks are now disabled by default ([#52](https://github.com/razorpay/razorpay-woocommerce/pull/52))
script.js CHANGED
@@ -40,22 +40,18 @@
40
  }
41
 
42
  function addEvent(element, evnt, funct) {
43
- if (element.attachEvent){
44
  return element.attachEvent('on' + evnt, funct);
45
- }
46
- else return element.addEventListener(evnt, funct, false);
47
  }
48
 
49
- if( document.readyState === 'complete' ) {
50
  addEvent(document.getElementById('btn-razorpay'), 'click', openCheckout);
51
  openCheckout();
52
- }
53
- else
54
- {
55
  document.addEventListener('DOMContentLoaded', function() {
56
  addEvent(document.getElementById('btn-razorpay'), 'click', openCheckout);
57
  openCheckout();
58
  });
59
  }
60
-
61
  })();
40
  }
41
 
42
  function addEvent(element, evnt, funct) {
43
+ if (element.attachEvent) {
44
  return element.attachEvent('on' + evnt, funct);
45
+ } else return element.addEventListener(evnt, funct, false);
 
46
  }
47
 
48
+ if (document.readyState === 'complete') {
49
  addEvent(document.getElementById('btn-razorpay'), 'click', openCheckout);
50
  openCheckout();
51
+ } else {
 
 
52
  document.addEventListener('DOMContentLoaded', function() {
53
  addEvent(document.getElementById('btn-razorpay'), 'click', openCheckout);
54
  openCheckout();
55
  });
56
  }
 
57
  })();