class-wc-gateway-laybuy.php 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) {
  3. exit;
  4. }
  5. class WC_Payment_Gateway_Laybuy extends WC_Payment_Gateway
  6. {
  7. const GATEWAY_NAME = 'laybuy';
  8. const PAYMENTS_COUNT = 6;
  9. const COOKIE_GEOLOCATION_COUNTRY = 'laybuy_geo_country';
  10. const LOGO_THEME_WHITE = 'white';
  11. const LOGO_THEME_DARK = 'dark';
  12. protected $supported_currencies;
  13. protected $pay_over_time_limit_max = 1200;
  14. protected $pay_over_time_limit_min = 0.06;
  15. protected $pay_limits_max = [
  16. 'NZD' => 1500,
  17. 'AUD' => 1200,
  18. 'GBP' => 720,
  19. 'USD' => 1200,
  20. ];
  21. protected $compatibility_mode = false;
  22. protected $assets = [];
  23. protected $apiGateway;
  24. protected static $instance;
  25. const MODE_BASIC = 'basic';
  26. const MODE_PLUS = 'plus';
  27. protected $mode = self::MODE_BASIC;
  28. public function __construct() {
  29. $this->id = self::GATEWAY_NAME;
  30. $this->icon = apply_filters( 'woocommerce_laybuy_gateway_icon', 'https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_logo_small.svg' );
  31. $this->has_fields = false;
  32. $this->method_title = 'Laybuy';
  33. $this->method_description = __( 'Use Laybuy as a credit card processor for WooCommerce.', 'woo_laybuy' );
  34. $this->supported_currencies = array(
  35. 'AUD', 'NZD', 'GBP', 'USD'
  36. );
  37. if (defined('WC_LAYBUY_PLUS')) {
  38. $this->mode = self::MODE_PLUS;
  39. }
  40. // Get setting values.
  41. $this->title = $this->get_option( 'title' );
  42. $this->description = 'Pay by Laybuy';
  43. $this->enabled = $this->get_option( 'enabled' ) === 'yes';
  44. $this->assets = include 'assets.php';
  45. $currency = get_woocommerce_currency();
  46. if (!empty($this->assets[strtolower($currency)])) {
  47. $this->assets = $this->assets[strtolower($currency)];
  48. } else {
  49. $this->assets = $this->assets['aud'];
  50. }
  51. $this->supports = array('products', 'refunds');
  52. $this->init_form_fields();
  53. $this->init_settings();
  54. if ($this->enabled === 'no') {
  55. return;
  56. }
  57. $currency = get_woocommerce_currency();
  58. if (in_array($currency, $this->supported_currencies)) {
  59. if ($this->is_plus()) {
  60. $this->pay_over_time_limit_min = floatval($this->settings["${currency}_pay_limit_min"]);
  61. $this->pay_over_time_limit_max = floatval($this->settings["${currency}_pay_limit_max"]);
  62. $this->check_pay_over_time_limits();
  63. } else {
  64. $this->pay_over_time_limit_max = $this->pay_limits_max[$currency];
  65. }
  66. }
  67. if (!is_admin()) {
  68. $this->compatibility_mode = $this->settings['laybuy_compatibility_mode'] == 'yes';
  69. }
  70. $this->apiGateway = new Laybuy_ApiGateway($this->settings);
  71. if (array_key_exists('debug', $this->settings)) {
  72. WC_Laybuy_Logger::$enabled = $this->settings['debug'] === 'yes';
  73. }
  74. }
  75. /**
  76. * Instantiate the class if no instance exists. Return the instance.
  77. *
  78. * @since 2.0.0
  79. * @return WC_Payment_Gateway_Laybuy
  80. */
  81. public static function getInstance()
  82. {
  83. if (is_null(self::$instance)) {
  84. self::$instance = new self;
  85. }
  86. return self::$instance;
  87. }
  88. /**
  89. * Initialise Gateway Settings Form Fields
  90. */
  91. public function init_form_fields() {
  92. $this->form_fields = require( dirname( __FILE__ ) . '/laybuy-settings.php' );
  93. }
  94. /**
  95. * Is currency enabled for laybuy
  96. * @return bool
  97. */
  98. public function is_currency_enabled($currency) {
  99. return in_array($currency, (array) @$this->settings['currency']);
  100. }
  101. public function payment_fields() {
  102. if ($description = $this->get_description()) {
  103. $description = apply_filters('checkout_modify_description', $description, $this->get_order_total());
  104. echo wpautop(wptexturize($description));
  105. }
  106. }
  107. /**
  108. * Process a refund if supported.
  109. *
  110. * Note: This overrides the method defined in the parent class.
  111. *
  112. * @since 1.0.0
  113. * @see WC_Payment_Gateway::process_refund() For the method that this overrides.
  114. * @param int $order_id
  115. * @param float $amount Optional. The amount to refund. This cannot exceed the total.
  116. * @param string $reason Optional. The reason for the refund. Defaults to an empty string.
  117. * @return bool
  118. */
  119. public function process_refund($order_id, $amount = null, $reason = '') {
  120. $order_id = (int) $order_id;
  121. if (function_exists('wc_get_order')) {
  122. $order = wc_get_order( $order_id );
  123. } else {
  124. $order = new WC_Order( $order_id );
  125. }
  126. if (method_exists($this, 'can_refund_order') && !$this->can_refund_order($order)) {
  127. WC_Laybuy_Logger::error('Refund Failed - No Transaction ID.');
  128. return false;
  129. }
  130. $processManager = Laybuy_ProcessManager::getInstance();
  131. $processManager->setApiGateway($this->apiGateway)
  132. ->setWcGateway($this);
  133. return $processManager->refund($order_id, $amount, $reason);
  134. }
  135. public function generate_wysiwyg_html($key, $data) {
  136. $html = '';
  137. $id = str_replace('-', '', $key);
  138. $class = array_key_exists('class', $data) ? $data['class'] : '';
  139. $css = array_key_exists('css', $data) ? ('<style>' . $data['css'] . '</style>') : '';
  140. $name = "{$this->plugin_id}{$this->id}_{$key}";
  141. $title = array_key_exists('title', $data) ? $data['title'] : '';
  142. $value = array_key_exists($key, $this->settings) ? esc_attr( $this->settings[$key] ) : '';
  143. $description = array_key_exists('description', $data) ? $data['description'] : '';
  144. ob_start();
  145. include dirname( __FILE__ ) . '/admin/wysiwyg.php';
  146. $html = ob_get_clean();
  147. return $html;
  148. }
  149. /**
  150. * Process the HTML from one of the rich text editors and output the converted string.
  151. */
  152. private function process_and_print_laybuy_paragraph($output_filter, $product = null) {
  153. if (is_admin()) {
  154. return;
  155. }
  156. if (is_null($product)) {
  157. $product = $this->get_product_from_the_post();
  158. }
  159. if (!$this->is_product_supported($product, true)) {
  160. # Don't display anything on the product page if the product is not supported when purchased on its own.
  161. return;
  162. }
  163. if (!$this->is_currency_supported()) {
  164. # Don't display anything on the product page if the website currency is not within supported currencies.
  165. return;
  166. }
  167. if (!$product->is_in_stock() && $this->settings['laybuy_price_breakdown_out_of_stock'] == 'no') {
  168. return;
  169. }
  170. $of_or_from = 'of';
  171. $price = $this->getProductPrice($product);
  172. $price = floatval($price);
  173. if (!$price) {
  174. return '';
  175. }
  176. if (!$this->is_product_within_limits($product, true) && $output_filter == 'laybuy_html_on_product_thumbnails') {
  177. if ($this->is_plus() && $price < $this->pay_over_time_limit_min) {
  178. $html = $this->get_plus_limits_html('product_thumbnails_');
  179. } else {
  180. $html = $this->assets['category_page_pay_over_limit'];
  181. }
  182. $html = str_replace(array(
  183. '[PAY_TODAY]',
  184. '[AMOUNT]',
  185. '[MIN_PRICE]',
  186. '[MAX_PRICE]',
  187. ), array(
  188. $this->display_price_html( $price - $this->pay_over_time_limit_max ),
  189. $this->display_price_html( $this->pay_over_time_limit_max / 5 ),
  190. wc_price( $this->pay_over_time_limit_min, ['decimals' => 0] ),
  191. wc_price( $this->pay_over_time_limit_max, ['decimals' => 0] ),
  192. ), $html);
  193. } else {
  194. $amount = $this->display_price_html( round($price / self::PAYMENTS_COUNT, 2) );
  195. $html = str_replace(array(
  196. '[OF_OR_FROM]',
  197. '[AMOUNT]'
  198. ), array(
  199. $of_or_from,
  200. $amount
  201. ), $this->settings['category_pages_info_text']);
  202. }
  203. # Execute shortcodes on the string after running internal replacements,
  204. # but before applying filters and rendering.
  205. $html = do_shortcode( "<p class=\"laybuy-payment-info\">{$html}</p>" );
  206. # Add the Modal Window to the page
  207. # Website Admin have no access to the Modal Window codes for data integrity reasons
  208. //$html = $this->apply_modal_window($html);
  209. # Allow other plugins to maniplulate or replace the HTML echoed by this funtion.
  210. echo apply_filters( $output_filter, $html, $product, $price );
  211. }
  212. /**
  213. * Print a paragraph of Laybuy info onto each product item in the shop loop if enabled and the product is valid.
  214. *
  215. * Note: Hooked onto the "woocommerce_after_shop_loop_item_title" Action.
  216. */
  217. public function print_info_for_listed_products($product = null) {
  218. if (!$this->isAllowCurrencyForLaybuy()) {
  219. return;
  220. }
  221. if( is_single()) {
  222. if (!isset($this->settings['show_info_on_product_pages'])
  223. || $this->settings['show_info_on_product_pages'] != 'yes'
  224. || empty($this->settings['category_pages_info_text'])
  225. || !empty($product) && (!$product->is_in_stock() && $this->settings['laybuy_price_breakdown_out_of_stock'] == 'no') ) {
  226. # Don't display anything on Single product page unless the Display on Individual Page is enabled
  227. # The Variant selected is in stock
  228. return;
  229. }
  230. }
  231. else { #Category Pages
  232. if (!isset($this->settings['show_info_on_category_pages'])
  233. || $this->settings['show_info_on_category_pages'] != 'yes'
  234. || empty($this->settings['category_pages_info_text'])) {
  235. # Don't display anything on product items within the shop loop unless
  236. # the "Payment info on product listing pages" box is ticked
  237. # and there is a message to display.
  238. return;
  239. }
  240. $this->process_and_print_laybuy_paragraph(
  241. 'laybuy_html_on_product_thumbnails',
  242. $product
  243. );
  244. }
  245. }
  246. /**
  247. * Render Laybuy elements (logo and payment schedule) on Cart page.
  248. *
  249. * This is dependant on all of the following criteria being met:
  250. * - The Laybuy Payment Gateway is enabled.
  251. * - The cart total is valid and within the merchant payment limits.
  252. * - The "Payment Info on Cart Page" box is ticked and there is a message to display.
  253. * - All of the items in the cart are considered eligible to be purchased with Laybuy.
  254. *
  255. * Note: Hooked onto the "woocommerce_cart_totals_after_order_total" Action.
  256. */
  257. public function render_schedule_on_cart_page() {
  258. $total = WC()->cart->total;
  259. if (!array_key_exists('enabled', $this->settings) || $this->settings['enabled'] != 'yes') {
  260. return;
  261. } else {
  262. if ($total <= 0 ) {
  263. return;
  264. }
  265. }
  266. if(!$this->isAllowCurrencyForLaybuy()) {
  267. return;
  268. }
  269. if (
  270. !isset($this->settings['show_info_on_cart_page']) ||
  271. $this->settings['show_info_on_cart_page'] != 'yes' ||
  272. empty($this->settings['cart_page_info_text'])
  273. ) {
  274. return;
  275. }
  276. foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
  277. $product = $cart_item['data'];
  278. if (!$this->is_product_supported($product)) {
  279. return;
  280. }
  281. }
  282. if ($this->is_plus() && $total < $this->pay_over_time_limit_min) {
  283. $html = '<tr><td colspan="2">' . $this->get_plus_limits_html('cart_') . '<td/><tr/>';
  284. $html = str_replace(array(
  285. '[MIN_PRICE]',
  286. '[MAX_PRICE]',
  287. ), array(
  288. wc_price( $this->pay_over_time_limit_min, ['decimals' => 0] ),
  289. wc_price( $this->pay_over_time_limit_max, ['decimals' => 0] ),
  290. ), $html);
  291. } else if ( $this->pay_over_time_limit_max && $total > $this->pay_over_time_limit_max) {
  292. $fallback_asset = $this->assets['cart_pay_over_limit_asset'];
  293. $html = '<tr><td colspan="2">' . $fallback_asset . '<td/><tr/>';
  294. $html = str_replace(array(
  295. '[PAY_TODAY]',
  296. '[AMOUNT]'
  297. ), array(
  298. $this->display_price_html( $total - max($this->pay_over_time_limit_min, $this->pay_over_time_limit_max) ),
  299. $this->display_price_html( max($this->pay_over_time_limit_min, $this->pay_over_time_limit_max) / 5 )
  300. ), $html);
  301. } else {
  302. $schedule = $this->calculation(WC()->cart->total);
  303. $amount = $this->display_price_html($schedule['minimum_today']);
  304. $html = str_replace(array(
  305. '[AMOUNT]',
  306. ), array(
  307. $amount,
  308. ), $this->settings['cart_page_info_text']);
  309. }
  310. # Execute shortcodes on the string before applying filters and rendering it.
  311. $html = do_shortcode( $html );
  312. # Add the Modal Window to the page
  313. # Website Admin have no access to the Modal Window codes for data integrity reasons
  314. // $html = $this->apply_modal_window($html);
  315. # Allow other plugins to maniplulate or replace the HTML echoed by this funtion.
  316. echo apply_filters( 'laybuy_html_on_cart_page', $html );
  317. }
  318. /**
  319. * Convert the global $post object to a WC_Product instance.
  320. */
  321. private function get_product_from_the_post() {
  322. global $post;
  323. if (function_exists('wc_get_product')) {
  324. $product = wc_get_product( $post->ID );
  325. } else {
  326. $product = new WC_Product( $post->ID );
  327. }
  328. return $product;
  329. }
  330. /**
  331. * Is the given product supported by the Laybuy gateway?
  332. */
  333. private function is_product_supported($product, $alone = false) {
  334. if (!isset($this->settings['enabled']) || $this->settings['enabled'] != 'yes') {
  335. return false;
  336. }
  337. $productTypes = (array) $this->settings['product_types'];
  338. if (false !== array_search('variable', $productTypes)) {
  339. $productTypes[] = 'variation';
  340. }
  341. if (!empty($productTypes) && !in_array($product->get_type(), $productTypes)) {
  342. return false;
  343. }
  344. # Allow other plugins to exclude Laybuy from products that would otherwise be supported.
  345. return (bool)apply_filters( 'laybuy_is_product_supported', true, $product, $alone );
  346. }
  347. private function is_product_within_limits($product, $alone = false) {
  348. if ($this->is_plus()) {
  349. return $this->is_product_within_limits_plus($product);
  350. }
  351. $price = $this->getProductPrice($product);
  352. if ( $price < $this->pay_over_time_limit_min || $price > $this->pay_over_time_limit_max ) {
  353. return false;
  354. }
  355. if ( $alone && $price < $this->pay_over_time_limit_min) {
  356. # If the product is viewed as being on its own and priced lower that the merchant's minimum, it will be considered as not supported.
  357. return false;
  358. }
  359. return true;
  360. }
  361. private function is_product_within_limits_plus($product)
  362. {
  363. $price = $this->getProductPrice($product);
  364. if ($this->pay_over_time_limit_min && $this->pay_over_time_limit_max) {
  365. return $price >= $this->pay_over_time_limit_min && $price <= $this->pay_over_time_limit_max;
  366. }
  367. if (!$this->pay_over_time_limit_min && $this->pay_over_time_limit_max) {
  368. return $price <= $this->pay_over_time_limit_max;
  369. }
  370. if ($this->pay_over_time_limit_min && !$this->pay_over_time_limit_max) {
  371. return $price >= $this->pay_over_time_limit_min;
  372. }
  373. return true;
  374. }
  375. /**
  376. * Is the the website currency supported by the Laybuy gateway?
  377. */
  378. private function is_currency_supported() {
  379. $store_currency = strtoupper(get_woocommerce_currency());
  380. return in_array($store_currency, $this->supported_currencies);
  381. }
  382. private function display_price_html($price) {
  383. if (function_exists('wc_price')) {
  384. return wc_price($price);
  385. } elseif (function_exists('woocommerce_price')) {
  386. return woocommerce_price($price);
  387. }
  388. }
  389. /**
  390. * Provide a shortcode for rendering the standard Laybuy logo on individual product pages.
  391. *
  392. * E.g.:
  393. * - [laybuy_product_logo]
  394. *
  395. * @return string
  396. */
  397. public function shortcode_laybuy_logo($atts) {
  398. $atts = shortcode_atts( array(
  399. 'width' => '80px',
  400. 'link' => '',
  401. 'style' => 'float:none; display:inline-block; vertical-align:middle; height:1.2em; top: -2px; position: relative;'
  402. ), $atts );
  403. $logoUrl = 'https://integration-assets.laybuy.com/woocommerce_laybuy_icons/';
  404. if ($this->settings['laybuy_logo_theme'] === self::LOGO_THEME_WHITE) {
  405. $logoUrl .= 'laybuy_logo_small.svg';
  406. } else {
  407. $logoUrl .= 'laybuy_logo_small_white.svg';
  408. }
  409. if (!empty($atts['link'])) {
  410. return <<<LOGO
  411. <a href = "{$atts['link']}" target = "_blank" class="laybuy-logo-link" ><img style = "{$atts['style']}" src="{$logoUrl}" alt = "Laybuy" /></a >
  412. LOGO;
  413. } else {
  414. return <<<LOGO
  415. <img src="{$logoUrl}" alt = "Laybuy" />
  416. LOGO;
  417. }
  418. }
  419. public function shortcode_laybuy_iframe_src() {
  420. $currency = get_woocommerce_currency();
  421. switch ($currency) {
  422. case 'AUD' :
  423. $code = 'au';
  424. break;
  425. case 'GBP' :
  426. $code = 'gb';
  427. break;
  428. case 'USD' :
  429. $code = 'us';
  430. break;
  431. default :
  432. $code = 'nz';
  433. break;
  434. }
  435. return 'https://integration-assets.laybuy.com/laybuy-cms-page/dist/index_' . $code . '.html';
  436. }
  437. public function product_price_breakdown() {
  438. global $product;
  439. if (
  440. !is_product() ||
  441. (!$product->is_in_stock() && $this->settings['laybuy_price_breakdown_out_of_stock'] == 'no') ||
  442. !$this->is_product_supported($product, true)
  443. ) {
  444. return;
  445. }
  446. $settings = get_option( 'woocommerce_laybuy_settings', true );
  447. $font_size = (strlen($settings['laybuy_fontsize_in_breakdowns']) == 4) ? $settings['laybuy_fontsize_in_breakdowns'] : '12px';
  448. $logo_width = '80px';
  449. if ($font_size === '14px') {
  450. $logo_width = '90px';
  451. }
  452. elseif ($font_size === '16px') {
  453. $logo_width = '100px';
  454. }
  455. elseif ($font_size === '18px') {
  456. $logo_width = '110px';
  457. }
  458. elseif ($font_size === '20px') {
  459. $logo_width = '120px';
  460. }
  461. $price = floatval($this->getProductPrice($product));
  462. if (!$this->isAllowCurrencyForLaybuy() || empty($price)) {
  463. return;
  464. }
  465. $payment_breakdown = $this->calculation($price);
  466. $weekly_payment = wc_price($payment_breakdown['weekly_payment']);
  467. if (!$price) {
  468. return '';
  469. }
  470. if ($this->is_plus() && $price < $this->pay_over_time_limit_min) {
  471. $html = str_replace(array(
  472. '[MIN_PRICE]',
  473. '[MAX_PRICE]',
  474. ), array(
  475. wc_price( $this->pay_over_time_limit_min, ['decimals' => 0]),
  476. wc_price( $this->pay_over_time_limit_max, ['decimals' => 0])
  477. ), $this->get_plus_limits_html());
  478. echo '<p class="laybuy-inline-widget">' . do_shortcode($html) . '</p>';
  479. return;
  480. }
  481. if (!$this->is_product_within_limits($product)) {
  482. $html = $this->assets['product_page_pay_over_limit'];
  483. $html = str_replace(array(
  484. '[PAY_TODAY]',
  485. '[AMOUNT]'
  486. ), array(
  487. $this->display_price_html( $price - $this->pay_over_time_limit_max ),
  488. $this->display_price_html( $this->pay_over_time_limit_max / 5 )
  489. ), $html);
  490. $html_breakdown = '<p class="laybuy-inline-widget">' . do_shortcode($html) . '</p>';
  491. echo $html_breakdown;
  492. return;
  493. }
  494. $html_breakdown = '<p class="laybuy-inline-widget">';
  495. $html_breakdown .= str_replace(array(
  496. '[AMOUNT]',
  497. '[LOGO_WIDTH]'
  498. ), array(
  499. $weekly_payment,
  500. $logo_width
  501. ), do_shortcode( $this->settings['product_pages_info_text'] ));
  502. $html_breakdown .= '</p>';
  503. echo $html_breakdown;
  504. }
  505. public function isAllowCurrencyForLaybuy() {
  506. return in_array(get_woocommerce_currency(), $this->settings['currency']);
  507. }
  508. public function checkout_modify_description( $description, $total ) {
  509. $settings = get_option( 'woocommerce_laybuy_settings', true );
  510. if ($total < $this->pay_over_time_limit_min) {
  511. return '';
  512. }
  513. $payment_breakdown = $this->calculation( $total );
  514. $currencyCode = $settings['laybuy_currency_prefix_in_breakdowns'] === 'yes';
  515. $price_prefix = '';
  516. if ($currencyCode) {
  517. $price_prefix = get_woocommerce_currency() . ' ';
  518. }
  519. if(!$this->isAllowCurrencyForLaybuy()) {
  520. return;
  521. }
  522. $weekly_payment = $price_prefix . wc_price( $payment_breakdown['weekly_payment'] );
  523. if ($this->pay_over_time_limit_max && $total > $this->pay_over_time_limit_max) {
  524. if (isset($settings['laybuy_wide_layout_setting']) && $settings['laybuy_wide_layout_setting'] == "yes") {
  525. $html = '<div class="laybuy-checkout-content "><p class="title">Pay <strong>[PAY_TODAY]</strong> today & 5 weekly interest-free payments of <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img style="width:25% !important" class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg" /><img style="width:25% !important" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg" /><img style="width:25% !important" class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg" /><img style="width:25% !important" class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg" /></div>
  526. </div><div style="clear: both;"></div>';
  527. } else {
  528. $html = $this->assets['checkout_page_pay_over_limit'];
  529. }
  530. $html = str_replace(array(
  531. '[PAY_TODAY]',
  532. '[AMOUNT]'
  533. ), array(
  534. $this->display_price_html( $total - $this->pay_over_time_limit_max ),
  535. $this->display_price_html( $this->pay_over_time_limit_max / 5 )
  536. ), $html);
  537. } else {
  538. if (isset($settings['laybuy_wide_layout_setting']) && $settings['laybuy_wide_layout_setting'] == "yes") {
  539. $html = '<div class="laybuy-checkout-content "><p class="title">Pay it in 6 weekly, interest-free payments from <strong>[AMOUNT]</strong></p><div class="laybuy-checkout-img"><img style="width:25% !important" class="left-column" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_pay.jpg" /><img style="width:25% !important" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_schedule.jpg" /><img style="width:25% !important" class="left-column second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_complete.jpg" /><img style="width:25% !important" class="second-row" src="https://integration-assets.laybuy.com/woocommerce_laybuy_icons/laybuy_done.jpg" /></div>
  540. </div><div style="clear: both;"></div>';
  541. } else {
  542. $html = do_shortcode($this->settings['checkout_page_info_text']);
  543. }
  544. }
  545. $html = nl2br($html);
  546. if (!$html) {
  547. return '';
  548. }
  549. $html_breakdown = '<p class="laybuy-checkout-widget">';
  550. $html_breakdown .= str_replace(array(
  551. '[AMOUNT]'
  552. ), array(
  553. $weekly_payment
  554. ), $html);
  555. $html_breakdown .= '</p>';
  556. return $html_breakdown;
  557. }
  558. public function calculation($dividend, $divisor = self::PAYMENTS_COUNT) {
  559. // multiplying it to 100 makes is having a better precision.
  560. // stripe use this
  561. $dividend = $dividend * 100;
  562. if( 0 < ($dividend % $divisor) ) {
  563. // get weeklys
  564. $weekly_payment = intval( $dividend / $divisor );
  565. // get minimum payment
  566. $minimum_today = intval( $dividend - ($weekly_payment * 5) );
  567. $calculation = array(
  568. 'minimum_today' => $minimum_today * 0.01,
  569. 'weekly_payment' => $weekly_payment * 0.01
  570. );
  571. } else {
  572. $even_weekly_payment = $dividend / $divisor;
  573. $calculation = array(
  574. 'minimum_today' => $even_weekly_payment * 0.01,
  575. 'weekly_payment' => $even_weekly_payment * 0.01
  576. );
  577. }
  578. return $calculation;
  579. }
  580. /**
  581. * Processses the orders that are redirected.
  582. *
  583. * @since 4.0.0
  584. * @version 4.0.0
  585. */
  586. public function process_redirect_order() {
  587. if (
  588. !isset($_GET['gateway_id']) ||
  589. constant('WC_GATEWAY_LAYBUY_ID') !== $_GET['gateway_id'] ||
  590. !isset($_GET['post_type']) ||
  591. !isset($_GET['quote_id'])
  592. ) {
  593. return;
  594. }
  595. $quote_id = wc_clean( $_GET['quote_id'] );
  596. $status = strtoupper($_GET['status']);
  597. $token = $_GET['token'];
  598. $processManager = Laybuy_ProcessManager::getInstance();
  599. $processManager->setApiGateway($this->apiGateway)
  600. ->setWcGateway($this)
  601. ->setCompatibilityMode($this->compatibility_mode);
  602. $processManager->processRedirectPayment($quote_id, $status, $token);
  603. }
  604. /**
  605. * Create order - Part 1 of 2.
  606. *
  607. * Override WooCommerce's create_order function and make our own order-quote object. We will manually
  608. * convert this into a proper WC_Order object later, if the checkout completes successfully. Part of the data
  609. * collected here is submitted to the Laybuy API to generate a token, the rest is persisted to the
  610. * database to build the WC_Order object. Based on WooCommerce 2.6.8.
  611. *
  612. * Note: This needs to follow the WC_Checkout::create_order() method very closely. In order to properly
  613. * create the WC_Order object later, we need to make sure we're storing all of the data that will be
  614. * needed later. If it fails, it needs to return an integer that evaluates to true in order to bypass the
  615. * standard WC_Order creation process.
  616. *
  617. * Note: Hooked onto the "woocommerce_create_order" Filter.
  618. *
  619. */
  620. public function create_order_quote($null, $checkout) {
  621. if ($this->compatibility_mode) {
  622. return;
  623. }
  624. $processManager = Laybuy_ProcessManager::getInstance();
  625. $processManager->setApiGateway($this->apiGateway)
  626. ->setWcGateway($this)
  627. ->createQuote($checkout);
  628. }
  629. public function process_payment($order_id)
  630. {
  631. return Laybuy_ProcessManager::getInstance()
  632. ->setApiGateway($this->apiGateway)
  633. ->setWcGateway($this)
  634. ->createOrder($order_id);
  635. }
  636. /**
  637. * If calling wc_create_order() for an Laybuy Quote, tell wp_insert_post() to reuse the ID of the quote.
  638. */
  639. public function filter_woocommerce_new_order_data( $order_data ) {
  640. if (array_key_exists('laybuy_quote_id', $GLOBALS) && is_numeric($GLOBALS['laybuy_quote_id']) && $GLOBALS['laybuy_quote_id'] > 0) {
  641. $order_data['import_id'] = (int) $GLOBALS['laybuy_quote_id'];
  642. unset($GLOBALS['laybuy_quote_id']);
  643. }
  644. return $order_data;
  645. }
  646. /**
  647. * Checks to see if all criteria is met before showing payment method.
  648. *
  649. * @return bool
  650. */
  651. public function is_available() {
  652. return true; // debug
  653. $is_available = parent::is_available();
  654. if (!$is_available) {
  655. return false;
  656. }
  657. $currency = get_woocommerce_currency();
  658. if ($this->settings['laybuy_geolocate_ip'] === 'no') {
  659. return $this->is_currency_enabled($currency);
  660. }
  661. $country = $this->get_country();
  662. if ($country) {
  663. $map = ['GBP' => 'GB', 'AUD' => 'AU', 'NZD' => 'NZ', 'USD' => 'US'];
  664. $countryList = [];
  665. foreach (array_values($this->settings['currency']) as $cur) {
  666. if (isset($map[$cur])) {
  667. $countryList[] = $map[$cur];
  668. }
  669. }
  670. if (count($countryList) > 0 && !in_array($country, $countryList)) {
  671. return false;
  672. }
  673. return true;
  674. }
  675. return $this->is_currency_enabled($currency);
  676. }
  677. public function filter_woocommerce_get_price_html($price, $product) {
  678. if (is_object($product) && $product instanceof WC_Product) {
  679. ob_start();
  680. $this->print_info_for_listed_products($product);
  681. $html = ob_get_clean();
  682. return $price . $html;
  683. }
  684. return $price;
  685. }
  686. public function getProductPrice($product)
  687. {
  688. if ($product->is_type('composite')) {
  689. return $product->get_composite_price_including_tax( 'min', true );
  690. }
  691. if ($product->is_type('bundle')) {
  692. return $product->get_bundle_price_including_tax( 'min', true );
  693. }
  694. if ($product->is_type('grouped')) {
  695. $children = $product->get_children();
  696. $price = 0;
  697. foreach ($children as $key => $value) {
  698. $childProduct = wc_get_product( $value );
  699. if ($price !== 0) {
  700. $price = min($price, $this->getProductPrice($childProduct));
  701. } else {
  702. $price = $this->getProductPrice($childProduct);
  703. }
  704. }
  705. return $price;
  706. }
  707. if ($product->is_type( 'variable' )) {
  708. return $product->get_variation_price( 'min', false );
  709. }
  710. if (function_exists('wc_get_price_including_tax')) {
  711. return wc_get_price_including_tax( $product );
  712. } elseif (method_exists($product, 'get_price_including_tax')) {
  713. return $product->get_price_including_tax();
  714. } else {
  715. return $product->get_price();
  716. }
  717. }
  718. public function get_plus_limits_html($namespace = '')
  719. {
  720. if ($this->pay_over_time_limit_min && !$this->pay_over_time_limit_max) {
  721. return $this->assets["${namespace}available_on_orders_over"];
  722. }
  723. if (!$this->pay_over_time_limit_min && $this->pay_over_time_limit_max) {
  724. return $this->assets["${namespace}available_on_orders_under"];
  725. }
  726. return $this->assets["${namespace}available_on_orders_between"];
  727. }
  728. public function is_plus()
  729. {
  730. return self::MODE_PLUS === $this->mode;
  731. }
  732. public function check_pay_over_time_limits()
  733. {
  734. $currency = get_woocommerce_currency();
  735. if (
  736. in_array($currency, $this->supported_currencies) &&
  737. !$this->pay_over_time_limit_min &&
  738. !$this->pay_over_time_limit_max
  739. ) {
  740. $settings = (array) get_option( 'woocommerce_laybuy_settings', true );
  741. $settings["{$currency}_pay_limit_min"] = 0.06;
  742. $settings["{$currency}_pay_limit_max"] = $this->pay_limits_max[$currency];
  743. update_option('woocommerce_laybuy_settings', $settings);
  744. }
  745. }
  746. public function check_cart_within_limits($gateways)
  747. {
  748. if (is_admin()) {
  749. return $gateways;
  750. }
  751. if (WC()->cart->total < $this->pay_over_time_limit_min) {
  752. if (isset($_GET['debug'])) {
  753. var_dump('UNSET');exit;
  754. }
  755. unset($gateways[$this->id]);
  756. }
  757. return $gateways;
  758. }
  759. public function checkout_add_laybuy_paragraph()
  760. {
  761. if (!$this->is_plus()) {
  762. return;
  763. }
  764. if (WC()->cart->total < $this->pay_over_time_limit_min) {
  765. $html = str_replace(array(
  766. '[MIN_PRICE]',
  767. '[MAX_PRICE]',
  768. ), array(
  769. wc_price( $this->pay_over_time_limit_min, ['decimals' => 0]),
  770. wc_price( $this->pay_over_time_limit_max, ['decimals' => 0]),
  771. ), $this->get_plus_limits_html());
  772. echo '<div class="laybuy-checkout-widget">' . do_shortcode($html) . '</div>';
  773. }
  774. }
  775. public function getProductPriceBreakdownHookPriority()
  776. {
  777. return $this->settings['product_price_breakdown_hook_priority'];
  778. }
  779. public function change_woocommerce_currency( $currency )
  780. {
  781. if (!isset($this->settings['laybuy_geolocate_ip']) || $this->settings['laybuy_geolocate_ip'] === 'no') {
  782. return $currency;
  783. }
  784. $country = $this->get_country();
  785. if (empty($country)) {
  786. return $currency;
  787. }
  788. $map = ['GB' => 'GBP', 'AU' => 'AUD', 'NZ' => 'NZD', 'US' => 'USD'];
  789. if (!isset($map[$country]) || empty($this->settings['currency'])) {
  790. return $currency;
  791. }
  792. $countryList = array_values($this->settings['currency']);
  793. if (in_array($map[$country], $countryList)) {
  794. $currency = $map[$country];
  795. }
  796. return $currency;
  797. }
  798. public function is_enabled() {
  799. return $this->enabled == 'yes';
  800. }
  801. private function get_country()
  802. {
  803. $country = null;
  804. if (isset($_COOKIE[self::COOKIE_GEOLOCATION_COUNTRY])) {
  805. $country = $_COOKIE[self::COOKIE_GEOLOCATION_COUNTRY];
  806. } else {
  807. $geolocation = apply_filters('laybuy_geolocation', []);
  808. if (empty($geolocation)) {
  809. $geolocation = WC_Geolocation::geolocate_ip();
  810. }
  811. if (!empty($geolocation['country'])) {
  812. $country = $geolocation['country'];
  813. setcookie( self::COOKIE_GEOLOCATION_COUNTRY, $country, time()+3600*24*30);
  814. }
  815. }
  816. return $country;
  817. }
  818. public function generate_support_request_btn_html( $key, $data )
  819. {
  820. $field_key = $this->get_field_key( $key );
  821. ob_start();
  822. ?>
  823. <tr valign="top">
  824. <th scope="row" class="titledesc">
  825. <label for="<?php echo esc_attr( $field_key ); ?>"><?php echo wp_kses_post( $data['title'] ); ?> <?php echo $this->get_tooltip_html( $data ); // WPCS: XSS ok. ?></label>
  826. </th>
  827. <td class="forminp">
  828. <fieldset>
  829. <legend class="screen-reader-text"><span><?php echo wp_kses_post( $data['title'] ); ?></span></legend>
  830. <button id="laybuy_send_support_request" class="button-cancel woocommerce-save-button" type="button" value="<?php esc_attr_e( 'Send Support Report', 'woocommerce' ); ?>"><?php esc_html_e( 'Send Support Request', 'woocommerce' ); ?></button>
  831. <?php echo $this->get_description_html( $data ); // WPCS: XSS ok. ?>
  832. </fieldset>
  833. </td>
  834. </tr>
  835. <?php
  836. return ob_get_clean();
  837. }
  838. }