diff --git a/api/v3/Stripe/Listevents.php b/api/v3/Stripe/Listevents.php index 0b8bc31c655d9411dcf8285dffa5d9b333f39d3c..17002361e04995276b96f4563b970b6f2372a297 100644 --- a/api/v3/Stripe/Listevents.php +++ b/api/v3/Stripe/Listevents.php @@ -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; diff --git a/api/v3/Stripe/Populatelog.php b/api/v3/Stripe/Populatelog.php index fe6e1549c77b225af7e28e63e9c0f6e7da45cef6..1f6faf320aca9b9e21a2cf1798449aeef099109f 100644 --- a/api/v3/Stripe/Populatelog.php +++ b/api/v3/Stripe/Populatelog.php @@ -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; + } } diff --git a/api/v3/Stripe/Populatewebhookqueue.php b/api/v3/Stripe/Populatewebhookqueue.php new file mode 100644 index 0000000000000000000000000000000000000000..55cfae7ae2ed7c58805bc7d923c7acc7d473bf76 --- /dev/null +++ b/api/v3/Stripe/Populatewebhookqueue.php @@ -0,0 +1,109 @@ +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); +} diff --git a/docs/api.md b/docs/api.md index 8f25d9111ea7f101b0a9727c1698266c540a9b3d..06f1aa77d4880c669e80bfa5808af727a492b216 100644 --- a/docs/api.md +++ b/docs/api.md @@ -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: