billwerk_subscriptions-1.x-dev/modules/billwerk_subscriptions_handler_default/src/EventSubscriber/BillwerkWebhookEventSubscriber.php
modules/billwerk_subscriptions_handler_default/src/EventSubscriber/BillwerkWebhookEventSubscriber.php
<?php
declare(strict_types=1);
namespace Drupal\billwerk_subscriptions_handler_default\EventSubscriber;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\billwerk_subscriptions\Api;
use Drupal\billwerk_subscriptions\BillwerkDataObjectFactory;
use Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent;
use Drupal\billwerk_subscriptions\Exception\WebhookException;
use Drupal\billwerk_subscriptions\LogHelper;
use Drupal\billwerk_subscriptions\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Subscriber to Billwerk Webhook events, providing default handling.
*/
class BillwerkWebhookEventSubscriber implements EventSubscriberInterface {
/**
* The settings config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected ImmutableConfig $config;
/**
* The constructor.
*
* @param \Drupal\billwerk_subscriptions\BillwerkDataObjectFactory $billwerkDataObjectFactory
* The Billwerk Data Object Factory.
* @param \Drupal\billwerk_subscriptions\LogHelper $logHelper
* The Log Helper.
* @param \Drupal\billwerk_subscriptions\Api $api
* The Billwerk API.
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* The Config Factory.
*/
public function __construct(
protected readonly BillwerkDataObjectFactory $billwerkDataObjectFactory,
protected readonly LogHelper $logHelper,
protected readonly Api $api,
ConfigFactoryInterface $configFactory,
) {
$this->config = $configFactory->get('billwerk_subscriptions_handler_default.settings');
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
// NOTE: We do not list or subscribe to all available webhooks here.
// Just the ones that are likely to be relevant.
// Of course all available Billwerk webhooks can be used like this.
// The webhooks documentation can be found here:
// @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks.html
// @codingStandardsIgnoreStart
return [
BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'ContractCreated' => 'onContractCreated',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'ContractChanged' => 'onContractChanged',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'ContractDataChanged' => 'onContractDataChanged',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'ContractCancelled' => 'onContractCancelled',.
BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'ContractDeleted' => 'onContractDeleted',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'CustomerCreated' => 'onCustomerCreated',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'CustomerChanged' => 'onCustomerChanged',
BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'CustomerLocked' => 'onCustomerLocked',
BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'CustomerUnlocked' => 'onCustomerUnlocked',
BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'CustomerDeleted' => 'onCustomerDeleted',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'OrderSucceeded' => 'onOrderSucceeded',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'DebitAuthCancelled' => 'onDebitAuthCancelled',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'PaymentBearerExpired' => 'onPaymentBearerExpired',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'PaymentBearerExpiring' => 'onPaymentBearerExpiring',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'PaymentDataChanged' => 'onPaymentDataChanged',
BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'PaymentEscalated' => 'onPaymentEscalated',
BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'PaymentEscalationReset' => 'onPaymentEscalationReset',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'DunningCreated' => 'onDunningCreated',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'PaymentProcessStatusChanged' => 'onPaymentProcessStatusChanged',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'PaymentRegistered' => 'onPaymentRegistered',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'PaymentSucceeded' => 'onPaymentSucceeded',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'RecurringBillingApproaching' => 'onRecurringBillingApproaching',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'TrialEndApproaching' => 'onTrialEndApproaching',
// BillwerkWebhookEvent::EVENT_NAME_PREFIX . 'Test' => 'onTest',.
];
// @codingStandardsIgnoreEnd
}
/**
* Contract Created.
*
* Sent when a contract was created either via self-service or Admin UI.
*
* ContractCreated is a subset of the ContractChanged. If you have signed up
* to ContractChanged webhook you do not need to additionally . You can also
* filter ContractChanged webhooks for a type Signup. ContractCreated webhook
* is always sent before ContractChanged webhook!
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
* The event.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/contract-created.html
*/
public function onContractCreated(BillwerkWebhookEvent $event): void {
$externalCustomerId = $event->extractDataValue('ExternalCustomerId');
if (!empty($externalCustomerId)) {
if (!is_numeric($externalCustomerId)) {
throw new WebhookException('Expected numeric "ExternalCustomerId" value, but non-numeric value: "' . $externalCustomerId . '" given.');
}
// We only handle contracts of users with the Drupal External Customer ID
// set.
$subscriber = Subscriber::loadByDrupalUid((int) $externalCustomerId);
if ($subscriber !== NULL) {
$contractId = $event->extractDataValue('ContractId');
// Trigger the subscription refresh:
if (!$subscriber->hasUserBillwerkContractId()) {
// Set the contract ID in the user profile, if none is set yet.
$subscriber->setUserBillwerkContractId($contractId);
// Refresh the user account with the subscription roles etc.:
$subscriber->refreshFromBillwerkContractSubscription();
}
else {
// Log an error, as the user should not have more than one contract.
$this->logHelper->warning(
'New contract was created at Billwerk for Drupal UID: "@uid" / Billwerk Customer ID: "@customerId" but the user already has Billwerk Contract ID "@contractId" assigned. Skipped assigning the new contract.',
[
'@uid' => $externalCustomerId,
'@customerId' => $event->extractDataValue('CustomerId'),
'@contractId' => $contractId,
],
[
'eventData' => $event->getRawDataString(),
]);
}
}
}
}
// @codingStandardsIgnoreStart
/**
* Contract Changed.
*
* Sent whenever the state of a contract has changed. There are various
* reasons why the contract could have changed, e.g.:
* - The trial has ended
* - The contract has ended
* - The contract was up- or downgraded
* - A component subscription was added or changed.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/contract-changed.html
*
* Typical Scenario:
* You want to listen to this hook to make sure your customers gets what he
* ordered (and only what they paid for). Together with Contract Created, this
* is the most important hook. To find out what the current state of the
* contract is, simply fetch the referenced contract by id and make sure to
* configure your service to deliver what is configured in the contract.
*
* Contract Change types
* - Signup
* - Upgrade
* - TrialEndChange
* - ComponentSubscriptionChange
* - DiscountSubscriptionChange
* - Timebased
* - EndContract, Annulation
* - Pause, Resume
* - ExternalSubscriptionSync
* As multiple changes can be performed via an order and an order will lead to an Upgrade contract change - it is not enough to rely on a single type like e.g. ComponentSubscriptionChange. The type is intended to be used to opt-out early scenarios.
*
* All contract change webhooks contain the fields ContractId, ContractChangedType and ContractChangeId. To access more details about what has happened in the contract, call the REST API to get all the details of the contract change.
*
* Example values:
* ```
* {
* "ContractId": "645b429bb73d36f442b223df",
* "CustomerId": "645b429bb73d36f442b223db",
* "ExternalCustomerId": "249969",
* "ContractChangeId": "645b42d7f85fee194bce8990",
* "ContractChangeType": "Upgrade",
* "Event": "ContractChanged",
* "EntityId": "63b2d4405b49105c19fa7714"
* }
* ```
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
* The event.
*/
// public function onContractChanged(BillwerkWebhookEvent $event): void {
// // Typically you may want to use SubscriberContractChangedEvent instead!
// }.
/**
* Sent whenever data of a contract has changed. This occurs if you execute a PUT/PATCH on a contract, update custom fields or the external Id.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/contract-data-changed.html
*
* Typical Scenario:
* You want to listen to this hook to make sure your system gets notified when there was a change. To find out what the current state of the contract is, simply fetch the referenced contract by id.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onContractDataChanged(BillwerkWebhookEvent $event): void {
// // Do nothing by default, as this is not relevant for us typically.
// // We care for Contract and Customer changes.
// }.
/**
* Contract Cancelled.
*
* Sent when the cancellation was triggered, NOT when the contract actually ends. If you want to get notified when a contract ends, use the Contract Changed webhook instead. Usually a cancellation is triggered some time before the contract actually ends (notice period). *
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/contract-cancelled.html
*
* Typical Scenario:
* You might want to listen to this hook to get into contact with the customer to make him a special offer if he withdraws his cancellation.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onContractCancelled(BillwerkWebhookEvent $event): void {
// // Do nothing, as this is called when the cancellation is requested,
// // NOT when the contract is canceled! So we handle the relevant
// // event in onContractChanged!
// // Notifications can be easily set up at Billwerk.
// }.
/**
* Debit Auth Cancelled.
*
* This webhook is triggered when the authorization to debit money has been revoked either by customer or PSP. The next payment will fail and you probably want to take appropriate action.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment/debit-auth-cancelled.html
*
* Typical Scenario:
* A customer cancelled the payment authorization in PayPal, the webhook is triggered and you can show a notification to the customer, that there's no valid payment method stored.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onDebitAuthCancelled(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// }
/**
* Payment Bearer Expired.
*
* This webhook notifies you about a payment bearer that just expired. The next payment will fail and you probably want to take appropriate action.
*
* @see https://developer.billwerk.io/docs/webhooks/payment/paymentBearerExpired
*
* Typical Scenario:
* The credit card expired and you want to show the customer a notification, that the current payment method is invalid.
*/
// public function onPaymentBearerExpired(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// // Notifications can be easily set up at Billwerk.
// }.
/**
* Payment Bearer Expiring.
*
* This webhook notifies you about a payment bearer that is due to expire soon (typically a credit card). You probably want to inform your customer and ask them to provide new payment information. This hook will be triggered even for contracts that have ended, but you can filter such contracts in your webhook handler.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment/payment-bearer-expiring.html
*
* Typical Scenario: *
* The credit card expires in one month and you want to show the customer a notification, that the current payment method will be invalid shortly.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onPaymentBearerExpiring(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// // Notifications can be easily set up at Billwerk.
// }.
/**
* Payment Data Changed.
*
* This webhook is triggered when a new payment bearer is assigned to a contract (e.g. customer has entered new credit card data after the old card expired). *
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment.html#UUID-1380dd38-cc4a-6c06-927c-a8fbafe4cde3
*
* Typical Scenario:
* The payment data was changed and you want to notify the customer, that the change was successful.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onPaymentDataChanged(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// // Notifications can be easily set up at Billwerk.
// }.
/**
* Payment Process Status Changed.
*
* There are different reasons why a payment processes for a contract is paused, e.g several payment retries failed. In such cases billwerk stops any further payment processes. You might want to get informed about these events to take appropriate action. This webhook will be triggered if the process is stopped or started. *
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment/payment-process-status-changed.html
*
* Typical Scenario:
* Due to a direct debit chargeback the recurring payments are switched off for this contract and the webhook ist triggered. After checking the status of RecurringPaymentsPaused inside the contract you want to inform the customer about a heavy payment problem.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onPaymentProcessStatusChanged(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// }
/**
* Payment Registered.
*
* Sometimes you might have external payments/refunds outside of billwerk (e.g. bank transfer by your customer). These payments are either registered manually or via account reconciliation. This webhook will be triggered when a new external payment/refund is registered in billwerk.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment/payment-registered.html
*
* Typical Scenario:
* After registering an external payment for a contract, you want to get the current balance of the contract.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onPaymentRegistered(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// }
/**
* Payment Succeeded.
*
* This webhook is sent when a payment was successfully completed. Usually, it's not required that you worry about this in your application.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment/payment-succeeded.html
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onPaymentSucceeded(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// }
// @codingStandardsIgnoreEnd
/**
* Contract Deleted.
*
* Sent when a contract was deleted via API or via UI, or when a contract was
* deleted following deletion of its customer.
*
* Typical Scenario:
* Upon receiving this hook, you can safely proceed to delete the related data
* on your own systems. As the data is deleted, you cannot request more info
* on this contract at this stage.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
* The event.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/contract-deleted.html
*/
public function onContractDeleted(BillwerkWebhookEvent $event): void {
$contractId = $event->extractDataValue('ContractId');
if (!empty($contractId)) {
// We only handle contracts of users with the Drupal External Customer ID
// set.
$subscriber = Subscriber::loadByContractId($contractId);
if ($subscriber !== NULL) {
// Remove the contract from the user account:
$subscriber->setUserBillwerkContractId('');
$this->logHelper->info(
'Removed Billwerk Contract ID "@contractId" from User #@uid because the contract was deleted at Billwerk.',
[
'@contractId' => $contractId,
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
$onBillwerkContractDeleted = $this->config->get('on_billwerk_contract_deleted');
if (!empty($onBillwerkContractDeleted)) {
switch ($onBillwerkContractDeleted) {
case 'block':
$subscriber->getUser()->block()->save();
$this->logHelper->info(
'Blocked user #@uid because the contract was deleted at Billwerk and user blocking is configured as reaction in billwerk_subscriptions_handler_default.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
break;
case 'delete':
// @improve: Better use user_cancel() here to respect the \Drupal::config('user.settings')->get('cancel_method');.
// As this includes batch etc. for now we decided against this for
// bad DX reasons.
$subscriber->getUser()->delete();
$this->logHelper->info(
'Deleted user #@uid because the contract was deleted at Billwerk and user deletion is configured as reaction in billwerk_subscriptions_handler_default.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
break;
}
}
}
}
}
// @codingStandardsIgnoreStart
/**
* Customer Created.
*
* Sent whenever a customer has been created, e.g. through a signup.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/customer-created.html
*
* Typical Scenario:
* Usually, you want to listen to this hook to notice new customers on billwerk. After receiving the webhook, fetch the customer by id and create a new customer on your side.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onCustomerCreated(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // If you'd like to create a Drupal user automatically, once you
// // created a Customer in Billwerk, you would use this to create the
// // Drupal account.
// // That might for example be the case, if you'd like to use the
// // Billwerk hosted sign-up page instead of Drupal Account registrations.
// //
// // We've decided to first create the user account in Drupal instead,
// // as we also have NON-Drupal Customers in Billwerk that we don't want
// // Drupal accounts for!
// }.
/**
* Customer Changed.
*
* Sent whenever the base information of a customer has changed because it was modified via the customer portal.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/customer-changed.html
*
* Typical Scenario:
* Usually, you want to listen to this hook to keep your customer data in sync between your system and billwerk. To do so, fetch the customer by id and apply all necessary changes on your side.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onCustomerChanged(BillwerkWebhookEvent $event): void {
// // Typically you may want to use SubscriberCustomerChangedEvent instead!
// }.
// @codingStandardsIgnoreEnd
/**
* Customer Deleted.
*
* Sent whenever the base information of a customer has been deleted.
*
* Typical Scenario:
* Usually, you want to inform about that customer deleted. To do so, fetch
* the customer by id and apply all necessary changes on your side.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
* The event.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/customer-deleted.html
*/
public function onCustomerDeleted(BillwerkWebhookEvent $event): void {
// Disable the user account. We don't want to risk deleting a user here
// unexpectedly!
$billwerkCustomerId = $event->extractDataValue('CustomerId');
// @todo This may not be called because the customer has already been deleted and can't be retrieved anymore. Eventually we need to rely on onContractDeleted only?
$billwerkCustomer = $this->api->getCustomer($billwerkCustomerId);
if (!empty($billwerkCustomer) && !empty($billwerkCustomer['ExternalCustomerId'])) {
if (!is_numeric($billwerkCustomer['ExternalCustomerId'])) {
throw new WebhookException('Expected numeric "ExternalCustomerId" value, but non-numeric value: "' . $billwerkCustomer['ExternalCustomerId'] . '" given.');
}
$subscriber = Subscriber::loadByDrupalUid((int) $billwerkCustomer['ExternalCustomerId']);
if (!empty($subscriber)) {
$onBillwerkCustomerDeleted = $this->config->get('on_billwerk_customer_deleted');
if (!empty($onBillwerkCustomerDeleted)) {
switch ($onBillwerkCustomerDeleted) {
case 'block':
$subscriber->getUser()->block()->save();
$this->logHelper->info(
'Blocked user #@uid because the customer was deleted at Billwerk and user blocking is configured as reaction in billwerk_subscriptions_handler_default.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
break;
case 'delete':
// @improve: Better use user_cancel() here to respect the \Drupal::config('user.settings')->get('cancel_method');.
// As this includes batch etc. for now we decided against this for
// bad DX reasons.
$subscriber->getUser()->delete();
$this->logHelper->info(
'Deleted user #@uid because the customer was deleted at Billwerk and user deletion is configured as reaction in billwerk_subscriptions_handler_default.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
break;
}
}
}
}
}
// @codingStandardsIgnoreStart
/**
* Order Succeeded.
*
* Sent when an order was finished successfully. This includes all order types
* (signup, upgrade, ...).
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/order-succeeded.html
*
* Typical Scenario:
* You want to listen to this hook to send an order confirmation by email to the customer.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onOrderSucceeded(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// // Notifications can be easily set up at Billwerk.
// }.
/**
* Recurring Billing Approaching.
*
* This webhook is triggered when a billing period ended. Depending on your billing delay settings you can use this webhook to pass remaining metered usage or rated items before the actual recurring billing is processed.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/recurring-billing-approaching.html
*
* Typical Scenario:
* You want to listen to this hook to pass metered usage or rated items before the next recurring billing.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onRecurringBillingApproaching(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// // Notifications can be easily set up at Billwerk.
// }.
/**
* Trial End Approaching.
*
* Sent when the trial period of a contract is about to expire. The warning time can be defined per plan.
*
* @see https://developer.billwerk.io/docs/webhooks/customer_contract/trialEndApproaching
*
* Typical Scenario:
* You want to send a reminder to your customer if the trial expires in x days.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onTrialEndApproaching(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// // Notifications can be easily set up at Billwerk.
// }.
/**
* Customer Locked.
*
* Sent whenever a customer has been locked.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/customer-locked.html
*
* Typical Scenario:
* Usually, you want to inform about that customer locked. To do so, fetch the customer by id and apply all necessary changes on your side.
*/
public function onCustomerLocked(BillwerkWebhookEvent $event): void {
// We handle the contract affection results in "onContractChanged()".
// Notifications can be easily set up at Billwerk.
$billwerkCustomerId = $event->extractDataValue('CustomerId');
$billwerkCustomer = $this->api->getCustomer($billwerkCustomerId);
if (!empty($billwerkCustomer) && !empty($billwerkCustomer['ExternalCustomerId'])) {
if (!is_numeric($billwerkCustomer['ExternalCustomerId'])) {
throw new WebhookException('Expected numeric "ExternalCustomerId" value, but non-numeric value: "' . $billwerkCustomer['ExternalCustomerId'] . '" given.');
}
$subscriber = Subscriber::loadByDrupalUid((int) $billwerkCustomer['ExternalCustomerId']);
if (!empty($subscriber)) {
$onBillwerkCustomerDeleted = $this->config->get('on_billwerk_customer_locked');
if (!empty($onBillwerkCustomerDeleted)) {
switch ($onBillwerkCustomerDeleted) {
case 'block':
$subscriber->getUser()->block()->save();
$this->logHelper->info(
'Blocked user #@uid because the customer was locked at Billwerk and user blocking is configured as reaction in billwerk_subscriptions_handler_default.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
break;
}
}
}
}
}
/**
* Customer Unlocked.
*
* Sent whenever a customer has been unlocked.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/customer-unlocked.html
*
* Typical Scenario
* Usually, you want to inform about that customer unlocked. To do so, fetch the customer by id and apply all necessary changes on your side.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
public function onCustomerUnlocked(BillwerkWebhookEvent $event): void {
// We handle the contract affection results in "onContractChanged()".
// Notifications can be easily set up at Billwerk.
$billwerkCustomerId = $event->extractDataValue('CustomerId');
$billwerkCustomer = $this->api->getCustomer($billwerkCustomerId);
if (!empty($billwerkCustomer) && !empty($billwerkCustomer['ExternalCustomerId'])) {
if (!is_numeric($billwerkCustomer['ExternalCustomerId'])) {
throw new WebhookException('Expected numeric "ExternalCustomerId" value, but non-numeric value: "' . $billwerkCustomer['ExternalCustomerId'] . '" given.');
}
$subscriber = Subscriber::loadByDrupalUid((int) $billwerkCustomer['ExternalCustomerId']);
if (!empty($subscriber)) {
$onBillwerkCustomerDeleted = $this->config->get('on_billwerk_customer_unlocked');
if (!empty($onBillwerkCustomerDeleted)) {
switch ($onBillwerkCustomerDeleted) {
case 'unblock':
$subscriber->getUser()->activate()->save();
$this->logHelper->info(
'Unblocked user #@uid because the customer was unlocked at Billwerk and user unblocking is configured as reaction in billwerk_subscriptions_handler_default.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
break;
}
}
}
}
}
/**
* Payment Escalated.
*
* This webhook is triggered based on your payment escalation settings.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment/payment-escalated.html
*
* Typical scenario:
* You can use this hook to react to your customers non-payment, for example by sending a notification email or by suspending the account.
* The value of TriggerDays is the number of days since the payments due date. This is based on the oldest unpaid receivable, so it's possible that you receive a five-day-hook, then a ten-day-hook and a five-day-hook again.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment/payment-escalated.html
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
public function onPaymentEscalated(BillwerkWebhookEvent $event): void {
// Do nothing by default.
// We handle the contract affection results in "onContractChanged()".
$billwerkCustomerId = $event->extractDataValue('CustomerId');
$billwerkCustomer = $this->api->getCustomer($billwerkCustomerId);
if (!empty($billwerkCustomer) && !empty($billwerkCustomer['ExternalCustomerId'])) {
if (!is_numeric($billwerkCustomer['ExternalCustomerId'])) {
throw new WebhookException('Expected numeric "ExternalCustomerId" value, but non-numeric value: "' . $billwerkCustomer['ExternalCustomerId'] . '" given.');
}
$subscriber = Subscriber::loadByDrupalUid((int) $billwerkCustomer['ExternalCustomerId']);
if (!empty($subscriber)) {
$onBillwerkCustomerDeleted = $this->config->get('on_billwerk_customer_payment_escalated');
// @improve: Maybe it will be required to compare the "TriggerDays" parameter here, see
// https://docu.billwerk.plus/en/webhooks/payment/payment-escalated.html
// which would require a further setting for the limits.
if (!empty($onBillwerkCustomerDeleted)) {
switch ($onBillwerkCustomerDeleted) {
case 'block':
$subscriber->getUser()->block()->save();
$this->logHelper->info(
'Blocked user #@uid because the payment escalated at Billwerk and user blocking is configured as reaction in billwerk_subscriptions_handler_default.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
break;
}
}
}
}
}
/**
* Payment Escalation Reset.
*
* This webhook is triggered when an escalation process was reset automatically or manually.
* An automatic reset happens when the customer paid the vacant positions. The process can also be reset manually by you.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/payment.html
*
* Typical scenario:
* A customer paid the open receivables and now you want to unlock the service for him.
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
public function onPaymentEscalationReset(BillwerkWebhookEvent $event): void {
// Do nothing by default.
// We handle the contract affection results in "onContractChanged()".
$billwerkCustomerId = $event->extractDataValue('CustomerId');
$billwerkCustomer = $this->api->getCustomer($billwerkCustomerId);
if (!empty($billwerkCustomer) && !empty($billwerkCustomer['ExternalCustomerId'])) {
if (!is_numeric($billwerkCustomer['ExternalCustomerId'])) {
throw new WebhookException('Expected numeric "ExternalCustomerId" value, but non-numeric value: "' . $billwerkCustomer['ExternalCustomerId'] . '" given.');
}
$subscriber = Subscriber::loadByDrupalUid((int) $billwerkCustomer['ExternalCustomerId']);
if (!empty($subscriber)) {
$onBillwerkCustomerDeleted = $this->config->get('on_billwerk_customer_payment_escalation_reset');
if (!empty($onBillwerkCustomerDeleted)) {
switch ($onBillwerkCustomerDeleted) {
case 'unblock':
$subscriber->getUser()->activate()->save();
$this->logHelper->info(
'Unblocked user #@uid because the payment escalation was reset at Billwerk and user unblocking is configured as reaction in billwerk_subscriptions_handler_default.',
[
'@uid' => $subscriber->getUser()->id(),
],
NULL, __CLASS__ . __FUNCTION__);
break;
}
}
}
}
}
/**
* Dunning Created.
*
* This webhook is triggered when a new dunning PDF was created and sent/archived.
*
* @see https://docu.billwerk.plus/api/premium-enterprise/en/webhooks/customer-and-contract/dunning-created.html
*
* @param \Drupal\billwerk_subscriptions\Event\BillwerkWebhookEvent $event
*
* @return void
*/
// public function onDunningCreated(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // We handle the contract affection results in "onContractChanged()".
// }
/**
* Handles a test event.
*/
// public function onTest(BillwerkWebhookEvent $event): void {
// // Do nothing by default.
// // The base module writes a log entry already.
// }.
// @codingStandardsIgnoreEnd
}
