PageRenderTime 60ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/gateway-quickpay.php

https://bitbucket.org/perfectsolution/woocommerce-quickpay
PHP | 851 lines | 705 code | 121 blank | 25 comment | 93 complexity | c729ea772452d93c41129b32aa7ed06e MD5 | raw file
  1. <?php
  2. /*
  3. Plugin Name: WooCommerce Quickpay
  4. Plugin URI: https://bitbucket.org/perfectsolution/woocommerce-quickpay/src
  5. Description: Integrates your Quickpay payment getway into your WooCommerce installation.
  6. Version: 2.0.3
  7. Author: Patrick Pedersen
  8. Author URI: http://perfect-solution.dk
  9. */
  10. add_action('plugins_loaded', 'init_quickpay_gateway', 0);
  11. function init_quickpay_gateway() {
  12. if ( ! class_exists( 'WC_Payment_Gateway' )) { return; }
  13. // Add the gateway to WooCommerce
  14. function add_quickpay_gateway( $methods ) {
  15. $methods[] = 'WC_Quickpay'; return $methods;
  16. }
  17. class WC_Quickpay extends WC_Payment_Gateway {
  18. const PROTOCOL = 7;
  19. private $allowed_actions = array('capture', 'cancel', 'refund', 'splitcapture','renew','subscribe','recurring'),
  20. $gateway,
  21. $order,
  22. $ch,
  23. $payment_cancelled = 0;
  24. public function __construct() {
  25. $this->id = 'quickpay';
  26. $this->icon = '';
  27. $this->has_fields = false;
  28. $this->supports = array( 'subscriptions', 'products', 'subscription_cancellation', 'subscription_reactivation', 'subscription_suspension' );
  29. // Load the form fields and settings
  30. $this->init_form_fields();
  31. $this->init_settings();
  32. // Get gateway variables
  33. $this->title = $this->settings['title'];
  34. $this->description = $this->settings['description'];
  35. $this->gateway = (object) array(
  36. 'protocol' => self::PROTOCOL,
  37. 'merchant' => $this->settings['quickpay_merchantid'],
  38. 'apikey' => $this->settings['quickpay_apikey'],
  39. 'secret' => $this->settings['quickpay_md5secret'],
  40. 'language' => $this->settings['quickpay_language'],
  41. 'currency' => $this->settings['quickpay_currency'],
  42. 'autocapture' => $this->settings['quickpay_autocapture'] == 'yes' ? 1 : 0,
  43. 'cardtypelock' => $this->settings['quickpay_cardtypelock'],
  44. 'splitcapture' => $this->settings['quickpay_splitcapture'],
  45. 'captureoncomplete' => $this->settings['quickpay_captureoncomplete'],
  46. 'ibillOrCreditcard' => $this->settings['quickpay_ibillOrCreditcard']
  47. );
  48. // Actions
  49. add_action('woocommerce_api_wc_quickpay', array($this, 'check_quickpay_response'));
  50. add_action('quickpay-approved', array($this, 'process_complete'));
  51. add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
  52. add_action('woocommerce_receipt_quickpay', array($this, 'receipt_page'));
  53. add_action('shutdown', array($this, 'shutdown'));
  54. }
  55. // This check is runned every time a page request is made
  56. // -> Check if we are requesting a payment capture
  57. public function api_check_action() {
  58. if(is_admin()) {
  59. if(isset($_GET['quickpay_action']) AND in_array($_GET['quickpay_action'], $this->allowed_actions) AND isset($_GET['post'])) {
  60. global $woocommerce;
  61. $this->order = new WC_Order( intval( $_GET['post'] ) );
  62. if( $this->api_is_action_allowed( $_GET['quickpay_action'] ) ) {
  63. $this->api_action_router($this->order->id , $_GET['quickpay_action']);
  64. }
  65. }
  66. }
  67. }
  68. public function api_capture_on_order_status_complete() {
  69. global $order, $post, $woocommerce;
  70. $post_ids = array();
  71. if(isset($_GET['post']) AND is_array($_GET['post'])) {
  72. $post_ids = $_GET['post'];
  73. } elseif($post->ID === NULL) {
  74. if(isset($_GET['order_id'])) {
  75. $post_ids = array($_GET['order_id']);
  76. }
  77. }else {
  78. $post_ids = array($post->ID);
  79. }
  80. if( ! empty($post_ids)) {
  81. foreach($post_ids as $post_id) {
  82. $this->order = new WC_Order( $post_id );
  83. if($this->settings['quickpay_captureoncomplete'] == 'yes') {
  84. if($this->get_transaction_id() && $this->api_is_action_allowed('capture')) {
  85. $this->api_action_capture();
  86. }
  87. }
  88. }
  89. }
  90. }
  91. private function api_action_router( $order_id, $action ) {
  92. $this->order = new WC_Order( $order_id );
  93. if(in_array($action, $this->allowed_actions)) {
  94. call_user_func(array($this, 'api_action_' . $action));
  95. }
  96. }
  97. private function api_action_capture() {
  98. $response = WC_Quickpay_API::request($this->set_request_params('capture', array('amount' => $this->format_price($this->order->order_total), 'finalize' => 1)));
  99. if( ! is_wp_error($response)) {
  100. $this->note('Payment captured.');
  101. }
  102. }
  103. private function api_action_cancel() {
  104. $response = WC_Quickpay_API::request($this->set_request_params('cancel'));
  105. if( ! is_wp_error($response)) {
  106. if($this->subscr_is_active()) {
  107. if(WC_Subscriptions_Order::order_contains_subscription( $this->order )) {
  108. WC_Subscriptions_Manager::cancel_subscriptions_for_order( $this->order->id );
  109. }
  110. }
  111. $this->note('Payment cancelled.');
  112. }
  113. }
  114. private function api_action_renew() {
  115. $response = WC_Quickpay_API::request($this->set_request_params('renew'));
  116. if( ! is_wp_error($response)) {
  117. $this->note('Payment renewed.');
  118. }
  119. }
  120. private function api_action_refund() {
  121. $response = WC_Quickpay_API::request($this->set_request_params('refund', array('amount' => $this->format_price($this->order->order_total))));
  122. if( ! is_wp_error($response)) {
  123. $this->note('Payment refunded.');
  124. }
  125. }
  126. private function api_action_splitcapture() {
  127. if(! isset($_GET['amount']) )
  128. return FALSE;
  129. $finalize = (isset($_GET['quickpay_finalize']) AND $_GET['quickpay_finalize'] == 'yes') ? 1 : 0;
  130. $response = WC_Quickpay_API::request($this->set_request_params('capture', array('amount' => $this->format_price( $_GET['amount'] ), 'finalize' => $finalize)));
  131. if( ! is_wp_error($response)) {
  132. $this->note('Split capture: ' . $this->deformat_price($amount).$this->gateway->currency);
  133. }
  134. }
  135. private function api_action_recurring() {
  136. $response = WC_Quickpay_API::request($this->set_request_params('recurring', array(
  137. 'ordernumber' => time().'qp'.$this->get_order_number(),
  138. 'amount' => $this->format_price( WC_Subscriptions_Order::get_price_per_period( $this->order ) ),
  139. 'currency' => $this->gateway->currency,
  140. 'autocapture' => 1
  141. )));
  142. if( ! is_wp_error($response)) {
  143. $this->note('Recurring payment captured.');
  144. }
  145. }
  146. private function set_request_params($action, $fields = array()) {
  147. $params = array(
  148. 'protocol' => $this->gateway->protocol,
  149. 'msgtype' => $action,
  150. 'merchant' => $this->gateway->merchant
  151. );
  152. $append = array(
  153. 'transaction' => $this->get_transaction_id(),
  154. 'apikey' => $this->gateway->apikey,
  155. 'secret' => $this->gateway->secret
  156. );
  157. if( ! empty($fields)) {
  158. foreach($fields as $key => $value) {
  159. $params[$key] = $value;
  160. }
  161. }
  162. return array_merge($params, $append);
  163. }
  164. private function api_payment_status( $field ) {
  165. $response = WC_Quickpay_API::request(
  166. array(
  167. 'protocol' => $this->gateway->protocol,
  168. 'msgtype' => 'status',
  169. 'merchant' => $this->gateway->merchant,
  170. 'transaction' => $this->get_transaction_id(),
  171. 'apikey' => $this->gateway->apikey,
  172. 'secret' => $this->gateway->secret
  173. )
  174. );
  175. if( ! is_wp_error($response)) {
  176. if( $field == 'state' )
  177. return $response->state;
  178. else if( $field == 'msgtype' )
  179. return $response->history[count($response->history) -1]->msgtype;
  180. else if( $field == 'balance' )
  181. return $response->balance;
  182. }
  183. return FALSE;
  184. }
  185. private function api_is_action_allowed($action, $state = NULL) {
  186. if($state === NULL) {
  187. $state = $this->api_payment_status("state");
  188. }
  189. $allowed_states = array(
  190. 'capture' => array(1),
  191. 'cancel' => array(1, 9),
  192. 'refund' => array(3),
  193. 'renew' => array(1),
  194. 'splitcapture' => array(1,3),
  195. 'regular_header' => array(1,9,3),
  196. 'recurring' => array(9)
  197. );
  198. if($action === 'splitcapture') {
  199. $allowed_states['capture'] = array(1,3);
  200. }
  201. if(in_array($state, $allowed_states[$action]))
  202. return TRUE;
  203. else
  204. return FALSE;
  205. }
  206. private function note($message) {
  207. if(is_object($this->order)) {
  208. $this->order->add_order_note('Quickpay: ' . $message);
  209. }
  210. }
  211. public function process_complete() {
  212. global $woocommerce;
  213. $woocommerce->cart->empty_cart();
  214. }
  215. public function payment_fields() {
  216. if ($this->description) echo wpautop(wptexturize($this->description));
  217. if($this->gateway->ibillOrCreditcard === 'yes') {
  218. echo '<ul style="list-style:none;">';
  219. echo '<li><input style="margin:0;" type="radio" name="quickpay-gwType" value="creditcard" checked/> Credit card</li>';
  220. echo '<li><input style="margin:0;" type="radio" name="quickpay-gwType" value="iBill" /> iBill</li>';
  221. echo '</ul>';
  222. }
  223. }
  224. public function receipt_page( $order ) {
  225. $this->js_enqueue();
  226. echo $this->generate_quickpay_button( $order );
  227. }
  228. public function process_payment( $order_id ) {
  229. $this->order = new WC_Order( $order_id );
  230. $gwType = NULL;
  231. if($this->gateway->ibillOrCreditcard === 'yes') {
  232. if(isset($_POST['quickpay-gwType']) AND in_array($_POST['quickpay-gwType'], array('iBill', 'creditcard'))) {
  233. $gwType = $_POST['quickpay-gwType'];
  234. }
  235. }
  236. return array(
  237. 'result' => 'success',
  238. 'redirect' => add_query_arg('order', $this->order->id, add_query_arg('key', $this->order->order_key, add_query_arg('gwType', $gwType, get_permalink(get_option('woocommerce_pay_page_id')))))
  239. );
  240. }
  241. public function scheduled_subscription_payment($amount_to_charge, $order, $product_id) {
  242. $this->order = $order;
  243. $this->api_action_recurring();
  244. }
  245. private function generate_quickpay_button( $order_id ) {
  246. $this->order = new WC_Order( $order_id );
  247. $subscription = ($this->subscr_is_active()) ? WC_Subscriptions_Order::order_contains_subscription( $this->order ) : FALSE;
  248. $ordernumber = substr(md5(time()), 0, 3).'-QP-'.str_pad($this->get_order_number() , 4, 0, STR_PAD_LEFT);
  249. $query_args_cancellation = array('order' => $order_id, 'payment_cancellation' => 'yes');
  250. $query_args_callback = array('order' => $order_id, 'qp_callback' => 'true');
  251. $continueurl = add_query_arg('key', $this->order->order_key, add_query_arg('order', $order_id, get_permalink(get_option('woocommerce_thanks_page_id'))));
  252. $cancelurl = add_query_arg('key', $this->order->order_key, add_query_arg($query_args_cancellation, get_permalink(get_option('woocommerce_cart_page_id'))));
  253. $callbackurl = str_replace( 'https:', 'http:', add_query_arg( 'wc-api', 'WC_Quickpay', home_url( '/' ) ) );
  254. if($subscription) {
  255. $msgtype = 'subscribe';
  256. $amount = $this->format_price(WC_Subscriptions_Order::get_price_per_period( $this->order ));
  257. $description = 'qp_subscriber';
  258. } else {
  259. $msgtype = 'authorize';
  260. $amount = $this->format_price($this->order->order_total);
  261. $description = '';
  262. }
  263. if($this->settings['quickpay_ibillOrCreditcard'] === 'yes' AND isset($_GET['gwType']) AND (strtolower($_GET['gwType']) === 'ibill') ) {
  264. $cardtypelock = strtolower($_GET['gwType']);
  265. } else {
  266. $cardtypelock = $this->gateway->cardtypelock;
  267. }
  268. $md5check = md5(
  269. self::PROTOCOL . $msgtype . $this->settings['quickpay_merchantid'] . $this->settings['quickpay_language'] . $ordernumber
  270. .$amount . $this->settings['quickpay_currency'] . $continueurl . $cancelurl . $callbackurl . $this->gateway->autocapture
  271. .$cardtypelock . $description . $this->settings['quickpay_md5secret']
  272. );
  273. echo 'You will be automatically redirected to the payment window in <strong>5 seconds</strong>. Click on the button below if you are not redirected.';
  274. echo '
  275. <form id="quickpay_payment_form" action="https://secure.quickpay.dk/form/" method="post">
  276. <input type="hidden" name="protocol" value="'.self::PROTOCOL.'" />
  277. <input type="hidden" name="msgtype" value="'.$msgtype.'" />
  278. <input type="hidden" name="merchant" value="'.$this->gateway->merchant.'" />
  279. <input type="hidden" name="language" value="'.$this->gateway->language.'" />
  280. <input type="hidden" name="ordernumber" value="'.$ordernumber.'" />
  281. <input type="hidden" name="amount" value="'.$amount.'" />
  282. <input type="hidden" name="currency" value="'.$this->gateway->currency.'" />
  283. <input type="hidden" name="continueurl" value="'.$continueurl.'" />
  284. <input type="hidden" name="cancelurl" value="'.$cancelurl.'" />
  285. <input type="hidden" name="callbackurl" value="'.$callbackurl.'" />
  286. <input type="hidden" name="autocapture" value="'.$this->gateway->autocapture.'" />
  287. <input type="hidden" name="cardtypelock" value="'.$cardtypelock.'" />';
  288. if($subscription) {
  289. echo'<input type="hidden" name="description" value="'.$description.'" />';
  290. }
  291. echo '
  292. <input type="hidden" name="md5check" value="'.$md5check.'" />
  293. <input type="submit" value="'.$this->settings['quickpay_paybuttontext'].'" />
  294. </form>
  295. ';
  296. }
  297. public function on_order_cancellation($order_id) {
  298. global $woocommerce;
  299. $this->order = new WC_Order( $order_id );
  300. // Redirect the customer to account page if the current order is failed
  301. if($this->order->status == 'failed') {
  302. $woocommerce->add_error(__('<p><strong>Payment failure</strong>', 'woocommerce') . ': A problem with your payment on order <strong>#'.$this->order->id.'</strong> occured. Please try again to complete your order.</p>');
  303. wp_redirect(get_permalink(get_option('woocommerce_myaccount_page_id')));
  304. }
  305. $this->order->add_order_note('Quickpay Payment: Cancelled during process.');
  306. $woocommerce->add_error(__('<p><strong>Payment cancelled</strong>', 'woocommerce') . ': Due to cancellation of your payment, the order process was not completed. Please fulfill the payment to complete your order.</p>');
  307. }
  308. public function create_md5($p) {
  309. $cardexpire = isset($p['cardexpire']) ? $p['cardexpire'] : '';
  310. $md5 = $p['msgtype'].
  311. $p['ordernumber'].
  312. $p['amount'].
  313. $p['currency'].
  314. $p['time'].
  315. $p['state'].
  316. $p['qpstat'].
  317. $p['qpstatmsg'].
  318. $p['chstat'].
  319. $p['chstatmsg'].
  320. $p['merchant'].
  321. $p['merchantemail'].
  322. $p['transaction'].
  323. $p['cardtype'].
  324. $p['cardnumber'].
  325. $p['cardhash'].
  326. $cardexpire.
  327. $p['acquirer'].
  328. $p['splitpayment'].
  329. $p['fraudprobability'].
  330. $p['fraudremarks'].
  331. $p['fraudreport'].
  332. $p['fee'].
  333. $this->gateway->secret;
  334. return md5($md5);
  335. }
  336. public function check_quickpay_response() {
  337. $response = isset($_POST['qpstat']) ? (object) $_POST : FALSE;
  338. if($response) {
  339. // Fetch order number;
  340. preg_match('/\d{4,}$/', $response->ordernumber, $order_number);
  341. $order_number = end($order_number);
  342. $order_number = $this->find_order_by_order_number($order_number);
  343. $this->order = new WC_Order($order_number);
  344. // Update post meta
  345. update_post_meta( $this->order->id, 'TRANSACTION_ID', esc_attr($response->transaction) );
  346. // Check if order is a subscription
  347. $subscription = ($this->subscr_is_active()) ? WC_Subscriptions_Order::order_contains_subscription( $this->order ) : FALSE;
  348. if(WC_Quickpay_API::validate_response($response, $this->settings['quickpay_md5secret'])) {
  349. if($subscription) {
  350. if($this->order->status === 'completed') {
  351. $order_note = 'Subscription payment captured.';
  352. } else {
  353. $this->order->update_status('completed');
  354. $this->order->reduce_order_stock();
  355. $order_note = 'Subscription sign up completed.';
  356. }
  357. if($this->api_action_recurring()) {
  358. $this->order->add_order_note($order_note);
  359. WC_Subscriptions_Manager::process_subscription_payments_on_order( $this->order->id, $product_id );
  360. }
  361. } else {
  362. $this->order->update_status('processing');
  363. $this->order->reduce_order_stock();
  364. $this->note('Payment authorized.');
  365. }
  366. } else {
  367. $this->order->add_order_note('Quickpay payment FAILED. Message from Quickpay: ' . $response->qpstatmsg);
  368. if($subscription) {
  369. WC_Subscriptions_Manager::process_subscription_payment_failure_on_order( $this->order );
  370. } else {
  371. $this->order->update_status('failed');
  372. }
  373. }
  374. }
  375. }
  376. private function get_order_number() {
  377. $order_number = null;
  378. if(isset($this->order)) {
  379. $order_number = str_replace('#','', $this->order->get_order_number());
  380. }
  381. return $order_number;
  382. }
  383. public function find_order_by_order_number( $order_number ) {
  384. // search for the order by custom order number
  385. $query_args = array(
  386. 'numberposts' => 1,
  387. 'meta_key' => '_order_number',
  388. 'meta_value' => $order_number,
  389. 'post_type' => 'shop_order',
  390. 'post_status' => 'publish',
  391. 'fields' => 'ids'
  392. );
  393. list( $order_id ) = get_posts( $query_args );
  394. // order was found
  395. if ( $order_id !== null ) return $order_id;
  396. // if we didn't find the order, then it may be that this plugin was disabled and an order was placed in the interim
  397. $order = new WC_Order( $order_number );
  398. if ( isset( $order->order_custom_fields['_order_number'][0] ) ) {
  399. // _order_number was set, so this is not an old order, it's a new one that just happened to have post_id that matched the searched-for order_number
  400. return 0;
  401. }
  402. return $order->id;
  403. }
  404. public function shutdown() {
  405. if( ! empty($this->ch)) {
  406. curl_close($this->ch);
  407. }
  408. }
  409. public function init_form_fields() {
  410. $this->form_fields = array(
  411. 'enabled' => array(
  412. 'title' => __( 'Enable/Disable', 'woothemes' ),
  413. 'type' => 'checkbox',
  414. 'label' => __( 'Enable Quickpay Payment', 'woothemes' ),
  415. 'default' => 'yes'
  416. ),
  417. '_Account_setup' => array(
  418. 'type' => 'title',
  419. 'title' => 'Quickpay account'
  420. ),
  421. 'quickpay_merchantid' => array(
  422. 'title' => __('Quickpay Merchant ID', 'woothemes'),
  423. 'type' => 'text',
  424. 'description' => __('Type in your merchant ID from Quickpay.', 'woothemes')
  425. ),
  426. 'quickpay_md5secret' => array(
  427. 'title' => __('Secret MD5 string', 'woothemes'),
  428. 'type' => 'text',
  429. 'description' => __('This is the unique MD5 secret key, which the system uses to verify your transactions.')
  430. ),
  431. 'quickpay_apikey' => array(
  432. 'title' => __('Quickpay API key'),
  433. 'type' => 'text',
  434. 'description' => 'The API key is unique and can be requested from within the Quickpay Administrator Tool'
  435. ),
  436. '_Extra_gateway_settings' => array(
  437. 'type' => 'title',
  438. 'title' => 'Extra gateway settings'
  439. ),
  440. 'quickpay_language' => array(
  441. 'title' => __('Language', 'woothemes'),
  442. 'description' => __('Payment Window Language', 'woothemes'),
  443. 'type' => 'select',
  444. 'options' => array(
  445. 'da' => 'Danish',
  446. 'de'=>'German',
  447. 'en'=>'English',
  448. 'fr'=>'French',
  449. 'it'=>'Italian',
  450. 'no'=>'Norwegian',
  451. 'nl'=>'Dutch',
  452. 'pl'=>'Polish',
  453. 'se'=>'Swedish'
  454. )
  455. ),
  456. 'quickpay_currency' => array(
  457. 'title' => __('Currency', 'woothemes'),
  458. 'description' => 'Choose your currency. Please make sure to use the same currency as in your WooCommerce currency settings.',
  459. 'type' => 'select',
  460. 'options' => array(
  461. 'DKK' => 'DKK',
  462. 'EUR' => 'EUR',
  463. 'USD' => 'USD'
  464. )
  465. ),
  466. 'quickpay_cardtypelock' => array(
  467. 'title' => __( 'Cardtype lock', 'woothemes' ),
  468. 'type' => 'text',
  469. 'description' => __( 'Default: creditcard. Type in the cards you wish to accept (comma separated). See the valid payment types here: <b>http://quickpay.dk/features/cardtypelock/</b>', 'woothemes' ),
  470. 'default' => __( 'creditcard', 'woothemes' )
  471. ),
  472. 'quickpay_splitcapture' => array(
  473. 'title' => __( 'Enable split payments', 'woothemes' ),
  474. 'type' => 'checkbox',
  475. 'label' => __( 'Accept split payments in your system.', 'woothemes' ),
  476. 'description' => __( 'Remember to turn this on in your Quickpay Manager. Click for <a target="_blank" href="http://quickpay.dk/features/split-payment/">help</a>', 'woothemes' ),
  477. 'default' => 'yes'
  478. ),
  479. 'quickpay_autocapture' => array(
  480. 'title' => __( 'Allow autocapture', 'woothemes' ),
  481. 'type' => 'checkbox',
  482. 'label' => __( 'Enable/Disable', 'woothemes' ),
  483. 'description' => __( 'Automatically capture payments.' ),
  484. 'default' => 'no'
  485. ),
  486. 'quickpay_captureoncomplete' => array(
  487. 'title' => __( 'Capture on complete', 'woothemes' ),
  488. 'type' => 'checkbox',
  489. 'label' => __( 'Enable/Disable', 'woothemes' ),
  490. 'description' => __( 'When enabled quickpay payments will automatically be captured when order state is set to "Complete".' ),
  491. 'default' => 'no'
  492. ),
  493. 'quickpay_ibillOrCreditcard' => array(
  494. 'title' => __( 'Choose credit card or iBill on payment selection', 'woothemes' ),
  495. 'type' => 'checkbox',
  496. 'label' => __( 'Enable/Disable', 'woothemes' ),
  497. 'description' => __( 'Allows your customers to choose between iBill and credit card when choosing type of payment. <b>(Requires iBill agreement)</b>' ),
  498. 'default' => 'no'
  499. ),
  500. '_Shop_setup' => array(
  501. 'type' => 'title',
  502. 'title' => 'Shop setup'
  503. ),
  504. 'title' => array(
  505. 'title' => __( 'Title', 'woothemes' ),
  506. 'type' => 'text',
  507. 'description' => __( 'This controls the title which the user sees during checkout.', 'woothemes' ),
  508. 'default' => __( 'Quickpay', 'woothemes' )
  509. ),
  510. 'description' => array(
  511. 'title' => __( 'Customer Message', 'woothemes' ),
  512. 'type' => 'textarea',
  513. 'description' => __( 'This controls the description which the user sees during checkout.', 'woothemes' ),
  514. 'default' => 'Pay via Quickpay. Allows you to pay with your credit card via Quickpay.'
  515. ),
  516. 'quickpay_paybuttontext' => array(
  517. 'title' => __( 'Text on payment button', 'woothemes' ),
  518. 'type' => 'text',
  519. 'description' => __( 'This text will show on the button which opens the Quickpay window.', 'woothemes' ),
  520. 'default' => __( 'Open secure Quickpay window.', 'woothemes' )
  521. )
  522. );
  523. if($this->subscr_is_active()) {
  524. $this->form_fields['woocommerce-subscriptions'] = array(
  525. 'type' => 'title',
  526. 'title' => 'Subscriptions'
  527. );
  528. $this->form_fields['quickpay_autodraw_subscription'] = array(
  529. 'title' => __( 'Automatically capture the first payment of a subscription.', 'woothemes' ),
  530. 'type' => 'checkbox',
  531. 'label' => __( 'Enable/Disable', 'woothemes' ),
  532. 'description' => __( 'Automatically capture the first payment of a subscription when order is complete.' ),
  533. 'default' => 'yes'
  534. );
  535. }
  536. }
  537. public function admin_options() {
  538. ?>
  539. <h3><?php _e('Quickpay', 'woothemes'); ?></h3>
  540. <p><?php _e('Allows you to receive payments via Quickpay.', 'woothemes'); ?></p>
  541. <table class="form-table">
  542. <?php $this->generate_settings_html(); ?>
  543. </table>
  544. <?php
  545. }
  546. public function quickpay_meta_boxes() {
  547. add_meta_box('quickpay-payment-actions', __('Quickpay Payment', 'woocommerce'), array(&$this, 'quickpay_meta_box_payment'), 'shop_order', 'side', 'high');
  548. }
  549. public function quickpay_meta_box_payment() {
  550. global $post, $woocommerce;
  551. $this->order = new WC_Order( $post->ID );
  552. $transaction_id = $this->get_transaction_id();
  553. $state = $this->api_payment_status('state');
  554. $balance = $this->api_payment_status('balance');
  555. if($transaction_id) { ?>
  556. <p><strong>Current payment state: <?php echo $this->format_msgtype($this->api_payment_status("msgtype"))?></strong></p>
  557. <?php if($this->api_is_action_allowed('regular_header', $state)) : ?>
  558. <h3>Standard actions</h3>
  559. <ul class="order_actions">
  560. <?php if($this->api_is_action_allowed('capture', $state)) : ?>
  561. <li>
  562. <a class="button button-primary" onclick="return notify('You are about to CAPTURE this payment.');" href="<?php echo admin_url('post.php?post='.$post->ID.'&action=edit&quickpay_action=capture'); ?>"><?php _e('Capture', 'woocommerce'); ?></a>
  563. </li>
  564. <?php endif; ?>
  565. <?php if($this->api_is_action_allowed('cancel', $state)) : ?>
  566. <li>
  567. <a class="button" onclick="return notify('You are about to CANCEL this payment.')" href="<?php echo admin_url('post.php?post='.$post->ID.'&action=edit&quickpay_action=cancel'); ?>"><?php _e('Cancel', 'woocommerce'); ?></a>
  568. </li>
  569. <?php endif; ?>
  570. <?php if($this->api_is_action_allowed('refund', $state)) : ?>
  571. <li>
  572. <a class="button" onclick="return notify('You are about to REFUND this payment.')" href="<?php echo admin_url('post.php?post='.$post->ID.'&action=edit&quickpay_action=refund'); ?>"><?php _e('Refund', 'woocommerce'); ?></a>
  573. </li>
  574. <li>&nbsp;</li>
  575. <?php endif; ?>
  576. </ul>
  577. <?php endif; ?>
  578. <br />
  579. <?php if($this->gateway->splitcapture == 'yes' AND $this->api_is_action_allowed('splitcapture', $state) AND $balance < $this->format_price($this->order->order_total)) : ?>
  580. <h3>Split payment</h3>
  581. <ul class="order_actions">
  582. <li style="text-align:left;">Balance: <?php echo $this->deformat_price($balance)?> <?php echo $this->gateway->currency ?></li>
  583. <li style="text-align:left;">
  584. <span id="quickpay_balance_container">
  585. Remaining:
  586. <span id="quickpay_balance"><?php echo $this->deformat_price($this->format_price($this->order->order_total)-$balance)?></span>
  587. <?php echo $this->gatewat->currency ?>
  588. </span>
  589. </li>
  590. </ul>
  591. <ul>
  592. <li>
  593. <p>
  594. <span><input type="text" onkeyup="quickpay_url_modify()" style="width:87%;text-align:right;" id="quickpay_split_amount" name="quickpay_split_amount" /></span><span> <?php echo $this->gateway->currency?>
  595. </p>
  596. <p>
  597. <span><a id="quickpay_split_button" class="button" onclick="return notify('You are about to SPLIT CAPTURE this payment. This means that you will capture the amount stated in the input field. The payment state will remain open.')" href="<?php echo admin_url('post.php?post='.$post->ID.'&action=edit&quickpay_action=splitcapture'); ?>"><?php _e('Split Capture', 'woocommerce'); ?></a></span>
  598. <span><a id="quickpay_split_finalize_button" class="button" onclick="return notify('You are about to SPLIT CAPTURE and FINALIZE this payment. This means that you will capture the amount stated in the input field and that you can no longer capture money from this transaction.')" href="<?php echo admin_url('post.php?post='.$post->ID.'&action=edit&quickpay_action=splitcapture&quickpay_finalize=yes'); ?>"><?php _e('Split and finalize', 'woocommerce'); ?></a></span>
  599. </p>
  600. </li>
  601. <?php endif; ?>
  602. </ul>
  603. <?php
  604. }
  605. }
  606. public function add_custom_order_data($column) {
  607. global $post, $woocommerce;
  608. $this->order = new WC_Order( $post->ID );
  609. // Show transaction ID on the overview
  610. if($column == 'billing_address') {
  611. // Get the transaction status
  612. $status = $this->api_payment_status('msgtype');
  613. // Insert transaction id and payment status if any
  614. $transaction_id = $this->get_transaction_id();
  615. if($transaction_id) {
  616. echo '<small class=\"meta\">Transaction id: '.$transaction_id.'</small><br />';
  617. echo '<small style="color:'.$this->colors( $status ).'">Payment state: '.$this->format_msgtype($status).'</small>';
  618. }
  619. }
  620. }
  621. private function get_transaction_id() {
  622. if(is_object($this->order)) {
  623. $transaction_id = get_post_meta( $this->order->id , 'TRANSACTION_ID', true);
  624. if($transaction_id != '') {
  625. return $transaction_id;
  626. } else {
  627. return false;
  628. }
  629. } else {
  630. return false;
  631. }
  632. }
  633. private function subscr_is_active() {
  634. if( ! function_exists(is_plugin_active)) {
  635. include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
  636. }
  637. if(is_plugin_active('woocommerce-subscriptions/woocommerce-subscriptions.php')) {
  638. return true;
  639. }
  640. return false;
  641. }
  642. private function colors( $msgtype ) {
  643. $colors = array('capture' => '#85ad74', 'cancel' => '#ad74a2', 'subscribe' => '#85ad74');
  644. $msgtype = trim($msgtype);
  645. if(array_key_exists($msgtype, $colors))
  646. return $colors[$msgtype];
  647. else
  648. return '#7499ad';
  649. }
  650. private function format_price( $price ) {
  651. return number_format($price * 100, 0, '', '');
  652. }
  653. private function deformat_price( $price ) {
  654. return number_format($price / 100, 2, '.', '');
  655. }
  656. private function format_msgtype( $msgtype ) {
  657. $lc = substr($msgtype , -1);
  658. if($lc == 'l')
  659. $append = 'led';
  660. else
  661. $append = ($lc == 'e') ? 'd' : 'ed';
  662. return $msgtype . $append;
  663. }
  664. private function js_confirm( $message ) {
  665. return "javascript:";
  666. }
  667. public function js_enqueue() {
  668. wp_enqueue_script('core', plugins_url( '/core.js', __FILE__ ), array('jquery'));
  669. }
  670. }
  671. class WC_Quickpay_API {
  672. private static $error = NULL;
  673. private static $ch = NULL;
  674. public static function validate_response($response, $secret) {
  675. if(isset($response->ordernumber) AND isset($response->qpstat)) {
  676. if(self::response_md5($response, $secret) == $response->md5check AND $response->qpstat === '000') {
  677. return TRUE;
  678. }
  679. return FALSE;
  680. }
  681. return FALSE;
  682. }
  683. public static function response_md5($p, $secret) {
  684. if(is_object($p)) {
  685. $cardexpire = isset($p->cardexpire) ? $p->cardexpire : '';
  686. $md5 = $p->msgtype.$p->ordernumber.$p->amount.$p->currency.$p->time.$p->state.$p->qpstat.$p->qpstatmsg.
  687. $p->chstat.$p->chstatmsg.$p->merchant.$p->merchantemail.$p->transaction.$p->cardtype.$p->cardnumber.
  688. $p->cardhash.$cardexpire.$p->acquirer.$p->splitpayment.$p->fraudprobability.$p->fraudremarks.$p->fraudreport.
  689. $p->fee.$secret;
  690. return md5($md5);
  691. }
  692. return FALSE;
  693. }
  694. public static function request_md5($settings) {
  695. return md5( implode('' , $settings) );
  696. }
  697. public function request($params) {
  698. foreach($params as $key=>$value) {
  699. $params_string .= $key .'='.$value.'&';
  700. }
  701. $params_string = rtrim($params_string,'&');
  702. curl_setopt(self::curl(), CURLOPT_POSTFIELDS, $params_string.'&md5check=' . self::request_md5($params) );
  703. $response = simplexml_load_string(curl_exec(self::curl()));
  704. if($response->qpstat == '000') {
  705. return $response;
  706. }
  707. return new WP_Error('request-error', $response->qpstatmsg);
  708. }
  709. private static function curl() {
  710. if(self::$ch === NULL) {
  711. self::$ch = curl_init();
  712. curl_setopt(self::$ch, CURLOPT_URL, 'https://secure.quickpay.dk/api');
  713. curl_setopt(self::$ch, CURLOPT_POST, TRUE);
  714. curl_setopt(self::$ch, CURLOPT_RETURNTRANSFER, TRUE);
  715. }
  716. return self::$ch;
  717. }
  718. }
  719. add_filter('woocommerce_payment_gateways', 'add_quickpay_gateway' );
  720. if(is_admin()) {
  721. $WC_Quickpay = new WC_Quickpay();
  722. add_filter('manage_shop_order_posts_custom_column',array($WC_Quickpay, 'add_custom_order_data'));
  723. add_action('wp_before_admin_bar_render', array($WC_Quickpay, 'api_check_action'));
  724. add_action('add_meta_boxes', array($WC_Quickpay, 'quickpay_meta_boxes'));
  725. add_action('scheduled_subscription_payment_quickpay', array($WC_Quickpay, 'scheduled_subscription_payment'), 10, 3);
  726. add_action('woocommerce_order_status_completed', array($WC_Quickpay, 'api_capture_on_order_status_complete'));
  727. add_action('admin_menu', array($WC_Quickpay, 'js_enqueue'));
  728. }
  729. }
  730. ?>