From c148f6acb296125d343901222e1eef71d7edfe08 Mon Sep 17 00:00:00 2001 From: beppe Date: Thu, 14 Aug 2025 13:29:07 +0200 Subject: [PATCH 1/4] Remove custom check --- src/Adyen/Service/BankingWebhookParser.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Adyen/Service/BankingWebhookParser.php b/src/Adyen/Service/BankingWebhookParser.php index f606d9d16..f9a7dc8f3 100644 --- a/src/Adyen/Service/BankingWebhookParser.php +++ b/src/Adyen/Service/BankingWebhookParser.php @@ -29,15 +29,7 @@ public function getGenericWebhook() { $jsonPayload = (array)json_decode($this->payload, true); - // custom check for RelayedAuthenticationRequest as it doesn't include the attribute 'type' - if (is_array($jsonPayload) && - array_key_exists('id', $jsonPayload) && - array_key_exists('paymentInstrumentId', $jsonPayload)) { - $clazz = new RelayedAuthenticationRequest(); - return (object)$this->deserializewebhook($clazz); - } - - // handle other webhook events using `type attribute + // handle webhook events based on `type` try { $type = $jsonPayload['type']; } catch (Exception $ex) { @@ -48,6 +40,10 @@ public function getGenericWebhook() return (object)$this->deserializewebhook($clazz); } + if (in_array($type, ($clazz = new RelayedAuthenticationRequest())->getTypeAllowableValues())) { + return (object)$this->deserializewebhook($clazz); + } + if (in_array($type, ($clazz = new BalanceAccountBalanceNotificationRequest())->getTypeAllowableValues())) { return (object)$this->deserializewebhook($clazz); } From af6b9d3c64424f62625bdd57eb7a34d23786580c Mon Sep 17 00:00:00 2001 From: beppe Date: Thu, 14 Aug 2025 13:29:15 +0200 Subject: [PATCH 2/4] Update test --- tests/Unit/NotificationTest.php | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/tests/Unit/NotificationTest.php b/tests/Unit/NotificationTest.php index d39db9a85..bd95d5be9 100644 --- a/tests/Unit/NotificationTest.php +++ b/tests/Unit/NotificationTest.php @@ -401,17 +401,20 @@ public function testBankingWebhookParserBalanceAccountBalanceNotificationRequest public function testRelayedAuthenticationRequest() { $jsonString = '{ - "id": "1ea64f8e-d1e1-4b9d-a3a2-3953e385b2c8", - "paymentInstrumentId": "PI123ABCDEFGHIJKLMN45678", - "purchase": { - "date": "2025-03-06T15:17:55Z", - "merchantName": "widgetsInc", - "originalAmount": { - "currency": "EUR", - "value": 14548 - } - } - }'; + "id": "1ea64f8e-d1e1-4b9d-a3a2-3953e385b2c8", + "paymentInstrumentId": "PI123ABCDEFGHIJKLMN45678", + "purchase": { + "date": "2025-03-06T15:17:55Z", + "merchantName": "widgetsInc", + "originalAmount": { + "currency": "EUR", + "value": 14548 + } + }, + "environment": "test", + "timestamp": "2025-07-08T02:01:05+02:00", + "type": "balancePlatform.authentication.relayed" + }'; $webhookParser = new BankingWebhookParser($jsonString); $result = $webhookParser->getGenericWebhook(); From 194b208c05be265e2c40c63c957396aa81e6d63a Mon Sep 17 00:00:00 2001 From: beppe Date: Thu, 14 Aug 2025 14:06:45 +0200 Subject: [PATCH 3/4] Refactor to include PR comments --- src/Adyen/Service/BankingWebhookParser.php | 131 ++++++++++----------- 1 file changed, 63 insertions(+), 68 deletions(-) diff --git a/src/Adyen/Service/BankingWebhookParser.php b/src/Adyen/Service/BankingWebhookParser.php index f9a7dc8f3..d24065c31 100644 --- a/src/Adyen/Service/BankingWebhookParser.php +++ b/src/Adyen/Service/BankingWebhookParser.php @@ -13,135 +13,130 @@ use Adyen\Model\ReportWebhooks\ReportNotificationRequest; use Adyen\Model\TransactionWebhooks\TransactionNotificationRequestV4; use Adyen\Model\TransferWebhooks\TransferNotificationRequest; -use Exception; -use PhpParser\Error; +use JsonException; + +class WebhookParseException extends \RuntimeException +{ +} class BankingWebhookParser { private $payload; + private const WEBHOOK_CLASSES = [ + AuthenticationNotificationRequest::class, + RelayedAuthenticationRequest::class, + BalanceAccountBalanceNotificationRequest::class, + AccountHolderNotificationRequest::class, + BalanceAccountNotificationRequest::class, + PaymentNotificationRequest::class, + SweepConfigurationNotificationRequest::class, + ReportNotificationRequest::class, + TransferNotificationRequest::class, + TransactionNotificationRequestV4::class + ]; + public function __construct(string $payload) { $this->payload = $payload; } - public function getGenericWebhook() + /** + * Parse payload into the appropriate webhook model based on the `type` field. + * + * @return object The deserialized webhook model. + * @throws WebhookParseException + */ + public function getGenericWebhook(): object { - $jsonPayload = (array)json_decode($this->payload, true); - - // handle webhook events based on `type` try { - $type = $jsonPayload['type']; - } catch (Exception $ex) { - throw new Error("'type' attribute not found in payload: " . $this->payload); - } - - if (in_array($type, ($clazz = new AuthenticationNotificationRequest())->getTypeAllowableValues())) { - return (object)$this->deserializewebhook($clazz); - } - - if (in_array($type, ($clazz = new RelayedAuthenticationRequest())->getTypeAllowableValues())) { - return (object)$this->deserializewebhook($clazz); - } - - if (in_array($type, ($clazz = new BalanceAccountBalanceNotificationRequest())->getTypeAllowableValues())) { - return (object)$this->deserializewebhook($clazz); - } - - if (in_array($type, ($clazz = new AccountHolderNotificationRequest)->getTypeAllowableValues())) { - return (object)self::deserializewebhook($clazz); + $jsonPayload = json_decode($this->payload, true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw new WebhookParseException("Invalid JSON payload: " . $e->getMessage(), 0, $e); } - if (in_array($type, ($clazz = new BalanceAccountNotificationRequest())->getTypeAllowableValues())) { - return (object)self::deserializeWebhook($clazz); + if (!isset($jsonPayload['type'])) { + throw new WebhookParseException("'type' attribute not found in payload: " . $this->payload); } - if (in_array($type, ($clazz = new PaymentNotificationRequest())->getTypeAllowableValues())) { - return (object)self::deserializeWebhook($clazz); - } - - if (in_array($type, ($clazz = new SweepConfigurationNotificationRequest())->getTypeAllowableValues())) { - return (object)self::deserializeWebhook($clazz); - } - - if (in_array($type, ($clazz = new ReportNotificationRequest())->getTypeAllowableValues())) { - return (object)self::deserializeWebhook($clazz); - } - - if (in_array($type, ($clazz = new TransferNotificationRequest())->getTypeAllowableValues())) { - return(object)self::deserializeWebhook($clazz); - } + $type = $jsonPayload['type']; - if (in_array($type, ($clazz = new TransactionNotificationRequestV4())->getTypeAllowableValues())) { - return(object)self::deserializeWebhook($clazz); + foreach (self::WEBHOOK_CLASSES as $class) { + $instance = new $class(); + if (in_array($type, $instance->getTypeAllowableValues(), true)) { + return $this->deserializeWebhook($instance); + } } - // throw error in case the webhook can not be parsed - throw new \Error("Could not parse the payload: " . $this->payload); + throw new WebhookParseException("Could not parse the payload: " . $this->payload); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ + /** + * Type-safe getters for specific webhook classes. + */ public function getAuthenticationNotificationRequest(): AuthenticationNotificationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(AuthenticationNotificationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getRelayedAuthenticationRequest(): RelayedAuthenticationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(RelayedAuthenticationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getBalanceAccountBalanceNotificationRequest(): BalanceAccountBalanceNotificationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(BalanceAccountBalanceNotificationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getAccountHolderNotificationRequest(): AccountHolderNotificationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(AccountHolderNotificationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getBalanceAccountNotificationRequest(): BalanceAccountNotificationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(BalanceAccountNotificationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getPaymentNotificationRequest(): PaymentNotificationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(PaymentNotificationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getSweepConfigurationNotificationRequest(): SweepConfigurationNotificationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(SweepConfigurationNotificationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getReportNotificationRequest(): ReportNotificationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(ReportNotificationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getTransferNotificationRequest(): TransferNotificationRequest { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(TransferNotificationRequest::class); } - /** @noinspection PhpIncompatibleReturnTypeInspection */ public function getTransactionNotificationRequestV4(): TransactionNotificationRequestV4 { - return $this->getGenericWebhook(); + return $this->getWebhookByClass(TransactionNotificationRequestV4::class); + } + + private function getWebhookByClass(string $expectedClass): object + { + $webhook = $this->getGenericWebhook(); + + if (!$webhook instanceof $expectedClass) { + throw new WebhookParseException("Expected $expectedClass but got " . get_class($webhook)); + } + + return $webhook; } - private function deserializeWebhook($clazz) + private function deserializeWebhook(object $instance): object { - return ObjectSerializer::deserialize($this->payload, get_class($clazz)); + return ObjectSerializer::deserialize($this->payload, get_class($instance)); } } From 26b5de06a0561e26bf6776bff2e4879e9f32ef79 Mon Sep 17 00:00:00 2001 From: beppe Date: Thu, 14 Aug 2025 14:18:50 +0200 Subject: [PATCH 4/4] Correct code --- src/Adyen/Service/BankingWebhookParser.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Adyen/Service/BankingWebhookParser.php b/src/Adyen/Service/BankingWebhookParser.php index d24065c31..b1bb89be9 100644 --- a/src/Adyen/Service/BankingWebhookParser.php +++ b/src/Adyen/Service/BankingWebhookParser.php @@ -15,10 +15,6 @@ use Adyen\Model\TransferWebhooks\TransferNotificationRequest; use JsonException; -class WebhookParseException extends \RuntimeException -{ -} - class BankingWebhookParser { private $payload;