artfulrobot / uk.artfulrobot.civicrm.gocardless

A CiviCRM extension providing GoCardless integration to handle UK Direct Debits.
GNU Affero General Public License v3.0
5 stars 18 forks source link

Reference CiviCRM contact ID in GoCardless UI #79

Closed michaelmcandrew closed 3 years ago

michaelmcandrew commented 4 years ago

We inherited a client site that was running a customised version of GoCardless 1.7. One of the customisations was to enable recording of the CiviCRM contact ID in the GoCardless UI, which the client found useful. We're now upgrading to a non customised version of 1.9 but would quite like to add the feature to record CivICRM IDs in GoCardless since it seems useful for others as well, I'm thinking that this might be a useful addition to the extension.

Rich says:

definitely. GC allows up to 3 (from memory) key:values of metadata for subscriptions. I was thinking one of those keys should be civicrm and the data should be a JSON object with {contactId:123, contributionRecurId: 345}. it would live here: https://github.com/artfulrobot/uk.artfulrobot.civicrm.gocardless/blob/master/CRM/GoCardlessUtils.php#L216

I took a quick look and it wasn't clear to me how to access the contact or contribution recur id in ::completeRedirectFlowWithGoCardless().

Some background on the motivation.

I think that the code was necessary for the client because they were transferring direct debits from an old provider and the contact ID was useful to cross check which mandates were in GoCardless already.

In my experience with Stripe it is handy for debugging in development (and also when things go wrong in production).

For reference, in case it is useful, here is the (AGPL) code that had been implemented to update the mandate in the customised extension. Important to note that this code was broken and not actually doing what they wanted it to do (I think the reference to worldpay is some copypasta).

