Using Rules with Commerce Funds

Submitted by victor.bourgade on

When working with Commerce Funds you'll probably need at some point to combine it with Rules to provides you the flexibility you need for your website. 

As a reminder Rules allows you to "define conditionally executed actions based on occurring events". It means that you can build your actions triggered by specific chosen events. For example, you can create a new transaction after a node is saved and we are going to see in the article below how to do it for an escrow payment.

First, we create a new content type called "escrow payment" which will provide the form which will host the data for our escrow transaction.

We need 2 mandatory fields for the transaction: the amount of the transaction and the recipient user who the money will be sent to. For the amount field we are going to use the "Price" field type and for the user an "Entity Reference" field type as following:

At this stage, a user can fill the form with an amount and chose a user to send the money to, but no money transfer is executed. To actually perform the transaction when the form is submitted we'll need to add a new Rules on the Event "After saving a new content item" with the "Conditions" and the "Actions" detailed below.

Here is what you should have at the end:

 

 

 

 

 

 

 

 

 

Conditions

To expose our content type fields to Rules, we first need to put the condition "Entity is of bundle" with the values below:

  • entity = data_selector : node,
  • type = value : node,
  • bundle = value : content_type_machine_name (escrow_payment here).

This condition will allow us to use our newly created fields in Rules, so we can use them in our action "Create a new transaction".

Actions

The action part resides in three processes:

  1. Creating a new transaction by filling the different pieces of information required for it, with the information provided by the user when filling our content type form "escrow payment".
  2. Saving it to the database, so we keep a track of it.
  3. Performing the transaction, so the different user balances are updated accordingly.

1. A transaction is not as simple as an amount and a user to send the money to. It's a more complex object with other parameters such as the creation date, the issuer, the currency etc. To create a transaction, we need to provide Rules with these pieces of information.

Let's add the action "Create a new transaction" with the following values:

  • Transaction type = value : escrow (Commerce Funds provides 6 types of transaction you can use here: deposit, transfer, escrow, payment, withdrawal_request, conversion)
  • Issuer = data_selector : node.uid.target_id (this is the node author, the user creating the node)
  • Recipient = data_selector : node.field_to_user.target_id (the user inserted into our field field_to_user)
  • Payment method = value : internal
  • Brut amount = data_selector : node.field_amount.number (the amount entered by the user)
  • Net amount = value : empty (leave it empty as it will be filled up by the module after fee calculation)
  • Fee = value : empty||value. Here you can leave it empty to use the configuration set in Commerce Funds or override the fees with a fixed fee amount.
  • Currency = data_selector: node.field_amount.currency_code (The currency used by the amount field)
  • Status = value : Canceled (Case sensitive. You can set 3 different values here Pending, Completed, Canceled) depending on the transaction type.
  • Hash = data_selector : node.uuid.value. Depending on your module version you might not have this option as it has been introduced in Commerce Funds 1.2. What we do here is using the node UUID as transaction hash which will secure our transaction ID without creating any UUID collision as our transaction and node will still have two different UUIDs.

If you have a field for letting a note with your transaction, it can be added to the transaction by implementing two new actions "set a data value". It is a bit tedious, but Rules is still blocked at the second milestone and is not finished yet. So, I hope it will be easier in the future.

First, "set a data value":

  • Data = data_selector : entity.notes (it selects the notes field from the transaction)
  • Value = data_selector : node.body.value (we insert the body value as transaction note)

The problem doing that is that we don't have set a text format for the transaction notes, which means if there is some HTML in the body value it will be rendered as plain text. We need to add a second action.

Second, "set a data value":

  • Data = data_selector : entity.notes.format 
  • Value = data_selector : node.body.format 

Now we have prepared our transaction entity, we need to save it in the database.

2. Add another action "Save entity":

  • Entity = data_selector : entity (Transaction) (for example commerce_funds_transaction_created)
  • Force saving = 1 (to force the saving, 0 otherwise)

At this point, we have created a transaction entity, we have saved it into the database but this transaction doesn't perform any action on user balances. No money amount is deducted or added to user account balances. 

3. Add a new action "Perform transaction":

  • Transaction = data_selector : entity (Transaction) (for example commerce_funds_transaction_created)

This action will trigger the transaction and update user virtual balances. 

Form validation

The only issue we have using Rules to perform a Commerce Funds Transaction is that it doesn't provide any form validation before triggering it. Rules, even in Drupal 7, didn't natively provide form integration. We could add Rules Forms Support module to achieve that though in D7.

Our only solution here is to implement a hook validation in a custom module. If you don't know how to create a custom module for Drupal you can take a look here.

Commerce Funds implements two constraints for validation. Constraints allow us to set a specific validation set to fields. As our fields are created with the form API we need to apply our constraints with a hook_entity_bundle_field_info_alter().

In your module_name.module file insert these lines: 

/**
 * Implements hook_entity_bundle_field_info_alter().
 *
 * Alter custom form for validation.
 */
function MODULE_NAME_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type->id() == 'node' && $bundle == 'CONTENT_TYPE_MACHINE_NAME') {
    if (!empty($fields['FIELD_AMOUNT_MACHINE_NAME'])) {
      $fields['FIELD_AMOUNT_MACHINE_NAME']->addConstraint('NetAmountBelowBalance'); // Add the validation set "NetAmountBelowBalance"
    }
    if (!empty($fields['FIELD_RECIPIENT_MACHINE_NAME'])) {
      $fields['FIELD_RECIPIENT_MACHINE_NAME']->addConstraint('IssuerEqualsCurrentUser'); // Add the validation set "IssuerEqualsCurrentUser"
    }
  }
}

You'll need to replace the uppercase name with your specific names. In our case it will be:

/**
 * Implements hook_entity_bundle_field_info_alter().
 *
 * Alter custom form for validation.
 */
function MODULE_NAME_entity_bundle_field_info_alter(&$fields, \Drupal\Core\Entity\EntityTypeInterface $entity_type, $bundle) {
  if ($entity_type->id() == 'node' && $bundle == 'escrow_payment') {
    if (!empty($fields['field_amount'])) {
      $fields['field_amount']->addConstraint('NetAmountBelowBalance');
    }
    if (!empty($fields['field_to_user'])) {
      $fields['field_to_user']->addConstraint('IssuerEqualsCurrentUser');
    }
  }
}

If you have several content types using Rules for triggering transactions you just need to add the entire first "if" block again (if bundle == "OTHER_CONTENT_TYPE") and set your field names inside the other "if" conditions. 

By doing that you will obtain the form validations:

 

 

 

 

 

 

 

 

 

That's it, we have seen how to completely get rid of the provided forms from Commerce Funds and implement ours owns using the Rules module. If you have any question don't hesitate to use the comments section below this article.

Tags

Drupal 8 Drupal 9 Commerce Funds Rules

About the writer

victor.bourgade

Victor is a web developer passionnated in drupal and bootstrap technologies. He likes challenges and beautiful designs.

When not behind his computer you'll find him drinking beers with friends or in the middle of nowhere hiking with his dog.