Skip to content
Snippets Groups Projects
Commit 27f18cc2 authored by mattwire's avatar mattwire
Browse files

Add API3 Stripe.Populatewebhookqueue and fix Stripe.Populatelog

parent d8f4104a
Branches
Tags
1 merge request!165Add API3 Stripe.Populatewebhookqueue and fix Stripe.Populatelog
......@@ -29,7 +29,7 @@ function _civicrm_api3_stripe_ListEvents_spec(&$spec) {
$spec['ppid']['api.required'] = TRUE;
$spec['type']['title'] = ts("Limit to the given Stripe events type, defaults to invoice.payment_succeeded.");
$spec['type']['api.default'] = 'invoice.payment_succeeded';
$spec['limit']['title'] = ts("Limit number of results returned (100 is max)");
$spec['limit']['title'] = ts("Limit number of results returned (100 is max, 25 default)");
$spec['starting_after']['title'] = ts("Only return results after this id.");
$spec['output']['api.default'] = 'brief';
$spec['output']['title'] = ts("How to format the output, brief or raw. Defaults to brief.");
......@@ -227,7 +227,7 @@ function civicrm_api3_stripe_ProcessParams($params) {
* as actual objects. To try to mimic the same results, we don't covert the entire
* json string to an array, only the data index, leaving the rest as objects.
*/
function civicrm_api3_stripe_listevents_massage_systemlog_json($data) {
function _civicrm_api3_stripe_listevents_massage_systemlog_json($data) {
// Decode from json, ensure top layer is an array.
$data = (array) json_decode($data);
// Also ensure the data layer is an array.
......@@ -275,7 +275,7 @@ 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);
$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.
......@@ -301,7 +301,7 @@ function civicrm_api3_stripe_Listevents($params) {
// 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)) {
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;
......@@ -345,7 +345,7 @@ 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);
$data = _civicrm_api3_stripe_listevents_massage_systemlog_json($dao->context);
$charge = $data['data']['object']->charge;
if ($charge && in_array($charge, $seen_charges)) {
continue;
......@@ -377,7 +377,7 @@ function civicrm_api3_stripe_Listevents($params) {
$item['livemode'] = $data['livemode'];
$item['pending_webhooks'] = $data['pending_webhooks'];
$item['type'] = $data['type'];
$invoice = NULL;
$charge = NULL;
$customer = NULL;
......@@ -389,7 +389,7 @@ function civicrm_api3_stripe_Listevents($params) {
$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;
......
......@@ -26,6 +26,8 @@
*/
function _civicrm_api3_stripe_Populatelog_spec(&$spec) {
$spec['ppid']['title'] = ts("The id of the payment processor.");
$spec['type']['title'] = ts("The event type - defaults to invoice.payment_succeeded.");
$spec['type']['api.default'] = 'invoice.payment_succeeded';
}
/**
......@@ -38,53 +40,53 @@ function _civicrm_api3_stripe_Populatelog_spec(&$spec) {
* @throws \CiviCRM_API3_Exception
*/
function civicrm_api3_stripe_Populatelog($params) {
$ppid = NULL;
if (array_key_exists('ppid', $params)) {
$ppid = $params['ppid'];
}
else {
// By default, select the live stripe processor (we expect there to be
// only one).
$query_params = ['class_name' => 'Payment_Stripe', 'is_test' => 0, 'return' => 'id'];
try {
$ppid = civicrm_api3('PaymentProcessor', 'getvalue', $params);
}
catch (CiviCRM_API3_Exception $e) {
if (!$params['ppid']) {
// By default, select the live stripe processor (we expect there to be only one).
$paymentProcessors = \Civi\Api4\PaymentProcessor::get(FALSE)
->addWhere('is_active', '=', TRUE)
->addWhere('is_test', '=', FALSE)
->addWhere('payment_processor_type_id:name', '=', 'Stripe')
->execute();
if ($paymentProcessors->rowCount !== 1) {
throw new API_Exception("Expected one live Stripe payment processor, but found none or more than one. Please specify ppid=.", 2234);
}
else {
$params['ppid'] = $paymentProcessors->first()['id'];
}
}
$params = ['limit' => 100, 'type' => 'invoice.payment_succeeded'];
if ($ppid) {
$params['ppid'] = $ppid;
}
$listEventsParams['limit'] = 100;
$listEventsParams['ppid'] = $params['ppid'];
$items = [];
$last_item = NULL;
$more = TRUE;
while(1) {
if ($last_item) {
$params['starting_after'] = $last_item->id;
$listEventsParams['starting_after'] = $last_item['id'];
}
$objects = civicrm_api3('Stripe', 'Listevents', $params);
$events = civicrm_api3('Stripe', 'Listevents', $listEventsParams)['values'];
if (count($objects['values']['data']) == 0) {
if (count($events) == 0) {
// No more!
break;
}
$items = array_merge($items, $objects['values']['data']);
$last_item = end($objects['values']['data']);
$items = array_merge($items, $events);
$last_item = end($events);
// Support the standard API3 limit clause
if (isset($params['options']['limit']) && $params['options']['limit'] > 0 && count($items) >= $params['options']['limit']) {
break;
}
}
$results = [];
foreach($items as $item) {
$id = $item->id;
$id = $item['id'];
// Insert into System Log if it doesn't exist.
$like_event_id = '%event_id=' . addslashes($id);
$sql = "SELECT id FROM civicrm_system_log WHERE message LIKE '$like_event_id'";
$dao= CRM_Core_DAO::executeQuery($sql);
if ($dao->N == 0) {
$message = "payment_notification processor_id=${ppid} event_id=${id}";
$contact_id = civicrm_api3_stripe_cid_for_trxn($item->data->object->charge);
$message = "payment_notification processor_id=${params['ppid']} event_id=${id}";
$contact_id = _civicrm_api3_stripe_cid_for_trxn($item['charge']);
if ($contact_id) {
$item['contact_id'] = $contact_id;
}
......@@ -94,11 +96,21 @@ function civicrm_api3_stripe_Populatelog($params) {
}
}
return civicrm_api3_create_success($results);
}
function civcrm_api3_stripe_cid_for_trxn($trxn) {
$params = ['trxn_id' => $trxn, 'return' => 'contact_id'];
$result = civicrm_api3('Contribution', 'getvalue', $params);
return $result;
/**
* @param string $trxn
* Stripe charge ID
*
* @return int|null
*/
function _civicrm_api3_stripe_cid_for_trxn($trxn) {
try {
$params = ['trxn_id' => $trxn, 'return' => 'contact_id'];
$result = (int)civicrm_api3('Contribution', 'getvalue', $params);
return $result;
}
catch (Exception $e) {
return NULL;
}
}
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/
/**
* Populate the CiviCRM civicrm_paymentprocessor_webhook with Stripe events.
*
* This api will take all stripe events known to Stripe that are of the type
* invoice.payment_succeeded and add them * to the civicrm_paymentprocessor_webhook table.
* It will not add an event that has already been added, so it can be run multiple times.
* Once added, they will be automatically processed by the Job.process_paymentprocessor_webhooks api call.
*/
use Civi\Api4\PaymentProcessor;
use Civi\Api4\PaymentprocessorWebhook;
/**
* Stripe.Populatewebhookqueue API specification
*
* @param array $spec description of fields supported by this API call
*/
function _civicrm_api3_stripe_Populatewebhookqueue_spec(&$spec) {
$spec['ppid']['title'] = ts("The id of the payment processor.");
$spec['type']['title'] = ts("The event type - defaults to invoice.payment_succeeded.");
$spec['type']['api.default'] = 'invoice.payment_succeeded';
}
/**
* Stripe.Populatewebhookqueue API
*
* @param array $params
*
* @return array API result descriptor
* @throws \API_Exception
* @throws \CiviCRM_API3_Exception
*/
function civicrm_api3_stripe_Populatewebhookqueue($params) {
if (!$params['ppid']) {
// By default, select the live stripe processor (we expect there to be only one).
$paymentProcessors = PaymentProcessor::get(FALSE)
->addWhere('is_active', '=', TRUE)
->addWhere('is_test', '=', FALSE)
->addWhere('payment_processor_type_id:name', '=', 'Stripe')
->execute();
if ($paymentProcessors->rowCount !== 1) {
throw new API_Exception("Expected one live Stripe payment processor, but found none or more than one. Please specify ppid=.", 2234);
}
else {
$params['ppid'] = $paymentProcessors->first()['id'];
}
}
$listEventsParams['limit'] = 100;
$listEventsParams['ppid'] = $params['ppid'];
$items = [];
$last_item = NULL;
while(1) {
if ($last_item) {
$listEventsParams['starting_after'] = $last_item['id'];
}
$events = civicrm_api3('Stripe', 'Listevents', $listEventsParams)['values'];
if (count($events) == 0) {
// No more!
break;
}
$items = array_merge($items, $events);
$last_item = end($events);
// Support the standard API3 limit clause
if (isset($params['options']['limit']) && $params['options']['limit'] > 0 && count($items) >= $params['options']['limit']) {
break;
}
}
$results = [];
$eventIDs = CRM_Utils_Array::collect('id', $items);
$paymentprocessorWebhookEventIDs = PaymentprocessorWebhook::get(FALSE)
->addWhere('payment_processor_id', '=', $params['ppid'])
->addWhere('event_id', 'IN', $eventIDs)
->execute()->column('event_id');
$missingEventIDs = array_diff($eventIDs, $paymentprocessorWebhookEventIDs);
foreach($items as $item) {
if (!in_array($item['id'], $missingEventIDs)) {
continue;
}
$webhookUniqueIdentifier = ($item['charge'] ?? '') . ':' . ($item['invoice'] ?? '') . ':' . ($item['subscription'] ?? '');
PaymentprocessorWebhook::create(FALSE)
->addValue('payment_processor_id', $params['ppid'])
->addValue('trigger', $item['type'])
->addValue('identifier', $webhookUniqueIdentifier)
->addValue('event_id', $item['id'])
->execute();
$results[] = $item['id'];
}
return civicrm_api3_create_success($results);
}
......@@ -6,30 +6,54 @@ The api commands are:
## Stripe
* `Stripe.Listevents`: Events are the notifications that Stripe sends to the Webhook. Listevents will list all notifications that have been sent. You can further restrict them with the following parameters:
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `type` - Limit to the given Stripe events type. By default, show invoice.payment_succeeded. Change to 'all' to show all.
* `output` - What information to show. Defaults to 'brief' which provides a summary. Alternatively use raw to get the raw JSON returned by Stripe.
* `limit` - Limit number of results returned (100 is max, 10 is default).
* `starting_after` - Only return results after this event id. This can be used for paging purposes - if you want to retreive more than 100 results.
* `source` - By default, source is set to "stripe" and limited to events reported by Stripe in the last 30 days. If instead you specify "systemlog" you can query the `civicrm_system_log` table for events, which potentially go back farther then 30 days.
* `subscription` - If you specify a subscription id, results will be limited to events tied to the given subscription id. Furthermore, both the `civicrm_system_log` table will be queried and the results will be supplemented by a list of expected charges based on querying Stripe, allowing you to easily find missing charges for a given subscription.
* `filter_processed` - Set to 1 if you want to filter out results for contributions that have been properly processed by CiviCRM already.
* `Stripe.Populatelog`: If you are running a version of CiviCRM that supports the SystemLog - then this API call will populate your SystemLog with all of your past Stripe Events. You can safely re-run and not create duplicates. With a populated SystemLog - you can selectively replay events that may have caused errors the first time or otherwise not been properly recorded. Parameters:
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `Stripe.Ipn`: Replay a given Stripe Event. Parameters. This will always fetch the chosen Event from Stripe before replaying.
* `id` - The id from the SystemLog of the event to replay.
* `evtid` - The Event ID as provided by Stripe.
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `noreceipt` - Set to 1 if you want to suppress the generation of receipts or set to 0 or leave out to send receipts normally.
* `Stripe.Retryall`: Attempt to replay all charges for a given payment processor that are completed in Stripe but not completed in CiviCRM.
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `limit` - Limit number of results (25 is default).
* `Stripe.Cleanup`: Cleanup and remove old database tables/fields that are no longer required.
#### `Stripe.Listevents`:
Events are the notifications that Stripe sends to the Webhook. Listevents will list all notifications that have been sent. You can further restrict them with the following parameters:
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `type` - Limit to the given Stripe events type. By default, show invoice.payment_succeeded. Change to 'all' to show all.
* `output` - What information to show. Defaults to 'brief' which provides a summary. Alternatively use raw to get the raw JSON returned by Stripe.
* `limit` - Limit number of results returned (100 is max, 10 is default).
* `starting_after` - Only return results after this event id. This can be used for paging purposes - if you want to retreive more than 100 results.
* `source` - By default, source is set to "stripe" and limited to events reported by Stripe in the last 30 days. If instead you specify "systemlog" you can query the `civicrm_system_log` table for events, which potentially go back farther then 30 days.
* `subscription` - If you specify a subscription id, results will be limited to events tied to the given subscription id. Furthermore, both the `civicrm_system_log` table will be queried and the results will be supplemented by a list of expected charges based on querying Stripe, allowing you to easily find missing charges for a given subscription.
* `filter_processed` - Set to 1 if you want to filter out results for contributions that have been properly processed by CiviCRM already.
#### `Stripe.Populatelog`:
This API call will populate your SystemLog with all of your past Stripe Events. You can safely re-run and not create duplicates. With a populated SystemLog - you can selectively replay events that may have caused errors the first time or otherwise not been properly recorded. Parameters:
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `type` - The event type - defaults to invoice.payment_succeeded.
The standard API3 "limit" option is also supported and if specified will limit the total number of events to that limit (default 0).
#### `Stripe.Populatewebhookqueue`:
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `type` - The event type - defaults to invoice.payment_succeeded.
The standard API3 "limit" option is also supported and if specified will limit the total number of events to that limit (default 0).
#### `Stripe.Ipn`:
Replay a given Stripe Event. Parameters. This will always fetch the chosen Event from Stripe before replaying.
* `id` - The id from the SystemLog of the event to replay.
* `evtid` - The Event ID as provided by Stripe.
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `noreceipt` - Set to 1 if you want to suppress the generation of receipts or set to 0 or leave out to send receipts normally.
#### `Stripe.Retryall`:
Attempt to replay all charges for a given payment processor that are completed in Stripe but not completed in CiviCRM.
* `ppid` - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* `limit` - Limit number of results (25 is default).
#### `Stripe.Cleanup`:
Cleanup and remove old database tables/fields that are no longer required.
### Import related.
......@@ -65,7 +89,7 @@ When importing subscriptions, all invoice/charges assigned to the subscription w
* [Stripe doc](https://stripe.com/docs/api/subscriptions/list)
#### Customers
#### Customers
* `Stripe.importallcustomers` - import all customers for a given payment processor.
* Parameters:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment