This release is a complete rewrite of the first dev version. This release provide the same functionalities as the first one but is made for passing the drupal security checks (as informations it's the first module I publish on drupal, so I might have forgotten some things).

Behaviors changes

  • When submitting an escrow payment, a transfer or a withdrawal request the commission is added to the amount of the request and not deducted to it for more transparency for the user. So the transfer, the escrow or the withdrawal equals the requested amount.
  • When making a deposit, the commission is deducted from the deposit.
  • Transfers and Escrows are now based on user names and not user email addresses.

New features

  • Views integration
  • Rules integration
  • Tokens integration
  • Username autocomplete
  • Transactions and Withdrawal requests as entity
  • Site balance block to follow the incomes from transactions
  • User balances block for the administration, so admin (or users with the permission) can see user balances on user pages

Project link

https://www.drupal.org/project/ubercart_funds

Git instructions

git clone --branch 7.x-2.x https://git.drupal.org/project/ubercart_funds.git

Comments

Aporie created an issue. See original summary.

aporie’s picture

Issue summary: View changes
avpaderno’s picture

Issue summary: View changes
sumit-k’s picture

Hi Aporie,

Please fix the pareview errors reported.

https://pareview.sh/pareview/https-git.drupal.org-project-ubercart_funds...

avpaderno’s picture

Status: Needs review » Needs work
aporie’s picture

Hi bsumit5577,

I have updated the code. Please pull.

There is still some things I don't know how to fix :

FILE: ...nds/views/ubercart_funds_handler_field_withdrawal_operations.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 23 | ERROR | Public method name
    |       | "UcFundsHandlerFieldWithdrawalOperations::pre_render"
    |       | is not in lowerCamel format
----------------------------------------------------------------------


FILE: ...t_funds/views/ubercart_funds_handler_field_escrow_operations.inc
----------------------------------------------------------------------
FOUND 1 ERRORS AFFECTING 1 LINES
----------------------------------------------------------------------
 23 | ERROR | Public method name
    |       | "UcFundsHandlerFieldEscrowOperations::pre_render" is
    |       | not in lowerCamel format
----------------------------------------------------------------------

Time: 333ms; Memory: 18Mb

How can I fix that ?

avpaderno’s picture

A function/method name uses the lower camel format when it is, in your case, preRender().

aporie’s picture

Yes, but doing that breaks the handler ... As it is a function from the extended class.

avpaderno’s picture

If the class implementing that method is replacing a method another module's class defines, ignore the warning, since you cannot change the method name.

In this case, change the status to Needs review.

aporie’s picture

Ok, thanks.

aporie’s picture

Status: Needs work » Needs review
avpaderno’s picture

Assigned: Unassigned » avpaderno

PAReview still reports errors. The most important are the following are:

  • The permission for the callback on ubercart_funds.module (line 96) should be probably changed.
  • There are various not used variables.
  • In the installation file, neither t() nor st() should be used directly. The code should use $t = get_t(); and $t('string to translate') to get the translation of a string.

I will make a manual review later today.

aporie’s picture

Hi kiamlaluno,

Ok I will take a look to the PARreview errors.

Thanks for taking a look to the module after. I hope everything will work well, now it's been a while I didn't take a look at it I have doubt :) (But if I opt-in for security it's because I performed a lot of tests before).

Thanks again.

avpaderno’s picture

Status: Needs review » Needs work

The LICENSE.txt file needs to be removed.

  $variables = array(
    'ubercart_funds_commissions',
    'uc_funds_withdrawal_methods',
  );
  // Delete set variables.
  foreach ($variables as $variable) {
    variable_del($variable);
  }

The code is deleting a persistent variable used by another module, or the variable name is wrong and it should be ubercart_funds_withdrawal_methods.

  $schema['uc_funds_user_funds'] = array(
    'description' => 'User Funds',
    'fields' => array(
      'uid' => array(
        'description' => 'The user id of the fund.',
        'type' => 'int',
        'not null' => TRUE,
        'default' => 0,
      ),
      'balance' => array(
        'description' => 'The balance of the user.',
        'type' => 'int',
        'size' => 'big',
        'not null' => TRUE,
        'default' => 0,
      ),
    ),
    'primary key' => array(
      'uid',
    ),
  );

The database table names should be prefixed by the module name.

module_load_include('inc', 'ubercart_funds', 'ubercart_funds.rules');
module_load_include('inc', 'ubercart_funds', 'ubercart_funds.pages');
module_load_include('inc', 'ubercart_funds', 'ubercart_funds.tokens');

Those function calls should not be outside functions.

/**
 * Implements hook_block_info().
 */
function ubercart_funds_block_info() {
  $blocks['admin_site_balance'] = array(
    'info' => t('Site Balance'),
    'cache' => DRUPAL_NO_CACHE,
  );

  $blocks['admin_user_balances'] = array(
    'info' => t('User Balances'),
    'cache' => DRUPAL_NO_CACHE,
  );

  $blocks['account_balance'] = array(
    'info' => t('Account Balance'),
    'cache' => DRUPAL_NO_CACHE,
  );

  $blocks['funds_operations'] = array(
    'info' => t('Account Funds Operations'),
    'cache' => DRUPAL_NO_CACHE,
  );

  return $blocks;
}

Block machine names should be prefixed by the module name. The same is true for routers specific to the module (defined in hook_menu()), and theme functions (defined in hook_theme()). For theme functions, the template name must match the theme name, in Drupal 7. You cannot have site_balance as theme name and ubercart-funds-account-balance as template name.

  $items['user/funds/deposit'] = array(
    'title' => 'Deposit Funds',
    'page callback' => '_ubercart_funds_deposit_funds',
    'access arguments' => array('deposit funds'),
    'type' => MENU_CALLBACK,
  );

Instead of using a page callback that is calling drupal_get_form(), the route should use 'drupal_get_form' as page callback and array('ubercart_funds_deposit_funds') as page arguments.

/**
 * Definition of _ubercart_funds_get_site_balance().
 */
function _ubercart_funds_get_admin_user_balances() {
  if (arg(0) == 'user' && is_numeric(arg(1))) {
    $user_uid = arg(1);
    $user_balance = db_query("SELECT balance FROM uc_funds_user_funds WHERE uid = :uid", array(':uid' => $user_uid))->fetchAssoc();

    return theme('account_balance', array('uid' => $user_uid, 'balance' => $user_balance['balance']));
  }
  else {
    return FALSE;
  }
}

The documentation comment should be more descriptive than Definition of _ubercart_funds_get_site_balance(). which is just giving known information (the function name).

  else {
    drupal_not_found();
    drupal_drupal_exit()();
  }

What is wrong in that code?

/**
 * Definition of ubercart_funds_configure_fees_submit().
 */

There are standard documentation comments for submission and validation handlers.

  drupal_goto('cart/checkout');

That is not the way submission handlers do redirects.

  if (is_numeric($withdraw_amount) && !(floatval($withdraw_amount) > 0)) {
    form_set_error('amount', t('Value must be greater than 0.'));
    return FALSE;
  }

  if (!$user->data || !array_key_exists($method, $user->data)) {
    form_set_error('methods', t('Please %enter_details for this withdrawal method first.',
    array('%enter_details' => l(t('enter details'), 'user/funds/manage/withdrawal-methods/' . $method))));
    return FALSE;
  }

  $amount_plus_fees = ($withdraw_amount + $commission) * 100;

  if ($amount_plus_fees > $user_balance['balance']) {
    form_set_error('amount', t('You cannot withdraw %amount using this payment method. Commission is %commission.',
    array(
      '%amount' => $withdraw_amount,
      '%commission' => uc_currency_format($commission),
    )));
    return FALSE;
  }

Validation handlers don't return Boolean values; they just set an error message for the form or the form element. Validation handlers then don't return after the first error found.

  if (!is_numeric($escrow_amount)) {
    form_set_error('amount', t('Value must be Numeric'));
    return FALSE;
  }

  if (is_numeric($escrow_amount) && !(floatval($escrow_amount / 100) > 0)) {
    form_set_error('amount', t('Value must be greater than 0'));
    return FALSE;
  }

There isn't the need to give two different error messages (and duplicate code) when it's sufficient to use The value must be a positive integer. as error message.

  if ($user->name == $user_exist->name) {
    form_set_error('username', t('You cannot make an escrow payment to yourself.'));
    return FALSE;
  }

There isn't any need to compare usernames when it's enough to compare the user IDs.

  $transaction = new stdClass();
  $transaction->issuer = $issuer->uid;
  $transaction->type = 'Escrow Payment';
  $transaction->recipient = $recipient->uid;
  $transaction->created = time();
  $transaction->brut_amount = $escrow_amount;
  $transaction->commission = $commission;
  $transaction->net_amount = $escrow_after_commission;
  $transaction->status = 'Pending';
  $transaction->notes = $form_state['values']['notes'];

  // Update issuer balance to provision the funds.
  $issuer_balance = db_query("SELECT * FROM uc_funds_user_funds WHERE uid = :uid", array(':uid' => $issuer->uid))->fetchAssoc();
  $issuer_balance['balance'] -= $escrow_after_commission;
  drupal_write_record('uc_funds_user_funds', $issuer_balance, 'uid');

What happens if the uc_funds_user_funds table doesn't have records for the user?

  global $user;
  $uid = $user->uid;

  $transaction = entity_load_single('transaction', $transaction_id);

  $issuer = $transaction->issuer;
  $recipient = $transaction->recipient;

  if ($transaction->status !== "Completed") {
    if ($type == "cancel") {
      if ($uid === $issuer || $uid === $recipient) {
        return TRUE;
      }
    }
    if ($type == "release" && $uid === $issuer) {
      return TRUE;
    }
  }

Is it necessary to use strict comparisons?

aporie’s picture

Hi kiamlaluno,

Haven't forgotten you, I hope I'll have a bit of time this weekend to take a look to your feedbacks.

Thanks BTW for your review.

aporie’s picture

Hi kiamlaluno,

I've started working on the modifications, but have a lot to do on commenting correctly the code.

In the meanwhile, prefixing the blocks name with the module name return a php error : "String data, right truncated: 1406 Data too long for column 'delta' at row 1". I don't want to lose pieces of information on setting the delta name. The name is now : ubercart_funds_admin_site_balance for example. Any idea what I can do about it ?

aporie’s picture

Hello,

I have updated my code. I've done my best to do it correctly but this is my first drupal module (and I guess managing one is far enough).

I have named my blocks "uc_funds_BLOCK_NAME" to avoid the error above. I know we should prefix everything with the module name to avoid conflicts between modules, but when we have a module with a long name (as ubercart_funds) it often ends up with error (and mostly with coding standards), so I don't know what I can do about that ...

Some details to your feedbacks :

--> module_load_include is not the good function to load files in modules. I have used include_once but I'm still not sure of the proper way for doing that.
--> For prefixing block name with module name, see above.
--> What happens if the uc_funds_user_funds table doesn't have records for the user? In the case you shown it's not possible that the user doesn't have a balance record as it is the issuer. The form validation will return an error saying the user doesn't have enough funds to cover the transfer.

I know my code and comments can be improved a lot, but for now what I wanted is a secure module to allow user to make transfers together. If anybody has security advises for me, I'm totally open to them.

I still have some PAReview error and know it (please see comments above) :

FILE: ...eb/sites/all/modules/custom/ubercart_funds/ubercart_funds.module
----------------------------------------------------------------------
FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE
----------------------------------------------------------------------
 860 | WARNING | Line exceeds 80 characters; contains 86 characters
----------------------------------------------------------------------


FILE: ...nds/views/ubercart_funds_handler_field_withdrawal_operations.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 23 | ERROR | Public method name
    |       | "UbercartFundsHandlerFieldWithdrawalOperations::pre_render"
    |       | is not in lowerCamel format
----------------------------------------------------------------------


FILE: ...t_funds/views/ubercart_funds_handler_field_escrow_operations.inc
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
 23 | ERROR | Public method name
    |       | "UbercartFundsHandlerFieldEscrowOperations::pre_render"
    |       | is not in lowerCamel format
----------------------------------------------------------------------

Time: 325ms; Memory: 18Mb

aporie’s picture

Status: Needs work » Needs review
avpaderno’s picture

module_load_include() is the correct function, and it is used from Drupal core modules. You cannot use it outside functions, but the same is true for include and include_once, which could cause problems when a module file is included by Drupal core (especially in early bootstrap time).

What I meant is that drupal_write_record('uc_funds_user_funds', $issuer_balance, 'uid') only updates an existing record; it doesn't create one if it doesn't exist. If you need to either update an existing record or create it, db_merge() is the function for that task.

aporie’s picture

Hey,

There are two ways of creating a new balance record in the db for a user :

- If he makes a deposit (it's a rule because it's triggered after ubercart checkout and was the only way I found)

function _ubercart_funds_rules_update_account_balance($order) in rules.inc

  // Update account balance.
      $account_exist = db_query("SELECT * FROM ubercart_funds_user_funds WHERE uid = :uid", array(':uid' => $user->uid))->fetchAssoc();

      if (!$account_exist) {
        drupal_write_record('ubercart_funds_user_funds', $user_balance);
      }
      else {
        $user_balance->balance += $account_exist['balance'];
        drupal_write_record('ubercart_funds_user_funds', $user_balance, 'uid');
      }

- If another user transfer him money through a transfer (direct) or an escrow payment (indirect)

function ubercart_funds_transfer_funds_submit() & function ubercart_funds_release_escrow_payment_submit in .module

$recipient_balance_exist = db_query("SELECT * FROM ubercart_funds_user_funds WHERE uid = :uid", array(':uid' => $recipient->uid))->fetchAssoc();

  if (!$recipient_balance_exist) {
    $recipient_balance = new stdClass();
    $recipient_balance->uid = $recipient->uid;
    $recipient_balance->balance = $transfer_amount;
    drupal_write_record('ubercart_funds_user_funds', $recipient_balance);
  }

After that the user shouldn't be able to make any transaction (so nothing written in the db) and should be ditched out on the form validation step. So in a way it's good that the balance record is key dependent, no ?

In the case you shown it's for an escrow payment (but could happen also for a transfer). When the time comes to write in the db, the issuer (so the user who wants to transfer money) must have an account with money on it and must actually have enough money to cover it.

avpaderno’s picture

Status: Needs review » Reviewed & tested by the community

Thank you for the explanation.

I didn't see any security issue. I will approve the application later today.

aporie’s picture

Hey,

Thanks ! It's kind of exiting for me. Everything is new.

avpaderno’s picture

Status: Reviewed & tested by the community » Fixed

Thank you for your contribution!
I am going to update your account so you can opt into security advisory coverage now.
These are some recommended readings to help with excellent maintainership:

You can find more contributors chatting on the IRC #drupal-contribute channel. So, come hang out and stay involved.
Thank you, also, for your patience with the review process.
Anyone is welcome to participate in the review process. Please consider reviewing other projects that are pending review. I encourage you to learn more about that process and join the group of reviewers.

I thank all the dedicated reviewers as well.

avpaderno’s picture

I apologize for the delay. I planned to approve this application yesterday, but I didn't have time.

aporie’s picture

Hey,

No problem. Thanks you for the links to the documentation. Think it will answer a lot of my questions :)

Yeah if I have time I could give a hand on reviewing other modules, but for now I need to update myself on D8. That's also the point of this module, I'll port it on D8.

Thanks again.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.