Coder Social home page Coder Social logo

pronamic / memberpress Goto Github PK

View Code? Open in Web Editor NEW
8.0 8.0 3.0 5.21 MB

MemberPress, Git-ified. This repository is just a mirror of the MemberPress plugin. Please do not send pull requests.

Home Page: https://memberpress.com/change-log/

License: GNU General Public License v2.0

PHP 85.20% JavaScript 8.62% CSS 5.97% SCSS 0.03% HTML 0.18%
memberpress wordpress wordpress-plugin pronamic

memberpress's People

Contributors

remcotolsma avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

memberpress's Issues

Incorrect value in `MeprSubscription.total` when adjusting price in `nl_NL` WordPress installation, `9,99` is saved as `999`

When the WordPress language / locale is configured to nl_NL the MeprSubscription->total value is calculated / stored incorrect. This is due to the incorrect handling in MeprSubscriptionsCtrl->create_or_update( $sub ) and MeprUtils::format_currency_us_float( $number, $num_decimals = 2 ).

app/lib/MeprUtils.php#L381-L408

  /**
   * Converts number to US format
   *
   * @param  mixed $number
   * @param  mixed $num_decimals
   * @return void
   */
  public static function format_currency_us_float($number, $num_decimals = 2) {
    global $wp_locale;

    if ( ! isset( $wp_locale ) || false === function_exists('number_format_i18n') ) {
      return self::format_float($number, $num_decimals);
    }

    $decimal_point = $wp_locale->number_format['decimal_point'];
    $thousands_sep = $wp_locale->number_format['thousands_sep'];

    // Remove thousand separator
    $number = str_replace ($thousands_sep, '' , $number);

    // Replaces decimal separator
    $index = strrpos( $number, $decimal_point );
    if( $index !== FALSE ){
      $number[ $index ] = '.';
    }

    return (float) $number;
  }

When the WordPress language / locale is configured to nl_NL this function will work with the following values:

// In `nl_NL` this will be a comma character: ','.
$decimal_point = $wp_locale->number_format['decimal_point'];
// In `nl_NL` this will be a full stop (period) character: '.'.
$thousands_sep = $wp_locale->number_format['thousands_sep'];

When a Dutch user enters for example a price of 9,99 this means 9 euros and 99 cents. It should be stored a 9.99 in the database, but it's stored as 999. This is due to the following code:

app/controllers/MeprSubscriptionsCtrl.php#L137-L172

  private function create_or_update($sub) {
    check_admin_referer( 'mepr_create_or_update_subscription', 'mepr_subscriptions_nonce' );

    extract($_POST, EXTR_SKIP);
    $user = new MeprUser();
    $user->load_user_data_by_login($user_login);
    $sub->user_id = $user->ID;
    $sub->subscr_id = wp_unslash($subscr_id);
    $sub->product_id = $product_id;
    $product = new MeprProduct($product_id);
    $sub->price = isset($price) ? MeprUtils::format_currency_us_float( $price ) : MeprUtils::format_currency_us_float( $product->price );
    $sub->period = isset($period) ? (int) $period : (int) $product->period;
    $sub->period_type = isset($period_type) ? (string) $period_type : (string) $product->period_type;
    $sub->limit_cycles = isset($limit_cycles) ? (boolean) $limit_cycles : $product->limit_cycles;
    $sub->limit_cycles_num = isset($limit_cycles_num) ? (int) $limit_cycles_num : (int) $product->limit_cycles_num;
    $sub->limit_cycles_action = isset($limit_cycles_action) ? $limit_cycles_action : $product->limit_cycles_action;
    $sub->limit_cycles_expires_after = isset($limit_cycles_expires_after) ? (int) $limit_cycles_expires_after : (int) $product->limit_cycles_expires_after;
    $sub->limit_cycles_expires_type = isset($limit_cycles_expires_type) ? (string) $limit_cycles_expires_type : (string) $product->limit_cycles_expires_type;
    $sub->tax_amount = MeprUtils::format_currency_us_float( $tax_amount );
    $sub->tax_rate = MeprUtils::format_currency_us_float( $tax_rate );
    $sub->total = MeprUtils::format_currency_us_float( $sub->price + $sub->tax_amount );
    $sub->status = $status;
    $sub->gateway = $gateway;
    $sub->trial = isset($trial) ? (boolean) $trial : false;
    $sub->trial_days = (int) $trial_days;
    $sub->trial_amount = MeprUtils::format_currency_us_float( $trial_amount );
    $sub->trial_tax_amount = (isset($trial_tax_amount) ? (float) $trial_tax_amount : 0.0);
    $sub->trial_total = (isset($trial_total) ? (float) $trial_total : 0.0);
    if(isset($created_at) && (empty($created_at) || is_null($created_at))) {
      $sub->created_at = MeprUtils::ts_to_mysql_date(time());
    }
    else {
      $sub->created_at = MeprUtils::ts_to_mysql_date(strtotime($created_at));
    }
    return $sub->store();
  }

On app/controllers/MeprSubscriptionsCtrl.php#L147 the submitted $_POST['price'] of 9,99 is converted to 9.99 and stored in $sub->price. On app/controllers/MeprSubscriptionsCtrl.php#L157 this value is passed to the MeprUtils::format_currency_us_float( ) function again and then it goes wrong. The $thousands_sep = '.' is replaced by an empty string '' and this will convert 9.99 to 999.

