From eebcb4d252283711adba385f6b2251fcfa7ed6c9 Mon Sep 17 00:00:00 2001 From: Jamie McClelland <jm@mayfirst.org> Date: Fri, 26 Feb 2021 16:53:37 -0500 Subject: [PATCH] refactor and properly display invoices queried from stripe. --- api/v3/Stripe/Listevents.php | 132 ++++++++++++++++++++++++++--------- 1 file changed, 99 insertions(+), 33 deletions(-) diff --git a/api/v3/Stripe/Listevents.php b/api/v3/Stripe/Listevents.php index 0f85e389..d4e1c891 100644 --- a/api/v3/Stripe/Listevents.php +++ b/api/v3/Stripe/Listevents.php @@ -255,24 +255,16 @@ function civicrm_api3_stripe_Listevents($params) { $filter_processed = $parsed['filter_processed']; $subscription = $parsed['subscription']; - $args = []; - if ($type) { - $args['type'] = $type; - } - if ($created) { - $args['created'] = $created; - } - if ($limit) { - $args['limit'] = $limit; - } - if ($starting_after) { - $args['starting_after'] = $starting_after; - } - + // $data_list will contain all the values we will return to the user. $data_list = [ 'data' => [] ]; + + // Search by a single Stripe subscription id. if ($subscription) { - // If we are searching by subscription, we ignore source. - $sql = 'SELECT id, context FROM civicrm_system_log WHERE message = %0 AND context LIKE %1 AND context LIKE %2 ORDER BY timestamp DESC limit %3'; + // If we are searching by subscription, we ignore source because we will + // search both the system log and we will query Stripe to get a complete + // list. + $sql = 'SELECT id, context FROM civicrm_system_log WHERE message = %0 AND + context LIKE %1 AND context LIKE %2 ORDER BY timestamp DESC limit %3'; $sql_params = [ 0 => [ 'payment_notification processor_id=' . $params['ppid'], 'String' ], 1 => [ '%' . $type . '%', 'String' ], @@ -281,20 +273,68 @@ function civicrm_api3_stripe_Listevents($params) { ]; $dao = CRM_Core_DAO::executeQuery($sql, $sql_params); + $seen_charges = []; while($dao->fetch()) { $data = civicrm_api3_stripe_listevents_massage_systemlog_json($dao->context); + if (in_array($data['data']['object']->charge, $seen_charges)) { + // We might get more then one event for a single charge if the first attempt fails. We + // don't need to list them all. + continue; + } + $seen_charges[] = $data['data']['object']->charge; $data['system_log_id'] = $dao->id; - $data_list['data'][] = $data; + + // Add this charge to the list to return. Index by timestamp so we can + // sort them chronologically later. + $data_list['data'][$data->created] = $data; } + // Now query stripe directly to see if there are any that system log didn't record. + $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $params['ppid']])); + $processor->setAPIParams(); + $invoices = $processor->stripeClient->invoices->all(['subscription' => $subscription]); + $seen_invoices = []; + foreach($invoices['data'] as $invoice) { + if ($invoice->charge) { + // We get a lot of repeats - for example, one item for the creation of the subscription + // and another for the initial charge. We only want one record per charge. + if (!in_array($invoice->charge, $seen_charges)) { + // If we already have this charge from system log, we don't need it again. + if (!in_array($invoice->charge, $seen_invoices)) { + // This means a charge was made that was not included in the system log. + $data_list['data'][$invoice->created] = $invoice; + $seen_invoices[] = $invoice->charge; + } + } + } + } + // Since we're combining data from the system log and from stripe, it may be + // out of chronological order. + asort($data_list); } + + // Query the last month of Stripe events. elseif ($source == 'stripe') { $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $params['ppid']])); $processor->setAPIParams(); + $args = []; + if ($type) { + $args['type'] = $type; + } + if ($created) { + $args['created'] = $created; + } + if ($limit) { + $args['limit'] = $limit; + } + if ($starting_after) { + $args['starting_after'] = $starting_after; + } $data_list = \Stripe\Event::all($args); } + + // Query the system log. else { - // source is systemlog. $sql = 'SELECT id, context FROM civicrm_system_log WHERE message = %0 AND context LIKE %1 ORDER BY timestamp DESC limit %2'; $sql_params = [ 0 => [ 'payment_notification processor_id=' . $params['ppid'], 'String' ], @@ -309,6 +349,7 @@ function civicrm_api3_stripe_Listevents($params) { $data_list['data'][] = $data; } } + $out = $data_list; if ($params['output'] == 'brief') { $out = []; @@ -323,37 +364,62 @@ function civicrm_api3_stripe_Listevents($params) { $item['pending_webhooks'] = $data['pending_webhooks']; $item['type'] = $data['type']; + $invoice = NULL; + $charge = NULL; + $customer = NULL; + $subscription = NULL; + $total = NULL; if (preg_match('/invoice\.payment_/', $data['type'])) { - $item['invoice'] = $data['data']['object']->id; - $item['charge'] = $data['data']['object']->charge; - $item['customer'] = $data['data']['object']->customer; - $item['subscription'] = $data['data']['object']->subscription; - $item['total'] = $data['data']['object']->total; + $invoice = $data['data']['object']->id; + $charge = $data['data']['object']->charge; + $customer = $data['data']['object']->customer; + $subscription = $data['data']['object']->subscription; + $total = $data['data']['object']->total; + } + elseif($data['object'] == 'invoice') { + $invoice = $data->id; + $charge = $data->charge; + $customer = $data->customer; + $subscription = $data->subscription; + $total = $data->total; + } + + $item['invoice'] = $invoice; + $item['charge'] = $charge; + $item['customer'] = $customer; + $item['subscription'] = $subscription; + $item['total'] = $total; - // We will populate several additional fields based on whether any - // of this data has been entered into CiviCRM. - $item['contact_id'] = NULL; - $item['contribution_recur_id'] = NULL; - $item['contribution_recur_status_id'] = NULL; - $item['contribution_id'] = NULL; - $item['contribution_status_id'] = NULL; - $item['processed'] = 'no'; + // We will populate several additional fields based on whether any + // of this data has been entered into CiviCRM. + $item['contact_id'] = NULL; + $item['contribution_recur_id'] = NULL; + $item['contribution_recur_status_id'] = NULL; + $item['contribution_id'] = NULL; + $item['contribution_status_id'] = NULL; + $item['processed'] = 'no'; + if ($customer) { // Check if the customer is in the stripe customer table. - $results = civicrm_api3('StripeCustomer', 'get', [ 'id' => $data['data']['object']->customer]); + $results = civicrm_api3('StripeCustomer', 'get', [ 'id' => $customer]); if ($results['count'] == 1) { $value = array_pop($results['values']); $item['contact_id'] = $value['contact_id']; } + } + + if ($subscription) { // Check if recurring contribution can be found. - $results = civicrm_api3('ContributionRecur', 'get', ['trxn_id' => $item['subscription']]); + $results = civicrm_api3('ContributionRecur', 'get', ['trxn_id' => $subscription]); if ($results['count'] > 0) { $item['contribution_recur_id'] = $results['id']; $contribution_recur = array_pop($results['values']); $status_id = $contribution_recur['contribution_status_id']; $item['contribution_recur_status_id'] = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $status_id); } + } + if ($charge && $invoice) { // Check if charge is in the contributions table. $contribution = NULL; $results = \Civi\Api4\Contribution::get() -- GitLab