```diff
diff --git b/src/extensions/uk.artfulrobot.civicrm.gocardless/gocardless.php a/src/extensions/uk.artfulrobot.civicrm.gocardless/gocardless.php
index dde7b57..5d841be 100644
--- b/src/extensions/uk.artfulrobot.civicrm.gocardless/gocardless.php
+++ a/src/extensions/uk.artfulrobot.civicrm.gocardless/gocardless.php
@@ -221,6 +221,52 @@ function gocardless_civicrm_buildForm( $formName, &$form ) {
   catch (Exception $e) {
     CRM_Core_Error::fatal('Sorry there was an error setting up your Direct Debit. Please contact us so we can look into what went wrong.');
   }
+  
+  //MTL - START
+  //update the contact ID, membership ID and contribution ID against Worldpay 
+  //update the mandate with custom ID - easy to update this way 
+
+  //update mandate 
+  try{
+       $mandateID = $result['subscription']->links->mandate; 
+       
+       if ($mandateID){
+               $result1 = civicrm_api3('Gocardless', 'updatemandate', array(
+                                               'sequential' => 1,
+                                               'mandate' => (string)$mandateID,
+                                               'contactID' => (string)$params['contactID'],
+                                               'membershipID' => (string)$params['membershipID'],
+                                               'contributionID' => (string)$params['contributionID']
+                                       ));
+       }
+   
+       //update subscription  
+       $subscriptionID = $result['subscription']->id; 
+       if ($subscriptionID){
+               
+               $result2 = civicrm_api3('Gocardless', 'updatesubscription', array(
+                                               'sequential' => 1,
+                                               'subscription' => (string)$subscriptionID,
+                                               'contributionRecurID' => (string)$params['contributionRecurID'],
+                                               'membershipID' => (string)$params['membershipID'],
+                                               'contributionID' => (string)$params['contributionID']
+                                       ));     
+       }
+  
+       //update subscription  
+       $customerID = $result1['values']->links->customer; 
+       if ($customerID){
+               $result3 = civicrm_api3('Gocardless', 'updatecustomer', array(
+                                               'sequential' => 1,
+                                               'customers' => (string)$customerID,
+                                               'contactID' => (string)$params['contactID'],
+                                               ));
+  
+       }
+  } catch (Exception $e) {
+       CRM_Core_Error::debug_log_message( 'ERROR = '. print_r('Details did not update correctly ',true), $out = false );
+  }
+  //MTL - END
 }
<?php

/**
 * Gocardless.Updatemandate API specification (optional)
 * This is used for documentation and validation.
 *
 * @param array $spec description of fields supported by this API call
 * @return void
 * @see http://wiki.civicrm.org/confluence/display/CRMDOC/API+Architecture+Standards
 */
function _civicrm_api3_gocardless_Updatemandate_spec(&$spec) {
  $spec['magicword']['api.required'] = 0;

}

/**
 * Gocardless.Updatemandate API
 *
 * @param array $params
 * @return array API result descriptor
 * @see civicrm_api3_create_success
 * @see civicrm_api3_create_error
 * @throws API_Exception
 */
function civicrm_api3_gocardless_Updatemandate($params) {

    //Now create the subscription against Gocardless 
    require 'vendor/autoload.php';
    $client = CRM_GoCardlessUtils::getApi(APIMODE); 

    $return = $client->mandates()->update($params['mandate'], [
                            "params" => ['metadata' => ['civicrmID' => $params['contactID'],
                                                       'membershipID' => $params['membershipID'],
                                                       'contributionID' => $params['contributionID']
                                                        ]
                            ]]); 

    return civicrm_api3_create_success($return, $params, 'Gocardless', 'Updatemandate');

  if (array_key_exists('magicword', $params) && $params['magicword'] == 'sesame') {
    $returnValues = array( // OK, return several data rows
      12 => array('id' => 12, 'name' => 'Twelve'),
      34 => array('id' => 34, 'name' => 'Thirty four'),
      56 => array('id' => 56, 'name' => 'Fifty six'),
    );
    // ALTERNATIVE: $returnValues = array(); // OK, success
    // ALTERNATIVE: $returnValues = array("Some value"); // OK, return a single value

    // Spec: civicrm_api3_create_success($values = 1, $params = array(), $entity = NULL, $action = NULL)
    return civicrm_api3_create_success($returnValues, $params, 'NewEntity', 'NewAction');
  } else {
    throw new API_Exception(/*errorMessage*/ 'Everyone knows that the magicword is "sesame"', /*errorCode*/ 1234);
  }
}
<?php

/**
 * Gocardless.Updatesubscription API specification (optional)
 * This is used for documentation and validation.
 *
 * @param array $spec description of fields supported by this API call
 * @return void
 * @see http://wiki.civicrm.org/confluence/display/CRMDOC/API+Architecture+Standards
 */
function _civicrm_api3_gocardless_Updatesubscription_spec(&$spec) {
  $spec['magicword']['api.required'] = 0;
}

/**
 * Gocardless.Updatesubscription API
 *
 * @param array $params
 * @return array API result descriptor
 * @see civicrm_api3_create_success
 * @see civicrm_api3_create_error
 * @throws API_Exception
 */
function civicrm_api3_gocardless_Updatesubscription($params) {

    //Now create the subscription against Gocardless 
    require 'vendor/autoload.php';
    $client = CRM_GoCardlessUtils::getApi(APIMODE); 

    $sql = "SELECT * FROM civicrm_value_gocardless_update_payment";
    $dao = CRM_Core_DAO::executeQuery( $sql);

    while ($dao->fetch()){

        $entity_id = $dao->entity_id;

        $sql_details = "select * from civicrm_contribution_recur 
                        where id = {$entity_id}";

        $dao_details = CRM_Core_DAO::executeQuery( $sql_details);   

        if ($dao_details->fetch()){
            $subscription_id = $dao_details->trxn_id;
            $amount = $dao_details->amount * 100;

            if ($subscription_id){
                $result = $client->subscriptions()->update($subscription_id, [
                    "params" => ["amount" => "{$amount}",
                            ]]);

                $subscription_id = '';

                //delete the table if we have updated the record correctly 
                $sql_del = "Delete from civicrm_value_gocardless_update_payment where entity_id = ".$entity_id;
                CRM_Core_DAO::executeQuery ( $sql_del );

            }

        }
    }

    return civicrm_api3_create_success($return, $params, 'Gocardless', 'Updatesubscription');

  if (array_key_exists('magicword', $params) && $params['magicword'] == 'sesame') {
    $returnValues = array( // OK, return several data rows
      12 => array('id' => 12, 'name' => 'Twelve'),
      34 => array('id' => 34, 'name' => 'Thirty four'),
      56 => array('id' => 56, 'name' => 'Fifty six'),
    );
    // ALTERNATIVE: $returnValues = array(); // OK, success
    // ALTERNATIVE: $returnValues = array("Some value"); // OK, return a single value

    // Spec: civicrm_api3_create_success($values = 1, $params = array(), $entity = NULL, $action = NULL)
    return civicrm_api3_create_success($returnValues, $params, 'NewEntity', 'NewAction');
  } else {
    throw new API_Exception(/*errorMessage*/ 'Everyone knows that the magicword is "sesame"', /*errorCode*/ 1234);
  }
}
<?php

/**
 * Gocardless.Updatecustomer API specification (optional)
 * This is used for documentation and validation.
 *
 * @param array $spec description of fields supported by this API call
 * @return void
 * @see http://wiki.civicrm.org/confluence/display/CRMDOC/API+Architecture+Standards
 */
function _civicrm_api3_gocardless_Updatecustomer_spec(&$spec) {
  $spec['magicword']['api.required'] = 0;
}

/**
 * Gocardless.Updatecustomer API
 *
 * @param array $params
 * @return array API result descriptor
 * @see civicrm_api3_create_success
 * @see civicrm_api3_create_error
 * @throws API_Exception
 */
function civicrm_api3_gocardless_Updatecustomer($params) {

    //Now create the subscription against Gocardless 
    require 'vendor/autoload.php';
    $client = CRM_GoCardlessUtils::getApi(APIMODE); 

    //CRM_Core_Error::debug_log_message( 'client = '. print_r($client,true), $out = false );

    $return = $client->customers()->update($params['customers'], [
                            "params" => ['metadata' => ['civicrm_ID' => $params['contactID']
                                                       ]]]); 

    return civicrm_api3_create_success('OK', $params, 'Gocardless', 'Updatecustomer');

  if (array_key_exists('magicword', $params) && $params['magicword'] == 'sesame') {
    $returnValues = array( // OK, return several data rows
      12 => array('id' => 12, 'name' => 'Twelve'),
      34 => array('id' => 34, 'name' => 'Thirty four'),
      56 => array('id' => 56, 'name' => 'Fifty six'),
    );
    // ALTERNATIVE: $returnValues = array(); // OK, success
    // ALTERNATIVE: $returnValues = array("Some value"); // OK, return a single value

    // Spec: civicrm_api3_create_success($values = 1, $params = array(), $entity = NULL, $action = NULL)
    return civicrm_api3_create_success($returnValues, $params, 'NewEntity', 'NewAction');
  } else {
    throw new API_Exception(/*errorMessage*/ 'Everyone knows that the magicword is "sesame"', /*errorCode*/ 1234);
  }
}
wmortada commented 4 years ago

It was definitely helpful to see the CiviCRM ids in GoCardless when we were first testing the integration.

artfulrobot commented 4 years ago

Metadata is supported by GC on

example reference:

Key-value store of custom data. Up to 3 keys are permitted, with key names up to 50 characters and values up to 500 characters.

Because of the 3 key limit, I propose to use the key civicrm and then we have 500 characters to use for a JSON object that would include:

Currently we don't track mandates directly in CiviCRM, but we do store subscription IDs (contributionRecur.processor_id) and payment IDs (contribution.trxn_id).

I think a sensible proposal would be:

Thoughts?

wmortada commented 4 years ago

That sounds sensible to me. I don't think there is a need to store data on the individual payments.

I'm in two minds about storing it as JSON though. I think this could make it less easy for non-technical users to understand. I guess I'd need to see how it looked in practice within GoCardless.

artfulrobot commented 4 years ago

@wmortada if we use all three fields up, we've not left any room for future changes. I think JSON's pretty readable- I know I'm technical, but it only adds a few " and {} to pretty standard english construct like colour: red, height: tall People who are looking at this are already aware of some pretty technical stuff (contact IDs for example).

wmortada commented 4 years ago

Yes, you are probably right. I don't have a strong feeling about it.

artfulrobot commented 4 years ago

Cheers for chipping in though @wmortada - always appreciated!