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

Implement update subscription via webhook

parent 50b3049d
Branches
Tags
No related merge requests found
......@@ -178,7 +178,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* @return bool
*/
public function supportsEditRecurringContribution() {
return FALSE;
return TRUE;
}
public function supportsRecurring() {
......
......@@ -387,9 +387,8 @@ class CRM_Core_Payment_StripeIPN {
if (!$this->setEventType($webhookEvent['trigger'])) {
// We don't handle this event
return FALSE;
};
// @todo consider storing webhook data when received.
$this->setVerifyData(TRUE);
}
$this->setExceptionMode(FALSE);
if (isset($emailReceipt)) {
$this->setSendEmailReceipt($emailReceipt);
......
......@@ -217,6 +217,41 @@ class CRM_Stripe_Api {
}
break;
case 'subscription_item':
switch ($name) {
default:
if (isset($stripeObject->$name)) {
return $stripeObject->$name;
}
\Civi::log()->error('getObjectParam: Tried to get param "' . $name . '" from "' . $stripeObject->object . '" but it is not set');
return NULL;
// unit_amount
}
break;
case 'price':
switch ($name) {
case 'unit_amount':
return (float) $stripeObject->unit_amount / 100;
case 'recurring_interval':
// eg. "year"
return (string) $stripeObject->recurring->interval ?? '';
case 'recurring_interval_count':
// eg 1
return (int) $stripeObject->recurring->interval_count ?? 0;
default:
if (isset($stripeObject->$name)) {
return $stripeObject->$name;
}
\Civi::log()->error('getObjectParam: Tried to get param "' . $name . '" from "' . $stripeObject->object . '" but it is not set');
return NULL;
// unit_amount
}
break;
}
return NULL;
......
......@@ -11,6 +11,7 @@
*/
namespace Civi\Stripe\Webhook;
use Brick\Money\Money;
use Civi\Api4\Contribution;
use Civi\Api4\ContributionRecur;
use CRM_Stripe_ExtensionUtil as E;
......@@ -943,7 +944,93 @@ class Events {
return $return;
}
$return->message = $this->formatResultMessage(__FUNCTION__, 'ignoring - not implemented');
$subscriptionID = $this->api->getValueFromStripeObject('subscription_id', 'String', $this->getData()->object);
$contributionRecur = $this->getRecurFromSubscriptionID($subscriptionID);
if (empty($contributionRecur)) {
// Subscription was not found in CiviCRM
$result = [];
\CRM_Mjwshared_Hook::webhookEventNotMatched('stripe', $this, 'subscription_not_found', $result);
if (empty($result['contributionRecur'])) {
$return->message = $this->formatResultMessage(__FUNCTION__, E::ts('No contributionRecur record found in CiviCRM. Ignored'));
$return->ok = TRUE;
return $return;
}
$contributionRecur = $result['contributionRecur'];
}
if (!isset($this->getData()->previous_attributes)) {
// Nothing changed?!
$return->message = $this->formatResultMessage(__FUNCTION__, E::ts('No changes. Ignored'));
$return->ok = TRUE;
return $return;
}
// First work out what changed. This is held in "previous_attributes" on webhook data
$previousAttributes = $this->getData()->previous_attributes;
// Simple check that we actually have some items data
// Otherwise it could just be a metadata change which we are not interested in.
$amountHasChanged = FALSE;
if (!empty($previousAttributes->items->data)) {
$amountHasChanged = TRUE;
}
if ($amountHasChanged) {
$objectData = $this->getData()->object;
$calculatedItems = [];
// Recalculate amount and update
foreach ($objectData->items->data as $item) {
$subscriptionItem['subscriptionItemID'] = $this->api->getValueFromStripeObject('id', 'String', $item);
$subscriptionItem['quantity'] = $this->api->getValueFromStripeObject('quantity', 'Int', $item);
$subscriptionItem['unit_amount'] = $this->api->getValueFromStripeObject('unit_amount', 'Float', $item->price);
$calculatedItem['currency'] = $this->api->getValueFromStripeObject('currency', 'String', $item->price);
$calculatedItem['amount'] = $subscriptionItem['unit_amount'] * $subscriptionItem['quantity'];
if ($this->api->getValueFromStripeObject('type', 'String', $item->price) === 'recurring') {
$calculatedItem['frequency_unit'] = $this->api->getValueFromStripeObject('recurring_interval', 'String', $item->price);
$calculatedItem['frequency_interval'] = $this->api->getValueFromStripeObject('recurring_interval_count', 'Int', $item->price);
}
if (empty($calculatedItem['frequency_unit'])) {
\Civi::log()->warning("StripeIPN: {$objectData->id} customer.subscription.updated:
Non recurring subscription items are not supported");
}
else {
$intervalKey = $calculatedItem['currency'] . '_' . $calculatedItem['frequency_unit'] . '_' . $calculatedItem['frequency_interval'];
if (isset($calculatedItems[$intervalKey])) {
// If we have more than one subscription item with the same currency and frequency add up the amounts and combine.
$calculatedItem['amount'] += ($calculatedItems[$intervalKey]['amount'] ?? 0);
$calculatedItem['subscriptionItem'] = $calculatedItems[$intervalKey]['subscriptionItem'];
}
$calculatedItem['subscriptionItem'][] = $subscriptionItem;
$calculatedItems[$intervalKey] = $calculatedItem;
}
}
}
// $calculatedItems now contains array of new prices by key [currency]_[frequency_unit]_[frequency_interval]
// Eg. $calculatedItems[usd_month_1] = [
// 'currency' => 'usd',
// 'amount' => '2000', (amount is in pence)
// ];
// Now check if recurring contribution matches frequency
$contributionRecurKey = mb_strtolower($contributionRecur['currency']) . "_{$contributionRecur['frequency_unit']}_{$contributionRecur['frequency_interval']}";
if (isset($calculatedItems[$contributionRecurKey])) {
$calculatedItem = $calculatedItems[$contributionRecurKey];
$templateContribution = \CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($contributionRecur['id']);
if (!Money::of($calculatedItem['amount'], mb_strtoupper($calculatedItem['currency']))
->isAmountAndCurrencyEqualTo(Money::of($templateContribution['total_amount'], $templateContribution['currency']))) {
// Create a new template contribution to update the amount
\Civi\Api4\ContributionRecur::updateAmountOnRecurMJW(FALSE)
->addWhere('id', '=', $contributionRecur['id'])
->addValue('amount', $calculatedItem['amount'])
->execute();
$return->message = $this->formatResultMessage(__FUNCTION__, 'recur: ' . $contributionRecur['id'] . '; new amount: ' . $calculatedItem['amount'] . ' currency: ' . $calculatedItem['currency']);
}
}
$return->ok = TRUE;
return $return;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment