| 1 |
<?php |
| 2 |
|
| 3 |
namespace MakeCommerce\Payment\Gateway\WooCommerce; |
| 4 |
|
| 5 |
trait Paylater { |
| 6 |
|
| 7 |
/** |
| 8 |
* Allowed pay later product view methods |
| 9 |
*/ |
| 10 |
public $allowed_pl_product_view_methods = array( |
| 11 |
'slice', |
| 12 |
'indivy-go', |
| 13 |
'liisi_ee', |
| 14 |
'bigbank' |
| 15 |
); |
| 16 |
|
| 17 |
/** |
| 18 |
* Allowed shop locations |
| 19 |
*/ |
| 20 |
public $allowed_pl_countries = array( |
| 21 |
'EE', |
| 22 |
'LV' |
| 23 |
); |
| 24 |
|
| 25 |
//needed so that the block is not show twice. |
| 26 |
public static $pay_later_shown = false; |
| 27 |
|
| 28 |
/** |
| 29 |
* Returns description for pay later method |
| 30 |
* |
| 31 |
* @throws exception when method is not implemented |
| 32 |
* @since 3.0.9 |
| 33 |
*/ |
| 34 |
public function get_paylater_method_description( $method, $price ) { |
| 35 |
|
| 36 |
switch ( $method ) { |
| 37 |
case 'slice': |
| 38 |
|
| 39 |
$amount = round( $price / 3, 2 ); |
| 40 |
$first_payment = date( 'Y-m-d', strtotime( '+30 days' ) ); |
| 41 |
|
| 42 |
return sprintf( __( 'Pay in <b>three equal parts, %s EUR per month</b>. First payment on <b>%s %s</b>', 'wc_makecommerce_domain' ), $amount, $this->month_to_text( strtotime( $first_payment ) ), $this->day_to_ordinal( strtotime( $first_payment ) ) ); |
| 43 |
case 'indivy-go': |
| 44 |
|
| 45 |
return __( 'Buy now, <b>pay on the 15th of the next month</b>', 'wc_makecommerce_domain' ); |
| 46 |
case 'liisi_ee': |
| 47 |
|
| 48 |
$interest = 11.9; |
| 49 |
$downpayment = 0; |
| 50 |
$months = 12; |
| 51 |
$amount = ( ( $price - $downpayment ) * ( 1 + ( $interest / 100 ) * $months / 12 ) ) / ( $months ); |
| 52 |
|
| 53 |
return sprintf( __( 'Pay <b>%s EUR per month for 12 months</b>. Interest %s', 'wc_makecommerce_domain' ), ceil( $amount ), $interest ) . '%'; |
| 54 |
case 'bigbank': |
| 55 |
|
| 56 |
$months = 12; |
| 57 |
$downpayment = 0; |
| 58 |
$interest = 12.9; |
| 59 |
$contractfee = 3; |
| 60 |
$managementfee = 0; |
| 61 |
|
| 62 |
$contractconclusion = $price / 100 * $contractfee; |
| 63 |
$creditamount = ( $price - $downpayment ) + $contractconclusion; |
| 64 |
|
| 65 |
$monthlypayment = $this->pmt( $interest, $months, $creditamount ); |
| 66 |
|
| 67 |
$totalamount = $months * $monthlypayment; |
| 68 |
$interestpayment = $totalamount - $creditamount - ( $months * $managementfee ); |
| 69 |
$annualinterest = $this->effect( $this->rate( $months, $monthlypayment, -( $creditamount - $contractconclusion ) ) * 12, 12 ) * 100; |
| 70 |
|
| 71 |
return sprintf( |
| 72 |
__( 'Pay <b>%s EUR per month</b> for 12 months<br> |
| 73 |
<br> |
| 74 |
<i>Borrowing rate %s%, credit amount %s€, contract conclusion fee %s€, total amount payable %s€, annual interest rate %s% |
| 75 |
Calculation is only informative.</i>' , 'wc_makecommerce_domain' |
| 76 |
), |
| 77 |
number_format( $monthlypayment, 2 ), |
| 78 |
number_format( $interest, 2 ), |
| 79 |
number_format( $creditamount, 2 ), |
| 80 |
number_format( $contractconclusion, 2 ), |
| 81 |
number_format( $totalamount, 2 ), |
| 82 |
number_format( $annualinterest, 2 ) |
| 83 |
); |
| 84 |
default: |
| 85 |
|
| 86 |
return 'Pay with ' . $method; |
| 87 |
} |
| 88 |
} |
| 89 |
|
| 90 |
/** |
| 91 |
* Calculate rate |
| 92 |
* |
| 93 |
* @since 3.0.10 |
| 94 |
*/ |
| 95 |
public function rate( $numberOfPeriods, $payment, $presentValue ) { |
| 96 |
|
| 97 |
$rate = 0.1; |
| 98 |
$close = false; |
| 99 |
$iter = 0; |
| 100 |
|
| 101 |
while ( !$close && $iter < 128 ) { |
| 102 |
|
| 103 |
$nextdiff = $this->rateNextGuess( $rate, $numberOfPeriods, $payment, $presentValue ); |
| 104 |
if ( !is_numeric( $nextdiff ) ) { |
| 105 |
break; |
| 106 |
} |
| 107 |
|
| 108 |
$rate1 = $rate - $nextdiff; |
| 109 |
$close = abs( $rate1 - $rate ) < 1.0e-08; |
| 110 |
$iter++; |
| 111 |
$rate = $rate1; |
| 112 |
} |
| 113 |
|
| 114 |
return $rate; |
| 115 |
} |
| 116 |
|
| 117 |
/** |
| 118 |
* Returns next rate guess |
| 119 |
* |
| 120 |
* @since 3.0.10 |
| 121 |
*/ |
| 122 |
private function rateNextGuess( $rate, $numberOfPeriods, $payment, $presentValue, $futureValue = 0, $type = 0 ) |
| 123 |
{ |
| 124 |
$tt1 = ( $rate + 1 ) ** $numberOfPeriods; |
| 125 |
$tt2 = ( $rate + 1 ) ** ( $numberOfPeriods - 1 ); |
| 126 |
$numerator = $futureValue + $tt1 * $presentValue + $payment * ( $tt1 - 1 ) * ( $rate * $type + 1 ) / $rate; |
| 127 |
$denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ( $tt1 - 1 ) |
| 128 |
* ( $rate * $type + 1 ) / ( $rate * $rate ) + $numberOfPeriods |
| 129 |
* $payment * $tt2 * ( $rate * $type + 1 ) / $rate + $payment * ( $tt1 - 1 ) * $type / $rate; |
| 130 |
|
| 131 |
return $numerator / $denominator; |
| 132 |
} |
| 133 |
|
| 134 |
/** |
| 135 |
* Calculate monthly payment |
| 136 |
* |
| 137 |
* @since 3.0.10 |
| 138 |
*/ |
| 139 |
public function pmt( $interest, $months, $loan ) |
| 140 |
{ |
| 141 |
$interest = $interest / 1200; |
| 142 |
$amount = $interest * -$loan * pow( ( 1 + $interest ), $months ) / ( 1 - pow( ( 1 + $interest ), $months ) ); |
| 143 |
|
| 144 |
return $amount; |
| 145 |
} |
| 146 |
|
| 147 |
/** |
| 148 |
* Calculate effect |
| 149 |
* |
| 150 |
* @since 3.0.10 |
| 151 |
*/ |
| 152 |
public function effect($nominalRate, $periodsPerYear) |
| 153 |
{ |
| 154 |
return ( ( 1 + $nominalRate / $periodsPerYear ) ** $periodsPerYear ) - 1; |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* Adds pay later block to product view |
| 159 |
* |
| 160 |
* @since 3.0.9 |
| 161 |
*/ |
| 162 |
public function add_pay_later_block_to_product() { |
| 163 |
|
| 164 |
if ( !in_array( wc_get_base_location()['country'], $this->allowed_pl_countries ) ) { |
| 165 |
return; |
| 166 |
} |
| 167 |
|
| 168 |
//don't show more than once |
| 169 |
if ( self::$pay_later_shown ) { |
| 170 |
return; |
| 171 |
} |
| 172 |
|
| 173 |
global $product; |
| 174 |
|
| 175 |
//ignore for subscription and grouped products |
| 176 |
if ( class_exists( '\WC_Subscriptions_Product' ) && \WC_Subscriptions_Product::is_subscription( $product ) ) { |
| 177 |
return; |
| 178 |
} |
| 179 |
|
| 180 |
//get product using variation id |
| 181 |
if ( isset( $_GET['paylater_variation_id'] ) ) { |
| 182 |
$product = wc_get_product( $_GET['paylater_variation_id'] ); |
| 183 |
} |
| 184 |
|
| 185 |
//dont use for grouped products. Too confusing to implement. Something for v2 perhaps |
| 186 |
if ( $product->get_type() == 'grouped' ) { |
| 187 |
return; |
| 188 |
} |
| 189 |
|
| 190 |
//get product price |
| 191 |
$price = wc_get_price_including_tax( $product ); |
| 192 |
|
| 193 |
//get allowed methods |
| 194 |
$methods = $this->get_allowed_pl_methods(); |
| 195 |
|
| 196 |
//add paylater css and js |
| 197 |
wp_enqueue_style( 'mc_paylater', plugins_url( '/css/paylater.css', __FILE__ ), array(), $this->version ); |
| 198 |
\MakeCommerce::mc_enqueue_script( |
| 199 |
'MC_PAYLATER', |
| 200 |
dirname( __FILE__ ) . '/js/mc_paylater.js', |
| 201 |
[ 'type' => $product->get_type() ], |
| 202 |
[ 'jquery' ] |
| 203 |
); |
| 204 |
|
| 205 |
$logo = plugins_url( '/images/paylater-en.png', __FILE__ ); |
| 206 |
if ( file_exists( dirname( __FILE__ ) . '/images/paylater-' . \MakeCommerce\i18n::get_two_char_locale() .'.png' ) ) { |
| 207 |
$logo = plugins_url( '/images/paylater-' . \MakeCommerce\i18n::get_two_char_locale() . '.png', __FILE__ ); |
| 208 |
} |
| 209 |
|
| 210 |
$displaycss = ''; |
| 211 |
if ( $product->get_type() == 'variable' || isset( $_GET['paylater_variation_id'] ) ) { |
| 212 |
$displaycss = 'style="display: none;"'; |
| 213 |
} |
| 214 |
|
| 215 |
echo '<div id="mc_paylater_parent">'; |
| 216 |
$pl_html = ' |
| 217 |
<div class="mc_paylater_block" '.$displaycss.' id="mc_paylater_block"> |
| 218 |
<img src="' . $logo . '" /> |
| 219 |
<div class="mc_paylater_block_inner"> |
| 220 |
'; |
| 221 |
|
| 222 |
$first = true; |
| 223 |
foreach ( $methods as $method ) { |
| 224 |
|
| 225 |
if ( in_array( $method->name, $this->settings["pl_show_methods"] ) |
| 226 |
&& $price >= $method->min_amount |
| 227 |
&& $price <= $method->max_amount |
| 228 |
) { |
| 229 |
if ( !$first ) { |
| 230 |
$pl_html .= '<hr />'; |
| 231 |
} |
| 232 |
|
| 233 |
$pl_html .= ' |
| 234 |
<a style="all: unset;" href="' . esc_url( $product->add_to_cart_url() ) .'&mc_paylater_method=' . $method->name . '&mc_paylater_method_country=' . $method->country . '"> |
| 235 |
<div class="mc_paylater_line" id="' . $method->name . '"> |
| 236 |
<div><img class="mc_paylater_image" src="' . $method->logo_url . '" /></div> |
| 237 |
<div><p class="mc_paylater_text">' . $this->get_paylater_method_description( $method->name, $price ) . '</p></div> |
| 238 |
</div> |
| 239 |
</a> |
| 240 |
'; |
| 241 |
|
| 242 |
$first = false; |
| 243 |
} |
| 244 |
} |
| 245 |
|
| 246 |
$pl_html .= ' |
| 247 |
</div> |
| 248 |
</div> |
| 249 |
'; |
| 250 |
|
| 251 |
//if first is false then at least on of the methods was accepted. Otherwise dont show pay later block at all |
| 252 |
if ( !$first ) { |
| 253 |
echo $pl_html; |
| 254 |
} |
| 255 |
|
| 256 |
echo '</div>'; |
| 257 |
|
| 258 |
self::$pay_later_shown = true; |
| 259 |
} |
| 260 |
|
| 261 |
/** |
| 262 |
* Returns all allowed paylater methods |
| 263 |
* |
| 264 |
* @since 3.0.9 |
| 265 |
*/ |
| 266 |
public function get_allowed_pl_methods() { |
| 267 |
|
| 268 |
global $wpdb; |
| 269 |
|
| 270 |
return $wpdb->get_results( ' |
| 271 |
SELECT |
| 272 |
* |
| 273 |
FROM |
| 274 |
' . $wpdb->prefix . MAKECOMMERCE_TABLENAME . ' |
| 275 |
WHERE |
| 276 |
`type` = "payLater" AND |
| 277 |
`name` IN (\''.implode( "','", $this->allowed_pl_product_view_methods ).'\') |
| 278 |
' ); |
| 279 |
} |
| 280 |
|
| 281 |
/** |
| 282 |
* Sets pay later settings for admin |
| 283 |
* |
| 284 |
* @since 3.0.9 |
| 285 |
*/ |
| 286 |
public function set_pay_later_settings() { |
| 287 |
|
| 288 |
$methods = $this->get_allowed_pl_methods(); |
| 289 |
|
| 290 |
if ( !empty( $methods ) && in_array( wc_get_base_location()['country'], $this->allowed_pl_countries ) ) { |
| 291 |
|
| 292 |
$this->form_fields['pl_title'] = array( |
| 293 |
'title' => '<hr><br>'.__( 'Pay later Settings', 'wc_makecommerce_domain' ), |
| 294 |
'type' => 'title', |
| 295 |
); |
| 296 |
|
| 297 |
$this->form_fields['pl_show_on_product_pages'] = array( |
| 298 |
'title' => __( 'Show pay later options at product view', 'wc_makecommerce_domain' ), |
| 299 |
'type' => 'checkbox', |
| 300 |
'default' => 'no' |
| 301 |
); |
| 302 |
|
| 303 |
$options = array(); |
| 304 |
foreach ( $methods as $method ) { |
| 305 |
if ( isset($method->display_name) ) { |
| 306 |
$options[$method->name] = $method->display_name; |
| 307 |
} else { |
| 308 |
$options[$method->name] = $method->name; |
| 309 |
} |
| 310 |
} |
| 311 |
|
| 312 |
$this->form_fields['pl_show_methods'] = array( |
| 313 |
'title' => __( 'Select desired pay later methods to show in product view', 'wc_makecommerce_domain' ), |
| 314 |
'type' => 'multiselect', |
| 315 |
'options' => $options, |
| 316 |
'desc_tip' => __( 'Select multiple methods by holding down Ctrl or Command button', 'wc_makecommerce_domain' ), |
| 317 |
); |
| 318 |
} else { |
| 319 |
|
| 320 |
$description = __( 'Your shop doesn\'t currently offer pay later methods with Maksekeskus. Adding pay later options to your should increases number of customers and purchase amounts. Please <a target="_blank" href="https://makecommerce.net/contact/">get in touch</a> with us to enable pay later for your shop.', 'wc_makecommerce_domain' ); |
| 321 |
|
| 322 |
if ( !in_array( wc_get_base_location()['country'], $this->allowed_pl_countries ) ) { |
| 323 |
$description = __( 'This pay later feature is not available in your country yet. <a target="_blank" href="https://makecommerce.net/contact/">Contact us</a> for more information!', 'wc_makecommerce_domain' ); |
| 324 |
} |
| 325 |
|
| 326 |
$this->form_fields['pl_title'] = array( |
| 327 |
'title' => '<hr><br>' . __( 'Pay later Settings', 'wc_makecommerce_domain' ), |
| 328 |
'description' => $description, |
| 329 |
'type' => 'title', |
| 330 |
); |
| 331 |
} |
| 332 |
} |
| 333 |
|
| 334 |
/** |
| 335 |
* Redirect to checkout after cart add via paylater and set selected method to session |
| 336 |
* |
| 337 |
* @since 3.0.9 |
| 338 |
*/ |
| 339 |
public function added_to_cart_via_paylater_redirect() { |
| 340 |
|
| 341 |
if ( isset( $_GET['mc_paylater_method'] ) ) { |
| 342 |
if ( !empty( $_GET['mc_paylater_method'] ) ) { |
| 343 |
|
| 344 |
WC()->session->set( 'mc_selected_paylater_method', $_GET['mc_paylater_method']); |
| 345 |
WC()->session->set( 'mc_selected_paylater_method_country', $_GET['mc_paylater_method_country']); |
| 346 |
|
| 347 |
return wc_get_checkout_url(); |
| 348 |
} |
| 349 |
} |
| 350 |
} |
| 351 |
|
| 352 |
static $autoselect_enqueued = false; |
| 353 |
|
| 354 |
/** |
| 355 |
* Automatically select chosen paylater method in checkout |
| 356 |
* |
| 357 |
* @since 3.0.9 |
| 358 |
*/ |
| 359 |
public function autoselect_chosen_paylater_method( $wccm_after_checkout ) { |
| 360 |
|
| 361 |
$selectedMethod = WC()->session->get( 'mc_selected_paylater_method' ); |
| 362 |
|
| 363 |
if ( $selectedMethod !== NULL && !self::$autoselect_enqueued ) { |
| 364 |
|
| 365 |
\MakeCommerce::mc_enqueue_script( |
| 366 |
'MC_PAYLATER_AUTOSELECT', |
| 367 |
dirname( __FILE__ ) . '/js/mc_paylater_autoselect.js', |
| 368 |
[ 'method' => $selectedMethod, 'country' => WC()->session->get( 'mc_selected_paylater_method_country' ), 'id' => $this->id ], |
| 369 |
[ 'jquery' ] |
| 370 |
); |
| 371 |
|
| 372 |
self::$autoselect_enqueued = true; |
| 373 |
|
| 374 |
//unset session after selecting once. |
| 375 |
WC()->session->__unset( 'mc_selected_paylater_method' ); |
| 376 |
WC()->session->__unset( 'mc_selected_paylater_method_country' ); |
| 377 |
} |
| 378 |
} |
| 379 |
|
| 380 |
static $pl_notice_shown = false; |
| 381 |
/** |
| 382 |
* Adds notification to admin when paylater is loaded for the first time |
| 383 |
* |
| 384 |
* @since 3.0.9 |
| 385 |
*/ |
| 386 |
public function paylater_first_time_notice() { |
| 387 |
|
| 388 |
if ( !in_array( wc_get_base_location()['country'], $this->allowed_pl_countries ) ) { |
| 389 |
return; |
| 390 |
} |
| 391 |
|
| 392 |
$user_id = get_current_user_id(); |
| 393 |
if ( isset( $_GET['mc_pay_later_notice_dismissed'] ) ) { |
| 394 |
add_user_meta( $user_id, 'mc_pay_later_notice_dismissed', 'true', true ); |
| 395 |
} |
| 396 |
|
| 397 |
if ( self::$pl_notice_shown || get_user_meta( $user_id, 'mc_pay_later_notice_dismissed' ) ) { |
| 398 |
return; |
| 399 |
} |
| 400 |
|
| 401 |
echo ' |
| 402 |
<div class="notice notice-info"> |
| 403 |
<p> |
| 404 |
<a style="float: right; " href="?mc_pay_later_notice_dismissed">' . __( 'Dismiss', 'wc_makecommerce_domain' ) . '</a> |
| 405 |
' . sprintf( __( 'MakeCommerce module now includes <a href="%s">option to display pay later options and payment terms examples in product view</a>. Having pay later options available potentially grows your customer base as well as average shopping cart. We recommend to use this option if considerable number of purchases are above 75 euros.', 'wc_makecommerce_domain' ), '/wp-admin/admin.php?page=wc-settings&tab=checkout§ion=makecommerce&mc_pay_later_notice_dismissed' ). ' |
| 406 |
</p> |
| 407 |
</div>'; |
| 408 |
|
| 409 |
self::$pl_notice_shown = true; |
| 410 |
} |
| 411 |
|
| 412 |
/** |
| 413 |
* Converts date to textual representation |
| 414 |
* Expects unixtime, returns textual month e.g. November, July |
| 415 |
* |
| 416 |
* @since 3.0.9 |
| 417 |
*/ |
| 418 |
public function month_to_text( $timestamp ) { |
| 419 |
|
| 420 |
$months = array( |
| 421 |
1 => __( 'January', 'wc_makecommerce_domain' ), |
| 422 |
2 => __( 'February', 'wc_makecommerce_domain' ), |
| 423 |
3 => __( 'March', 'wc_makecommerce_domain' ), |
| 424 |
4 => __( 'April', 'wc_makecommerce_domain' ), |
| 425 |
5 => __( 'May', 'wc_makecommerce_domain' ), |
| 426 |
6 => __( 'June', 'wc_makecommerce_domain' ), |
| 427 |
7 => __( 'July', 'wc_makecommerce_domain' ), |
| 428 |
8 => __( 'August', 'wc_makecommerce_domain' ), |
| 429 |
9 => __( 'September', 'wc_makecommerce_domain' ), |
| 430 |
10 => __( 'October', 'wc_makecommerce_domain' ), |
| 431 |
11 => __( 'November', 'wc_makecommerce_domain' ), |
| 432 |
12 => __( 'December', 'wc_makecommerce_domain' ) |
| 433 |
); |
| 434 |
|
| 435 |
return $months[ date( 'n', $timestamp ) ]; |
| 436 |
} |
| 437 |
|
| 438 |
/** |
| 439 |
* Converts date to textual representation |
| 440 |
* Expects unixtime, returns ordinal number/text mix e.g. 1st, 2nd |
| 441 |
* |
| 442 |
* @since 3.0.9 |
| 443 |
*/ |
| 444 |
public function day_to_ordinal( $timestamp ) { |
| 445 |
|
| 446 |
$days = array( |
| 447 |
1 => __( '1st', 'wc_makecommerce_domain' ), |
| 448 |
2 => __( '2nd', 'wc_makecommerce_domain' ), |
| 449 |
3 => __( '3rd', 'wc_makecommerce_domain' ), |
| 450 |
4 => __( '4th', 'wc_makecommerce_domain' ), |
| 451 |
5 => __( '5th', 'wc_makecommerce_domain' ), |
| 452 |
6 => __( '6th', 'wc_makecommerce_domain' ), |
| 453 |
7 => __( '7th', 'wc_makecommerce_domain' ), |
| 454 |
8 => __( '8th', 'wc_makecommerce_domain' ), |
| 455 |
9 => __( '9th', 'wc_makecommerce_domain' ), |
| 456 |
10 => __( '10th', 'wc_makecommerce_domain' ), |
| 457 |
11 => __( '11th', 'wc_makecommerce_domain' ), |
| 458 |
12 => __( '12th', 'wc_makecommerce_domain' ), |
| 459 |
13 => __( '13th', 'wc_makecommerce_domain' ), |
| 460 |
14 => __( '14th', 'wc_makecommerce_domain' ), |
| 461 |
15 => __( '15th', 'wc_makecommerce_domain' ), |
| 462 |
16 => __( '16th', 'wc_makecommerce_domain' ), |
| 463 |
17 => __( '17th', 'wc_makecommerce_domain' ), |
| 464 |
18 => __( '18th', 'wc_makecommerce_domain' ), |
| 465 |
19 => __( '19th', 'wc_makecommerce_domain' ), |
| 466 |
20 => __( '20th', 'wc_makecommerce_domain' ), |
| 467 |
21 => __( '21st', 'wc_makecommerce_domain' ), |
| 468 |
22 => __( '22nd', 'wc_makecommerce_domain' ), |
| 469 |
23 => __( '23rd', 'wc_makecommerce_domain' ), |
| 470 |
24 => __( '24th', 'wc_makecommerce_domain' ), |
| 471 |
25 => __( '25th', 'wc_makecommerce_domain' ), |
| 472 |
26 => __( '26th', 'wc_makecommerce_domain' ), |
| 473 |
27 => __( '27th', 'wc_makecommerce_domain' ), |
| 474 |
28 => __( '28th', 'wc_makecommerce_domain' ), |
| 475 |
29 => __( '29th', 'wc_makecommerce_domain' ), |
| 476 |
30 => __( '30th', 'wc_makecommerce_domain' ), |
| 477 |
31 => __( '31st', 'wc_makecommerce_domain' ) |
| 478 |
); |
| 479 |
|
| 480 |
return $days[ date( 'j', $timestamp ) ]; |
| 481 |
} |
| 482 |
} |
| 483 |
|