Skip to content

Commit a75043f

Browse files
authored
AcsWebhooks: remove custom fix (#809)
* Remove custom check * Update test * Refactor to include PR comments * Correct code
1 parent 445ba7f commit a75043f

File tree

2 files changed

+73
-83
lines changed

2 files changed

+73
-83
lines changed

src/Adyen/Service/BankingWebhookParser.php

Lines changed: 59 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -13,139 +13,126 @@
1313
use Adyen\Model\ReportWebhooks\ReportNotificationRequest;
1414
use Adyen\Model\TransactionWebhooks\TransactionNotificationRequestV4;
1515
use Adyen\Model\TransferWebhooks\TransferNotificationRequest;
16-
use Exception;
17-
use PhpParser\Error;
16+
use JsonException;
1817

1918
class BankingWebhookParser
2019
{
2120
private $payload;
2221

22+
private const WEBHOOK_CLASSES = [
23+
AuthenticationNotificationRequest::class,
24+
RelayedAuthenticationRequest::class,
25+
BalanceAccountBalanceNotificationRequest::class,
26+
AccountHolderNotificationRequest::class,
27+
BalanceAccountNotificationRequest::class,
28+
PaymentNotificationRequest::class,
29+
SweepConfigurationNotificationRequest::class,
30+
ReportNotificationRequest::class,
31+
TransferNotificationRequest::class,
32+
TransactionNotificationRequestV4::class
33+
];
34+
2335
public function __construct(string $payload)
2436
{
2537
$this->payload = $payload;
2638
}
2739

28-
public function getGenericWebhook()
40+
/**
41+
* Parse payload into the appropriate webhook model based on the `type` field.
42+
*
43+
* @return object The deserialized webhook model.
44+
* @throws WebhookParseException
45+
*/
46+
public function getGenericWebhook(): object
2947
{
30-
$jsonPayload = (array)json_decode($this->payload, true);
31-
32-
// custom check for RelayedAuthenticationRequest as it doesn't include the attribute 'type'
33-
if (is_array($jsonPayload) &&
34-
array_key_exists('id', $jsonPayload) &&
35-
array_key_exists('paymentInstrumentId', $jsonPayload)) {
36-
$clazz = new RelayedAuthenticationRequest();
37-
return (object)$this->deserializewebhook($clazz);
38-
}
39-
40-
// handle other webhook events using `type attribute
4148
try {
42-
$type = $jsonPayload['type'];
43-
} catch (Exception $ex) {
44-
throw new Error("'type' attribute not found in payload: " . $this->payload);
45-
}
46-
47-
if (in_array($type, ($clazz = new AuthenticationNotificationRequest())->getTypeAllowableValues())) {
48-
return (object)$this->deserializewebhook($clazz);
49-
}
50-
51-
if (in_array($type, ($clazz = new BalanceAccountBalanceNotificationRequest())->getTypeAllowableValues())) {
52-
return (object)$this->deserializewebhook($clazz);
53-
}
54-
55-
if (in_array($type, ($clazz = new AccountHolderNotificationRequest)->getTypeAllowableValues())) {
56-
return (object)self::deserializewebhook($clazz);
57-
}
58-
59-
if (in_array($type, ($clazz = new BalanceAccountNotificationRequest())->getTypeAllowableValues())) {
60-
return (object)self::deserializeWebhook($clazz);
49+
$jsonPayload = json_decode($this->payload, true, 512, JSON_THROW_ON_ERROR);
50+
} catch (JsonException $e) {
51+
throw new WebhookParseException("Invalid JSON payload: " . $e->getMessage(), 0, $e);
6152
}
6253

63-
if (in_array($type, ($clazz = new PaymentNotificationRequest())->getTypeAllowableValues())) {
64-
return (object)self::deserializeWebhook($clazz);
54+
if (!isset($jsonPayload['type'])) {
55+
throw new WebhookParseException("'type' attribute not found in payload: " . $this->payload);
6556
}
6657

67-
if (in_array($type, ($clazz = new SweepConfigurationNotificationRequest())->getTypeAllowableValues())) {
68-
return (object)self::deserializeWebhook($clazz);
69-
}
70-
71-
if (in_array($type, ($clazz = new ReportNotificationRequest())->getTypeAllowableValues())) {
72-
return (object)self::deserializeWebhook($clazz);
73-
}
58+
$type = $jsonPayload['type'];
7459

75-
if (in_array($type, ($clazz = new TransferNotificationRequest())->getTypeAllowableValues())) {
76-
return(object)self::deserializeWebhook($clazz);
60+
foreach (self::WEBHOOK_CLASSES as $class) {
61+
$instance = new $class();
62+
if (in_array($type, $instance->getTypeAllowableValues(), true)) {
63+
return $this->deserializeWebhook($instance);
64+
}
7765
}
7866

79-
if (in_array($type, ($clazz = new TransactionNotificationRequestV4())->getTypeAllowableValues())) {
80-
return(object)self::deserializeWebhook($clazz);
81-
}
82-
83-
// throw error in case the webhook can not be parsed
84-
throw new \Error("Could not parse the payload: " . $this->payload);
67+
throw new WebhookParseException("Could not parse the payload: " . $this->payload);
8568
}
8669

87-
/** @noinspection PhpIncompatibleReturnTypeInspection */
70+
/**
71+
* Type-safe getters for specific webhook classes.
72+
*/
8873
public function getAuthenticationNotificationRequest(): AuthenticationNotificationRequest
8974
{
90-
return $this->getGenericWebhook();
75+
return $this->getWebhookByClass(AuthenticationNotificationRequest::class);
9176
}
9277

93-
/** @noinspection PhpIncompatibleReturnTypeInspection */
9478
public function getRelayedAuthenticationRequest(): RelayedAuthenticationRequest
9579
{
96-
return $this->getGenericWebhook();
80+
return $this->getWebhookByClass(RelayedAuthenticationRequest::class);
9781
}
9882

99-
/** @noinspection PhpIncompatibleReturnTypeInspection */
10083
public function getBalanceAccountBalanceNotificationRequest(): BalanceAccountBalanceNotificationRequest
10184
{
102-
return $this->getGenericWebhook();
85+
return $this->getWebhookByClass(BalanceAccountBalanceNotificationRequest::class);
10386
}
10487

105-
/** @noinspection PhpIncompatibleReturnTypeInspection */
10688
public function getAccountHolderNotificationRequest(): AccountHolderNotificationRequest
10789
{
108-
return $this->getGenericWebhook();
90+
return $this->getWebhookByClass(AccountHolderNotificationRequest::class);
10991
}
11092

111-
/** @noinspection PhpIncompatibleReturnTypeInspection */
11293
public function getBalanceAccountNotificationRequest(): BalanceAccountNotificationRequest
11394
{
114-
return $this->getGenericWebhook();
95+
return $this->getWebhookByClass(BalanceAccountNotificationRequest::class);
11596
}
11697

117-
/** @noinspection PhpIncompatibleReturnTypeInspection */
11898
public function getPaymentNotificationRequest(): PaymentNotificationRequest
11999
{
120-
return $this->getGenericWebhook();
100+
return $this->getWebhookByClass(PaymentNotificationRequest::class);
121101
}
122102

123-
/** @noinspection PhpIncompatibleReturnTypeInspection */
124103
public function getSweepConfigurationNotificationRequest(): SweepConfigurationNotificationRequest
125104
{
126-
return $this->getGenericWebhook();
105+
return $this->getWebhookByClass(SweepConfigurationNotificationRequest::class);
127106
}
128107

129-
/** @noinspection PhpIncompatibleReturnTypeInspection */
130108
public function getReportNotificationRequest(): ReportNotificationRequest
131109
{
132-
return $this->getGenericWebhook();
110+
return $this->getWebhookByClass(ReportNotificationRequest::class);
133111
}
134112

135-
/** @noinspection PhpIncompatibleReturnTypeInspection */
136113
public function getTransferNotificationRequest(): TransferNotificationRequest
137114
{
138-
return $this->getGenericWebhook();
115+
return $this->getWebhookByClass(TransferNotificationRequest::class);
139116
}
140117

141-
/** @noinspection PhpIncompatibleReturnTypeInspection */
142118
public function getTransactionNotificationRequestV4(): TransactionNotificationRequestV4
143119
{
144-
return $this->getGenericWebhook();
120+
return $this->getWebhookByClass(TransactionNotificationRequestV4::class);
121+
}
122+
123+
private function getWebhookByClass(string $expectedClass): object
124+
{
125+
$webhook = $this->getGenericWebhook();
126+
127+
if (!$webhook instanceof $expectedClass) {
128+
throw new WebhookParseException("Expected $expectedClass but got " . get_class($webhook));
129+
}
130+
131+
return $webhook;
145132
}
146133

147-
private function deserializeWebhook($clazz)
134+
private function deserializeWebhook(object $instance): object
148135
{
149-
return ObjectSerializer::deserialize($this->payload, get_class($clazz));
136+
return ObjectSerializer::deserialize($this->payload, get_class($instance));
150137
}
151138
}

tests/Unit/NotificationTest.php

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -401,17 +401,20 @@ public function testBankingWebhookParserBalanceAccountBalanceNotificationRequest
401401
public function testRelayedAuthenticationRequest()
402402
{
403403
$jsonString = '{
404-
"id": "1ea64f8e-d1e1-4b9d-a3a2-3953e385b2c8",
405-
"paymentInstrumentId": "PI123ABCDEFGHIJKLMN45678",
406-
"purchase": {
407-
"date": "2025-03-06T15:17:55Z",
408-
"merchantName": "widgetsInc",
409-
"originalAmount": {
410-
"currency": "EUR",
411-
"value": 14548
412-
}
413-
}
414-
}';
404+
"id": "1ea64f8e-d1e1-4b9d-a3a2-3953e385b2c8",
405+
"paymentInstrumentId": "PI123ABCDEFGHIJKLMN45678",
406+
"purchase": {
407+
"date": "2025-03-06T15:17:55Z",
408+
"merchantName": "widgetsInc",
409+
"originalAmount": {
410+
"currency": "EUR",
411+
"value": 14548
412+
}
413+
},
414+
"environment": "test",
415+
"timestamp": "2025-07-08T02:01:05+02:00",
416+
"type": "balancePlatform.authentication.relayed"
417+
}';
415418

416419
$webhookParser = new BankingWebhookParser($jsonString);
417420
$result = $webhookParser->getGenericWebhook();

0 commit comments

Comments
 (0)