MemberPress does nothing with the gateway `create-subscriptions` capability

The default MemberPress gateways all have the create-subscriptions capability:

https://github.com/pronamic/memberpress/search?q=create-subscriptions

That is why we also have this in the Pronamic Pay MemberPress Add-On:

https://github.com/pronamic/wp-pronamic-pay-memberpress/blob/a9ed77a71e2c64ab14e744461661aba8ee08a121/src/Gateways/Gateway.php#L99-L118

You would expect MemberPress to also do something for gateways that don't have the create-subscriptions capability, but unfortunately that is not the case.

MemberPress does not display gateway exceptions/errors on payment form

The exceptions/errors from gateways are stored in $_POST['errors']:

public function process_payment_form() {
if(isset($_POST['mepr_process_payment_form']) && isset($_POST['mepr_transaction_id']) && is_numeric($_POST['mepr_transaction_id'])) {
$txn = new MeprTransaction($_POST['mepr_transaction_id']);
if($txn->rec != false) {
$mepr_options = MeprOptions::fetch();
if(($pm = $mepr_options->payment_method($txn->gateway)) && $pm instanceof MeprBaseRealGateway) {
$errors = $pm->validate_payment_form(array());
if(empty($errors)) {
// process_payment_form either returns true
// for success or an array of $errors on failure
try {
$pm->process_payment_form($txn);
}
catch(Exception $e) {
MeprHooks::do_action('mepr_payment_failure', $txn);
$errors = array($e->getMessage());
}
}
if(empty($errors)) {
//Reload the txn now that it should have a proper trans_num set
$txn = new MeprTransaction($txn->id);
$product = new MeprProduct($txn->product_id);
$sanitized_title = sanitize_title($product->post_title);
$query_params = array('membership' => $sanitized_title, 'trans_num' => $txn->trans_num, 'membership_id' => $product->ID);
if($txn->subscription_id > 0) {
$sub = $txn->subscription();
$query_params = array_merge($query_params, array('subscr_id' => $sub->subscr_id));
}
MeprUtils::wp_redirect($mepr_options->thankyou_page_url(build_query($query_params)));
}
else {
// Artificially set the payment method params so we can use them downstream
// when display_payment_form is called in the 'the_content' action.
$_REQUEST['payment_method_params'] = array(
'method' => $pm->id,
'amount' => $txn->amount,
'user' => new MeprUser($txn->user_id),
'product_id' => $txn->product_id,
'transaction_id' => $txn->id
);
$_REQUEST['mepr_payment_method'] = $pm->id;
$_POST['errors'] = $errors;
return;
}
}
}
}
$_POST['errors'] = array(__('Sorry, an unknown error occurred.', 'memberpress'));
}

Only errors in $_REQUEST['errors'] are displayed:

// Called in the 'the_content' hook ... used to display a signup form
public function display_payment_form() {
$mepr_options = MeprOptions::fetch();
if(isset($_REQUEST['payment_method_params'])) {
extract($_REQUEST['payment_method_params'], EXTR_SKIP);
if(isset($_REQUEST['errors']) && !empty($_REQUEST['errors'])) {
$errors = $_REQUEST['errors'];
MeprView::render('/shared/errors', get_defined_vars());
}
if(($pm = $mepr_options->payment_method($method)) &&
($pm instanceof MeprBaseRealGateway)) {
$pm->display_payment_form($amount, $user, $product_id, $transaction_id);
}
}
}

I did notice some 'Deprecated?' comments:

$_POST['errors'] = $errors; //Deprecated?
$_REQUEST['errors'] = $errors;

catch(MeprCreateException $e) {
$_POST['errors'] = array(__( 'The user was unable to be saved.', 'memberpress')); //Deprecated?
$_REQUEST['errors'] = array(__( 'The user was unable to be saved.', 'memberpress'));
return;
}

Sigh, MemberPress... ๐Ÿ˜ž

Stripe minimum charge amounts applied

In MemberPress version 1.11.7 minimum charge amounts have been introduced:

1.11.7 โ€“ 2023-07-10

Fixed

  • Round up to Stripe's minimum charge for each currency if checkout amount is less than minimum allowed by Stripe

Unfortunately, as MemberPress applies these minimum amounts per currency, this change also applies to third-party gateways. This results in for example a payment of โ‚ฌ 0,50 for a membership with a trial amount of only โ‚ฌ 0,02.

memberpress-trial-amount

There is a filter mepr_minimum_charge_amounts in https://github.com/pronamic/memberpress/blob/v1.11.7/app/data/minimum_charge_amounts.php, but as far as I can see it is not possible or would be difficult to change the minimum charge based on the selected gateway โ€” the product title in the screenshot for example is not updated when selecting a different payment method.

The easiest way to complete opt-out of the Stripe minimum charge amounts is by adding this filter:

\add_filter( 'mepr_minimum_charge_amounts', '__return_empty_array' );

Internal Help Scout ticket: https://secure.helpscout.net/conversation/2392565894/26308

Originally posted by @rvdsteege in pronamic/wp-pronamic-pay-memberpress#16

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.