diff --git a/README.md b/README.md index 6e4f7fd..9db5f7c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Webhook Module for PHP (Beta) +# Webhook Module for PHP Adyen library for handling notification webhooks. diff --git a/composer.json b/composer.json index 7cd78b4..5ce3786 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,7 @@ } }, "require-dev": { - "phpunit/phpunit": "^9.5.0", - "squizlabs/php_codesniffer": "^3.5" + "phpunit/phpunit": "~9.6.0", + "squizlabs/php_codesniffer": "~3.8.0" } } diff --git a/src/EventCodes.php b/src/EventCodes.php index 278c110..0887d29 100644 --- a/src/EventCodes.php +++ b/src/EventCodes.php @@ -48,4 +48,6 @@ final class EventCodes const CHARGEBACK = "CHARGEBACK"; const CHARGEBACK_REVERSED = "CHARGEBACK_REVERSED"; const SECOND_CHARGEBACK = "SECOND_CHARGEBACK"; + const NOTIFICATION_OF_CHARGEBACK = "NOTIFICATION_OF_CHARGEBACK"; + const REQUEST_FOR_INFORMATION = "REQUEST_FOR_INFORMATION"; } diff --git a/src/Notification.php b/src/Notification.php index c0d971a..63db687 100644 --- a/src/Notification.php +++ b/src/Notification.php @@ -63,6 +63,11 @@ public function getEventCode(): string return $this->eventCode; } + public function getAdditionalData(): array + { + return $this->additionalData ?? []; + } + public function isSuccess(): bool { return in_array($this->success, [true, "true"], true); diff --git a/src/PaymentStates.php b/src/PaymentStates.php index f1297e1..c366360 100644 --- a/src/PaymentStates.php +++ b/src/PaymentStates.php @@ -28,6 +28,7 @@ final class PaymentStates public const STATE_IN_PROGRESS = 'in_progress'; public const STATE_PENDING = 'pending'; public const STATE_PAID = 'paid'; + public const STATE_AUTHORIZED = 'authorized'; public const STATE_FAILED = 'failed'; public const STATE_REFUNDED = 'refunded'; public const STATE_PARTIALLY_REFUNDED = 'partially_refunded'; diff --git a/src/Processor/AbstractDisputeNotificationProcessor.php b/src/Processor/AbstractDisputeNotificationProcessor.php new file mode 100644 index 0000000..1298c2b --- /dev/null +++ b/src/Processor/AbstractDisputeNotificationProcessor.php @@ -0,0 +1,59 @@ +initialState; + $additionalData = $this->notification->getAdditionalData(); + $disputeStatus = $additionalData[self::DISPUTE_STATUS] ?? null; + + if ($this->notification->isSuccess() && + isset($disputeStatus) && + in_array($disputeStatus, self::FINAL_DISPUTE_STATUSES) && + in_array($state, self::CHARGEBACK_ORDER_STATES)) { + $state = PaymentStates::STATE_REFUNDED; + } + + return $state; + } +} diff --git a/src/Processor/AuthorisationProcessor.php b/src/Processor/AuthorisationProcessor.php index 7be91af..f7f7fd5 100644 --- a/src/Processor/AuthorisationProcessor.php +++ b/src/Processor/AuthorisationProcessor.php @@ -35,7 +35,11 @@ public function process(): ?string $state, [PaymentStates::STATE_NEW, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_PENDING] )) { - $state = $this->notification->isSuccess() ? PaymentStates::STATE_PAID : PaymentStates::STATE_FAILED; + if ($this->notification->isSuccess()) { + $state = $this->isAutoCapture ? PaymentStates::STATE_PAID : PaymentStates::STATE_AUTHORIZED; + } else { + $state = PaymentStates::STATE_FAILED; + } } return $state; diff --git a/src/Processor/ChargebackProcessor.php b/src/Processor/ChargebackProcessor.php index b0b82e2..0815b6b 100644 --- a/src/Processor/ChargebackProcessor.php +++ b/src/Processor/ChargebackProcessor.php @@ -23,21 +23,7 @@ namespace Adyen\Webhook\Processor; -use Adyen\Webhook\PaymentStates; - -class ChargebackProcessor extends Processor implements ProcessorInterface +class ChargebackProcessor extends AbstractDisputeNotificationProcessor implements ProcessorInterface { - public function process(): ?string - { - $state = $this->initialState; - - if (in_array( - $state, - [PaymentStates::STATE_NEW, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_PENDING] - )) { - $state = $this->notification->isSuccess() ? PaymentStates::CHARGE_BACK : PaymentStates::STATE_FAILED; - } - return $state; - } } diff --git a/src/Processor/NotificationOfChargebackProcessor.php b/src/Processor/NotificationOfChargebackProcessor.php new file mode 100644 index 0000000..7b499fe --- /dev/null +++ b/src/Processor/NotificationOfChargebackProcessor.php @@ -0,0 +1,32 @@ +unchanged(); + } +} diff --git a/src/Processor/Processor.php b/src/Processor/Processor.php index d41f9c5..b715acd 100644 --- a/src/Processor/Processor.php +++ b/src/Processor/Processor.php @@ -39,16 +39,26 @@ abstract class Processor implements ProcessorInterface */ protected $initialState; + /** + * @var bool + */ + protected $isAutoCapture; + abstract public function process(): ?string; /** * @param Notification $notification * @param string $state + * @param bool $isAutoCapture * @throws InvalidDataException */ - public function __construct(Notification $notification, string $state) - { + public function __construct( + Notification $notification, + string $state, + bool $isAutoCapture = true + ) { $this->notification = $notification; + $this->isAutoCapture = $isAutoCapture; $this->validateState($state); $this->initialState = $state; diff --git a/src/Processor/ProcessorFactory.php b/src/Processor/ProcessorFactory.php index d909432..19f7315 100644 --- a/src/Processor/ProcessorFactory.php +++ b/src/Processor/ProcessorFactory.php @@ -24,11 +24,12 @@ namespace Adyen\Webhook\Processor; use Adyen\Webhook\EventCodes; +use Adyen\Webhook\Exception\InvalidDataException; use Adyen\Webhook\Notification; class ProcessorFactory { - private static $adyenEventCodeProcessors = [ + private static array $adyenEventCodeProcessors = [ EventCodes::AUTHORISATION => AuthorisationProcessor::class, EventCodes::OFFER_CLOSED => OfferClosedProcessor::class, EventCodes::REFUND => RefundProcessor::class, @@ -51,20 +52,29 @@ class ProcessorFactory EventCodes::VOID_PENDING_REFUND => VoidPendingRefundProcessor::class, EventCodes::CHARGEBACK => ChargebackProcessor::class, EventCodes::CHARGEBACK_REVERSED => ChargebackReversedProcessor::class, - EventCodes::SECOND_CHARGEBACK => SecondChargebackProcessor::class + EventCodes::SECOND_CHARGEBACK => SecondChargebackProcessor::class, + EventCodes::NOTIFICATION_OF_CHARGEBACK => NotificationOfChargebackProcessor::class, + EventCodes::REQUEST_FOR_INFORMATION => RequestForInformationProcessor::class ]; /** * @param Notification $notification * @param string $paymentState + * @param bool $isAutoCapture * @return ProcessorInterface + * @throws InvalidDataException */ public static function create( Notification $notification, - string $paymentState + string $paymentState, + bool $isAutoCapture = true ): ProcessorInterface { return array_key_exists($notification->getEventCode(), self::$adyenEventCodeProcessors) - ? new self::$adyenEventCodeProcessors[$notification->getEventCode()]($notification, $paymentState) - : new DefaultProcessor($notification, $paymentState); + ? new self::$adyenEventCodeProcessors[$notification->getEventCode()]( + $notification, + $paymentState, + $isAutoCapture + ) + : new DefaultProcessor($notification, $paymentState, $isAutoCapture); } } diff --git a/src/Processor/RequestForInformationProcessor.php b/src/Processor/RequestForInformationProcessor.php new file mode 100644 index 0000000..6f9ce8a --- /dev/null +++ b/src/Processor/RequestForInformationProcessor.php @@ -0,0 +1,32 @@ +unchanged(); + } +} diff --git a/src/Processor/SecondChargebackProcessor.php b/src/Processor/SecondChargebackProcessor.php index d2d7bc5..6d5b743 100644 --- a/src/Processor/SecondChargebackProcessor.php +++ b/src/Processor/SecondChargebackProcessor.php @@ -23,21 +23,7 @@ namespace Adyen\Webhook\Processor; -use Adyen\Webhook\PaymentStates; - -class SecondChargebackProcessor extends Processor implements ProcessorInterface +class SecondChargebackProcessor extends AbstractDisputeNotificationProcessor implements ProcessorInterface { - public function process(): ?string - { - $state = $this->initialState; - - if (in_array( - $state, - [PaymentStates::STATE_NEW, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_PENDING] - )) { - $state = $this->notification->isSuccess() ? PaymentStates::CHARGE_BACK : PaymentStates::STATE_FAILED; - } - return $state; - } } diff --git a/tests/Unit/Processor/ProcessorFactoryTest.php b/tests/Unit/Processor/ProcessorFactoryTest.php index 22a1a46..a1e126e 100644 --- a/tests/Unit/Processor/ProcessorFactoryTest.php +++ b/tests/Unit/Processor/ProcessorFactoryTest.php @@ -51,8 +51,6 @@ use Adyen\Webhook\Processor\RefundedWithDataProcessor; use Adyen\Webhook\Processor\RefundFailedProcessor; use Adyen\Webhook\Processor\RefundProcessor; - - use Adyen\Webhook\Processor\ReportAvailableProcessor; use Adyen\Webhook\Processor\SecondChargebackProcessor; use Adyen\Webhook\Processor\VoidPendingRefundProcessor; @@ -141,6 +139,8 @@ public function processorPaymentStatesProvider(): array { return [ [EventCodes::AUTHORISATION, PaymentStates::STATE_NEW, PaymentStates::STATE_PAID, 'true'], + [EventCodes::AUTHORISATION, PaymentStates::STATE_NEW, PaymentStates::STATE_FAILED, 'false'], + [EventCodes::AUTHORISATION, PaymentStates::STATE_NEW, PaymentStates::STATE_AUTHORIZED, 'true', false], [EventCodes::AUTHORISATION, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_PAID, 'true'], [EventCodes::AUTHORISATION, PaymentStates::STATE_PENDING, PaymentStates::STATE_PAID, 'true'], [EventCodes::CANCELLATION, PaymentStates::STATE_NEW, PaymentStates::STATE_CANCELLED, 'true'], @@ -184,31 +184,38 @@ public function processorPaymentStatesProvider(): array [EventCodes::VOID_PENDING_REFUND, PaymentStates::STATE_NEW, PaymentStates::STATE_CANCELLED, 'true'], [EventCodes::VOID_PENDING_REFUND, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_CANCELLED, 'true'], [EventCodes::VOID_PENDING_REFUND, PaymentStates::STATE_PENDING, PaymentStates::STATE_CANCELLED, 'true'], - [EventCodes::CHARGEBACK, PaymentStates::STATE_NEW, PaymentStates::CHARGE_BACK, 'true'], - [EventCodes::CHARGEBACK, PaymentStates::STATE_IN_PROGRESS, PaymentStates::CHARGE_BACK, 'true'], - [EventCodes::CHARGEBACK, PaymentStates::STATE_PENDING, PaymentStates::CHARGE_BACK, 'true'], + [EventCodes::CHARGEBACK, PaymentStates::STATE_NEW, PaymentStates::STATE_NEW, 'true'], + [EventCodes::CHARGEBACK, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_IN_PROGRESS, 'true'], + [EventCodes::CHARGEBACK, PaymentStates::STATE_PENDING, PaymentStates::STATE_PENDING, 'true'], [EventCodes::CHARGEBACK_REVERSED, PaymentStates::STATE_NEW, PaymentStates::STATE_CANCELLED, 'true'], - [EventCodes::CHARGEBACK_REVERSED, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_CANCELLED, 'true'], + [EventCodes::CHARGEBACK_REVERSED, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_CANCELLED, + 'true'], [EventCodes::CHARGEBACK_REVERSED, PaymentStates::STATE_PENDING, PaymentStates::STATE_CANCELLED, 'true'], - [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_NEW, PaymentStates::CHARGE_BACK, 'true'], - [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_IN_PROGRESS, PaymentStates::CHARGE_BACK, 'true'], - [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_PENDING, PaymentStates::CHARGE_BACK, 'true'], - [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_NEW, PaymentStates::STATE_FAILED, 'false'], - [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_FAILED, 'false'], - [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_PENDING, PaymentStates::STATE_FAILED, 'false'] + [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_NEW, PaymentStates::STATE_NEW, 'true'], + [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_IN_PROGRESS, 'true'], + [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_PENDING, PaymentStates::STATE_PENDING, 'true'], + [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_NEW, PaymentStates::STATE_NEW, 'false'], + [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_IN_PROGRESS, PaymentStates::STATE_IN_PROGRESS, + 'false'], + [EventCodes::SECOND_CHARGEBACK, PaymentStates::STATE_PENDING, PaymentStates::STATE_PENDING, 'false'] ]; } /** * @dataProvider processorPaymentStatesProvider */ - public function testProcessorPaymentStates($eventCode, $originalState, $expectedState, $success) - { + public function testProcessorPaymentStates( + $eventCode, + $originalState, + $expectedState, + $success, + $isAutoCapture = true + ) { $notification = $this->createNotificationSuccess([ 'eventCode' => $eventCode, 'success' => $success, ]); - $processor = ProcessorFactory::create($notification, $originalState); + $processor = ProcessorFactory::create($notification, $originalState, $isAutoCapture); $newState = $processor->process(); $this->assertEquals($expectedState, $newState); }