From 924a0c0423c01befe9ec225715bf2fbec33d0a17 Mon Sep 17 00:00:00 2001 From: Matthias Reuter Date: Fri, 24 Jul 2015 19:30:03 +0200 Subject: [PATCH] 1.10.4 * MarketPress: Error upon payment activation fixed * WooCommerce: Official support for custom intervals (e.g. every 3rd month) added * WooCommerce: Different bugs in order processing and subscription handling fixed --- CHANGES.md | 19 +-- README.md | 17 +- lib/css/paymill.css | 2 +- lib/errors.inc.php | 4 + lib/integration/marketpress.inc.php | 6 +- lib/integration/subscriptions.inc.php | 2 +- lib/integration/woocommerce.inc.php | 228 ++++++++++++++------------ lib/js/paymill.js | 28 +++- lib/setup.inc.php | 2 +- license.txt | 4 +- paymill.php | 2 +- readme.txt | 42 +++-- 12 files changed, 206 insertions(+), 150 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b6e042e..000be6f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,25 +2,16 @@ Changelog ========= +1.10.4 +----- +* MarketPress: Error upon payment activation fixed +* WooCommerce: Official support for custom intervals (e.g. every 3rd month) added +* WooCommerce: Different bugs in order processing and subscription handling fixed 1.10.3 ----- * Common: Integration of PayFrame to enable use of SAQ A for easier PCI DSS 3.0 compliance. -PayFrame -We’ve introduced a “payment form” option for easier compliance with PCI requirements. - -In addition to having a payment form directly integrated in your checkout page, you -can use our embedded PayFrame solution to ensure that payment data never -touches your website. - -PayFrame is enabled by default, but you can choose between both options in the -plugin settings. Later this year, we’re bringing you the ability to customise the -appearance and text content of the PayFrame version. - -To learn more about the benefits of PayFrame, please visit our FAQ: -https://www.paymill.com/en/faq/howdoespaymillspayframesolutionwork - 1.10.2 ----- * Subscriptions: "required_offer_or_amount_and_currency_and_interval" error fixed diff --git a/README.md b/README.md index 4d8ed14..9f667c0 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ PAYMILL for WordPress * Tags: paymill, creditcard, elv, payment, woocommerce, paybutton, ecommerce, debitcard, subscriptions * Requires at least: 4.2.2 * Tested up to: 4.2.2 -* Stable tag: 1.10.3 +* Stable tag: 1.10.4 * License: [GPLv3 or later](http://www.gnu.org/licenses/gpl-3.0.html) With PAYMILL you are able to provide credit card based payments for your customers. @@ -46,6 +46,21 @@ Accept payments in up to 100 currencies. All major card brands like MasterCard, VISA, American Express, Diner's Club, Maestro etc. supported. Available in 40 countries across Europe so far. +Pay Frame +========= + +We’ve introduced a “payment form” option for easier compliance with PCI requirements. + +In addition to having a payment form directly integrated in your checkout page, you +can use our embedded PayFrame solution to ensure that payment data never +touches your website. + +PayFrame is enabled by default, but you can choose between both options in the +plugin settings. Later this year, we’re bringing you the ability to customise the +appearance and text content of the PayFrame version. + +To learn more about the benefits of PayFrame, please visit our FAQ: +https://www.paymill.com/en/faq/how-does-paymills-payframe-solution-work Team ==== diff --git a/lib/css/paymill.css b/lib/css/paymill.css index 2a4f01a..e26e79b 100644 --- a/lib/css/paymill.css +++ b/lib/css/paymill.css @@ -130,7 +130,7 @@ paymill_#form_credit, paymill_#form_elv{ .paymill_payment_logos{ margin-bottom:10px; } -.paymill_payment_logos img{ +.paymill_payment_logos img, .payment_method_paymill label img{ display:inline; } diff --git a/lib/errors.inc.php b/lib/errors.inc.php index 58725d8..f5e5a7e 100644 --- a/lib/errors.inc.php +++ b/lib/errors.inc.php @@ -49,6 +49,10 @@ public function getErrors($function=false,$flush=false){ $this->errors = array(); } } + + public function reset(){ + $this->errors = array(); + } } ?> \ No newline at end of file diff --git a/lib/integration/marketpress.inc.php b/lib/integration/marketpress.inc.php index 35e491b..292c029 100644 --- a/lib/integration/marketpress.inc.php +++ b/lib/integration/marketpress.inc.php @@ -19,10 +19,10 @@ class MP_Gateway_Paymill_for_WordPress extends MP_Gateway_API { var $plugin_name = 'paymill-for-wordpress'; //name of your gateway, for the admin side. - var $admin_name = ''; + var $admin_name = 'Paymill for WordPress'; //public name of your gateway, for lists and such. - var $public_name = ''; + var $public_name = 'Paymill'; //url for an image for your checkout method. Displayed on checkout form if set var $method_img_url = ''; @@ -164,7 +164,7 @@ function on_creation() { //set names here to be able to translate $this->admin_name = __('Paymill for WordPress', 'paymill'); - $this->public_name = $this->method_button_img_url = $settings['gateways']['paymill-for-wordpress']['name']; + $this->public_name = $settings['gateways']['paymill-for-wordpress']['name'] ? $settings['gateways']['paymill-for-wordpress']['name'] : 'Paymill'; //button img $this->method_img_url = $this->method_button_img_url = $settings['gateways']['paymill-for-wordpress']['image-url']; diff --git a/lib/integration/subscriptions.inc.php b/lib/integration/subscriptions.inc.php index 1cbc3e7..8d7349d 100644 --- a/lib/integration/subscriptions.inc.php +++ b/lib/integration/subscriptions.inc.php @@ -42,7 +42,7 @@ public function details($sub_id){ public function create($client, $offer, $payment, $startAt=false, $periodOfValidity=false){ if(paymill_BENCHMARK)paymill_doBenchmark(true,'paymill_subscription_create'); // benchmark load_paymill(); // this function-call can and should be used whenever working with Paymill API - + try{ $GLOBALS['paymill_loader']->request_subscription->setClient($client); $GLOBALS['paymill_loader']->request_subscription->setOffer($offer); diff --git a/lib/integration/woocommerce.inc.php b/lib/integration/woocommerce.inc.php index 41b83f3..cc2bb4b 100644 --- a/lib/integration/woocommerce.inc.php +++ b/lib/integration/woocommerce.inc.php @@ -279,11 +279,31 @@ function paymill_webhooks(){ */ error_log(var_export($subscription,true)."\n\n", 3, PAYMILL_DIR.'lib/debug/PHP_errors.log'); + // prevent multiple subscription renewals because of multiple webhook attempts. + $whole_period = 0; + switch ($subscription['period']) { + case 'day': + default: + $whole_period = intval($subscription['interval']) * 86400; + break; + case 'week': + $whole_period = intval($subscription['interval']) * 604800; + break; + case 'month': + $whole_period = intval($subscription['interval']) * 2160000; // using 25 days to prevent problems with shorter months + break; + case 'year': + $whole_period = intval($subscription['interval']) * 30240000; // using 350 days to prevent any timezone problems whatsoever + break; + } + if(count($subscription['completed_payments']) >= 1){ - $order = new WC_Order($subscription['order_id']); - - //WC_Subscriptions_Manager::process_subscription_payments_on_order($order, $subscription['product_id']); - WC_Subscriptions_Manager::process_subscription_payments_on_order($order); + if (strtotime(date(DATE_RFC822)) > strtotime($subscription['last_payment_date']) + $whole_period - 18000) { // minus 5 hours to prevent any problems with pending triggers + $order = new WC_Order($subscription['order_id']); + + //WC_Subscriptions_Manager::process_subscription_payments_on_order($order, $subscription['product_id']); + WC_Subscriptions_Manager::process_subscription_payments_on_order($order); + } }else{ $order = new WC_Order($subscription['order_id']); $order->payment_complete(); @@ -374,9 +394,8 @@ function init_paymill_gateway_class(){ if(class_exists('WC_Payment_Gateway')){ class WC_Gateway_Paymill_Gateway extends WC_Payment_Gateway{ - private $total = 0; - private $total_complete = 0; - private $total_sub_refund = 0; + private $totalProducts = 0; + private $totalSub = 0; private $cart = false; private $order_id = false; private $order = false; @@ -472,127 +491,129 @@ private function getCurrentClient(){ return $this->clientClass->getCurrentClient(); } private function getTotals(){ + $this->totalProducts = (floatval($this->order->get_total())*100); + // retrieve subscriptions amount if(class_exists('WC_Subscriptions_Order') && WC_Subscriptions_Order::order_contains_subscription($this->order)){ - foreach($this->cart as $product){ - if(is_object($product) && isset($product->id) && intval($product->id) > 0){ - $woo_sub_key = WC_Subscriptions_Manager::get_subscription_key($this->order_id,$product->id); - - if(!WC_Subscriptions_Manager::user_has_subscription(get_current_user_id(), $woo_sub_key)){ - $sub_amount_total = floatval(floatval(WC_Subscriptions_Order::get_recurring_total($this->order))*100); - $this->total = $this->total-$sub_amount_total; - if($this->total_complete == 0){ - $this->total_complete = $this->total_complete+$sub_amount_total; - } - } - } - } - // currently, there is no initial payment fee possible through paymill, so we are required to make a refund if a coupon is reducing initial fee. - /*if($this->total < 0){ - $this->total_sub_refund = (($this->total)*(-1)); - }*/ + $this->totalSub = floatval(floatval(WC_Subscriptions_Order::get_recurring_total($this->order))*100); + $this->totalProducts = $this->totalProducts-$this->totalSub; } } private function processSubscriptions(){ global $wpdb; - + // check wether subscriptions addon is activated if(class_exists('WC_Subscriptions_Order') && WC_Subscriptions_Order::order_contains_subscription($this->order)){ - $product = $this->cart; - //foreach($this->cart as $product){ - if(is_array($product) && isset($product['product_id']) && intval($product['product_id']) > 0){ + $products = $this->order->get_items(); + foreach($products as $product){ + if(is_array($product) && isset($product['product_id']) && intval($product['product_id']) > 0 && isset($product['subscription_period']) && $product['subscription_period'] != ''){ // product is a subscription? $woo_sub_key = WC_Subscriptions_Manager::get_subscription_key($this->order_id,$product['product_id']); - // check wether user already has subscription - //if(!WC_Subscriptions_Manager::user_has_subscription(get_current_user_id(), $woo_sub_key)){ - - // required vars - $amount = (floatval(WC_Subscriptions_Order::get_recurring_total($this->order))*100); - $currency = get_woocommerce_currency(); - $interval = WC_Subscriptions_Order::get_subscription_interval($this->order,$product['product_id']); - $length = intval(WC_Subscriptions_Order::get_subscription_length($this->order,$product['product_id'])); - $period = strtoupper(WC_Subscriptions_Order::get_subscription_period($this->order,$product['product_id'])); - if ($length > 0) { - $periodOfValidity = $length.' '.$period; - } else{ - $periodOfValidity = false; - } - $trial_end = strtotime(WC_Subscriptions_Product::get_trial_expiration_date($product['product_id'], get_gmt_from_date($this->order->order_date))); - if($trial_end === false){ - $trial_time = 0; - }else{ - $datediff = $trial_end - time(); - $trial_time = ceil($datediff/(60*60*24)); - } + // required vars + $amount = (floatval(WC_Subscriptions_Order::get_recurring_total($this->order))*100); + $currency = get_woocommerce_currency(); + $interval = intval($product['subscription_interval']); + $period = strtoupper($product['subscription_period']); + $length = strtoupper($product['subscription_length']); + + if ($length > 0) { + $periodOfValidity = $length.' '.$period; + } else{ + $periodOfValidity = false; + } + $trial_end = strtotime(WC_Subscriptions_Product::get_trial_expiration_date($product['product_id'], get_gmt_from_date($this->order->order_date))); + if($trial_end === false){ + $trial_time = 0; + }else{ + $datediff = $trial_end - time(); + $trial_time = ceil($datediff/(60*60*24)); + } + + // md5 name + $woo_sub_md5 = md5($amount.$currency.$interval.$trial_time); + + // get offer + $name = 'woo_'.$product['product_id'].'_'.$woo_sub_md5; + $offer = $this->subscriptions->offerGetDetailByName($name); + + // check wether offer exists in paymill + if($offer === false){ + // offer does not exist in paymill yet, create it + $params = array( + 'amount' => $amount, + 'currency' => $currency, + 'interval' => $interval.' '.$period, + 'name' => $name, + 'trial_period_days' => intval($trial_time) + ); + $offer = $this->subscriptions->offerCreate($params); - // md5 name - $woo_sub_md5 = md5($amount.$currency.$interval.$trial_time); - - // get offer - $name = 'woo_'.$product['product_id'].'_'.$woo_sub_md5; - $offer = $this->subscriptions->offerGetDetailByName($name); - - // check wether offer exists in paymill - if($offer === false){ - // offer does not exist in paymill yet, create it - $params = array( - 'amount' => $amount, - 'currency' => $currency, - 'interval' => $interval.' '.$period, - 'name' => $name, - 'trial_period_days' => intval($trial_time) - ); - $offer = $this->subscriptions->offerCreate($params); - - if($GLOBALS['paymill_loader']->paymill_errors->status()){ - $GLOBALS['paymill_loader']->paymill_errors->getErrors(); - return false; - } + if($GLOBALS['paymill_loader']->paymill_errors->status()){ + $GLOBALS['paymill_loader']->paymill_errors->getErrors(); + return false; } - // create user subscription - $user_sub = $this->subscriptions->create($this->client->getId(), $offer, $this->paymentClass->getPaymentID(),(isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false),$periodOfValidity); + } + // create user subscription + $user_sub = $this->subscriptions->create($this->client->getId(), $offer, $this->paymentClass->getPaymentID(),(isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false),$periodOfValidity); + + if($GLOBALS['paymill_loader']->paymill_errors->status()){ + //maybe offer cache is outdated, recache and try again + + $GLOBALS['paymill_loader']->paymill_errors->reset(); // reset error status + + $this->subscriptions->offerGetList(true); + + $params = array( + 'amount' => $amount, + 'currency' => $currency, + 'interval' => $interval.' '.$period, + 'name' => $name, + 'trial_period_days' => intval($trial_time) + ); + $offer = $this->subscriptions->offerCreate($params); if($GLOBALS['paymill_loader']->paymill_errors->status()){ $GLOBALS['paymill_loader']->paymill_errors->getErrors(); return false; - }else{ - $wpdb->query($wpdb->prepare('INSERT INTO '.$wpdb->prefix.'paymill_subscriptions (paymill_sub_id, woo_user_id, woo_offer_id) VALUES (%s, %s, %s)', - array( - $user_sub, - get_current_user_id(), - $woo_sub_key - ))); - - // subscription successful - do_action('paymill_woocommerce_subscription_created', array( - 'product_id' => $product['product_id'], - 'offer_id' => $offer, - //'offer_data' => $offer - )); - - return true; } - /*}else{ - // @todo: currently, WooCommerce does not support multiple subscriptions on checkout, so we can stop processing here if first subscription is already subscribed - $GLOBALS['paymill_loader']->paymill_errors->setError(__('Subscription already subscribed.', 'paymill')); + + $user_sub = $this->subscriptions->create($this->client->getId(), $offer, $this->paymentClass->getPaymentID(),(isset($_POST['paymill_delivery_date']) ? $_POST['paymill_delivery_date'] : false),$periodOfValidity); + if($GLOBALS['paymill_loader']->paymill_errors->status()){ $GLOBALS['paymill_loader']->paymill_errors->getErrors(); + return false; } - return false; - }*/ + } + + $wpdb->query($wpdb->prepare('INSERT INTO '.$wpdb->prefix.'paymill_subscriptions (paymill_sub_id, woo_user_id, woo_offer_id) VALUES (%s, %s, %s)', + array( + $user_sub, + get_current_user_id(), + $woo_sub_key + ))); + + // subscription successful + do_action('paymill_woocommerce_subscription_created', array( + 'product_id' => $product['product_id'], + 'offer_id' => $offer, + //'offer_data' => $offer + )); + + return true; } - //} + } }else{ return true; } } - private function processProducts(){ + private function processProducts(){ global $wpdb; - if($this->total > 0){ + + if($this->totalProducts > 0){ // make transaction - $GLOBALS['paymill_loader']->request_transaction->setAmount(round($this->total,2)); // e.g. "4200" for 42.00 EUR + $GLOBALS['paymill_loader']->request_transaction->setAmount(round($this->totalProducts,2)); // e.g. "4200" for 42.00 EUR $GLOBALS['paymill_loader']->request_transaction->setCurrency(get_woocommerce_currency()); if($this->paymentClass->getPreauthID() != false){ $GLOBALS['paymill_loader']->request_transaction->setPreauthorization($this->paymentClass->getPreauthID()); @@ -629,7 +650,7 @@ private function processProducts(){ ))); do_action('paymill_woocommerce_products_paid', array( - 'total' => $this->total, + 'total' => $this->totalProducts, 'currency' => get_woocommerce_currency(), 'client' => $response['body']['data']['client']['id'] )); @@ -653,11 +674,6 @@ public function process_payment($order_id){ $this->order_id = $order_id; $this->order_desc = $_SERVER['HTTP_HOST'].': '.__('Order #','paymill').$this->order_id.__(', Customer-ID #','paymill').get_current_user_id(); $this->order = new WC_Order($this->order_id); - $cart = $woocommerce->cart->get_cart(); - $cart = reset($cart); - $this->cart = $cart; - $this->total_complete = - $this->total = (floatval($this->order->get_total())*100); // load subscription class $this->subscriptions = new paymill_subscriptions('woocommerce'); @@ -668,7 +684,7 @@ public function process_payment($order_id){ // create payment object and preauthorization require_once(PAYMILL_DIR.'lib/integration/payment.inc.php'); - $this->paymentClass = new paymill_payment($this->client->getId(),$this->total_complete,get_woocommerce_currency()); // create payment object, as it should be used for next processing instead of the token. + $this->paymentClass = new paymill_payment($this->client->getId(),($this->totalProducts+$this->totalSub),get_woocommerce_currency()); // create payment object, as it should be used for next processing instead of the token. if($GLOBALS['paymill_loader']->paymill_errors->status()){ $GLOBALS['paymill_loader']->paymill_errors->getErrors(); return false; diff --git a/lib/js/paymill.js b/lib/js/paymill.js index a186a50..04dacef 100644 --- a/lib/js/paymill.js +++ b/lib/js/paymill.js @@ -4,9 +4,8 @@ if(paymill_pcidss3 == 1){ //document.getElementById("paymill_payment_errors").textContent += message + "\n"; console.log(message); } - - // Load embedded credit card frame in an iframe. - jQuery('body').on('updated_checkout',function(){ + + function paymill_embed_pcidss3_frame(){ // Prepare container element, either ID or DOM element - all variants are useable. var frameContainer = jQuery('#paymill_form_credit'); @@ -29,15 +28,25 @@ if(paymill_pcidss3 == 1){ }, frameCallback ); + } + + jQuery(document).ready(function(){ + paymill_embed_pcidss3_frame(); + }); + + // Load embedded credit card frame in an iframe. + jQuery('body').on('updated_checkout',function(){ + paymill_embed_pcidss3_frame(); }); } -jQuery(document).ready(function () { +jQuery(document).ready(function(){ var paymill_youshallpass = false; if(typeof paymill_shop_name != 'undefined'){ if(paymill_shop_name == 'woocommerce' || jQuery('body').hasClass('woocommerce-checkout')){ jQuery('body').on('click', paymill_form_checkout_submit_id, function(event) { + console.log('test'); // set delivery date if(jQuery.datepicker && jQuery("#e_deliverydate").length != 0){ var datefield = jQuery('#e_deliverydate').datepicker('getDate'); @@ -83,7 +92,16 @@ jQuery(document).ready(function () { } function bridgePreparePayment(){ // check which payment method is active - if((jQuery('#paymill_form_credit').is(':visible') || (jQuery('#paymill_card_number').length > 0 && jQuery('#paymill_card_number').val() != '')) && (jQuery('#payment_method_paymill').is(':checked') || jQuery('.wgm-second-checkout input[name=payment_method]').val() == 'paymill' || paymill_shop_name == 'cart66')){ + if( + ( + jQuery('#paymill_form_credit').is(':visible') || + (jQuery('#paymill_card_number').length > 0 && jQuery('#paymill_card_number').val() != '') + ) && + ( + jQuery('#payment_method_paymill').is(':checked') || + jQuery('.wgm-second-checkout input[name=payment_method]').val() == 'paymill' || + paymill_shop_name == 'cart66') + ){ if(paymill_pcidss3 == 1){ paymill.createTokenViaFrame({ amount_int: jQuery('.paymill_amount').val(), diff --git a/lib/setup.inc.php b/lib/setup.inc.php index 895c8ce..2907dab 100644 --- a/lib/setup.inc.php +++ b/lib/setup.inc.php @@ -79,7 +79,7 @@ function paymill_check_webhook(){ return '
'.__('No Webhook created yet.','paymill').' '.__('Please insert API Keys in General Settings and submit the form.','paymill').'
'; }else{ $webhooks = $GLOBALS['paymill_loader']->request->getAll($GLOBALS['paymill_loader']->request_webhook); - + $webhook_found = false; $nothing_found = true; $additional_webhooks = array(); diff --git a/license.txt b/license.txt index 68e59e1..2bccbcc 100644 --- a/license.txt +++ b/license.txt @@ -1,6 +1,6 @@ Paymill WordPress Plugin - is a plugin based on Paymill API allowing credit card payment in WordPress. (Currently supports WooCommerce only) - Code (c) 2013-2014 Matthias Reuter + is a plugin based on Paymill API allowing credit card payment in WordPress. + Code (c) 2013-2015 Matthias Reuter Project Leader: Matthias Reuter Project Website: https://www.paymill.com diff --git a/paymill.php b/paymill.php index 52ad2af..9ce3d0a 100644 --- a/paymill.php +++ b/paymill.php @@ -3,7 +3,7 @@ Plugin Name: PAYMILL GmbH Plugin URI: https://www.paymill.com Description: Payments made easy -Version: 1.10.3 +Version: 1.10.4 Author: Matthias Reuter info@straightvisions.com Author URI: http://elbnetz.com */ diff --git a/readme.txt b/readme.txt index e08e157..37272e8 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Donate link: Tags: paymill, creditcard, elv, sepa, payment, woocommerce, paybutton, shopp, shopplugin, marketpress, magic members, magicmembers, mgm, cart66, ecommerce, debitcard, subscriptions Requires at least: 4.2.2 Tested up to: 4.2.2 -Stable tag: 1.10.3 +Stable tag: 1.10.4 License: GPLv3 or later License URI: http://www.gnu.org/licenses/gpl-3.0.html @@ -33,6 +33,20 @@ With PAYMILL you are able to provide credit card based payments for your custome PAYMILL offers the fastest and easiest way to accept payments online. The innovative payment solution enables online businesses and services to integrate payments into their websites within a very short time. The developer-friendly REST API is flexibly integrable. Customize the check-out process the way you want or use the PAYMILL PayButton which allows an even easier integration. Super-fast account activation within a few days only. Top-notch customer support. Subscriptions supported and Mobile SDKs for iOS and Android available. Accept payments in up to 100 currencies. All major card brands like MasterCard, VISA, American Express, Diner's Club, Maestro etc. supported. Available in 40 countries across Europe so far. += Pay Frame = +We’ve introduced a “payment form” option for easier compliance with PCI requirements. + +In addition to having a payment form directly integrated in your checkout page, you +can use our embedded PayFrame solution to ensure that payment data never +touches your website. + +PayFrame is enabled by default, but you can choose between both options in the +plugin settings. Later this year, we’re bringing you the ability to customise the +appearance and text content of the PayFrame version. + +To learn more about the benefits of PayFrame, please visit our FAQ: +https://www.paymill.com/en/faq/how-does-paymills-payframe-solution-work + = Team = * Officially supported by PAYMILL @@ -152,23 +166,15 @@ Subscriptions fee cannot be changed, even through coupons. Please be aware that == Changelog == -= 1.10.3 = - -* Common: Integration of PayFrame to enable use of SAQ A for easier PCI DSS 3.0 compliance. += 1.10.4 = -PayFrame -We’ve introduced a “payment form” option for easier compliance with PCI requirements. +* MarketPress: Error upon payment activation fixed +* WooCommerce: Official support for custom intervals (e.g. every 3rd month) added +* WooCommerce: Different bugs in order processing and subscription handling fixed -In addition to having a payment form directly integrated in your checkout page, you -can use our embedded PayFrame solution to ensure that payment data never -touches your website. - -PayFrame is enabled by default, but you can choose between both options in the -plugin settings. Later this year, we’re bringing you the ability to customise the -appearance and text content of the PayFrame version. += 1.10.3 = -To learn more about the benefits of PayFrame, please visit our FAQ: -https://www.paymill.com/en/faq/howdoespaymillspayframesolutionwork +* Common: Integration of PayFrame to enable use of SAQ A for easier PCI DSS 3.0 compliance. = 1.10.2 = @@ -382,6 +388,12 @@ WooCommerce support added == Upgrade Notice == += 1.10.4 = + +* MarketPress: Error upon payment activation fixed +* WooCommerce: Official support for custom intervals (e.g. every 3rd month) added +* WooCommerce: Different bugs in order processing and subscription handling fixed + = 1.10.3 = * Common: Integration of PayFrame to enable use of SAQ A for easier PCI DSS 3.0 compliance.