Skip to content

Commit 5ee4fdd

Browse files
authored
BB-22354: Stripe - Invalid processing of unsupported events
1 parent 6e5b815 commit 5ee4fdd

3 files changed

Lines changed: 175 additions & 17 deletions

File tree

src/Oro/Bundle/StripeBundle/Event/StripeSDKEventFactory.php

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,20 @@
55
use Oro\Bundle\StripeBundle\Method\Config\Provider\StripePaymentConfigsProvider;
66
use Oro\Bundle\StripeBundle\Method\Config\StripePaymentConfig;
77
use Oro\Bundle\StripeBundle\Model\ChargeResponse;
8+
use Oro\Bundle\StripeBundle\Model\CustomerResponse;
89
use Oro\Bundle\StripeBundle\Model\PaymentIntentResponse;
10+
use Oro\Bundle\StripeBundle\Model\RefundResponse;
911
use Oro\Bundle\StripeBundle\Model\ResponseObjectInterface;
12+
use Oro\Bundle\StripeBundle\Model\SetupIntentResponse;
13+
use Oro\Bundle\StripeBundle\Model\UnsupportedResponse;
14+
use Stripe\Balance;
1015
use Stripe\Charge;
16+
use Stripe\Customer;
17+
use Stripe\Event;
1118
use Stripe\Exception\SignatureVerificationException;
1219
use Stripe\PaymentIntent;
20+
use Stripe\Refund;
21+
use Stripe\SetupIntent;
1322
use Symfony\Component\HttpFoundation\Request;
1423

1524
/**
@@ -22,7 +31,11 @@ class StripeSDKEventFactory implements StripeEventFactoryInterface
2231

2332
protected array $responseTypeClassMapping = [
2433
PaymentIntent::class => PaymentIntentResponse::class,
25-
Charge::class => ChargeResponse::class
34+
Balance::class => PaymentIntentResponse::class,
35+
Charge::class => ChargeResponse::class,
36+
Customer::class => CustomerResponse::class,
37+
Refund::class => RefundResponse::class,
38+
SetupIntent::class => SetupIntentResponse::class,
2639
];
2740

2841
public function __construct(StripePaymentConfigsProvider $paymentConfigsProvider)
@@ -32,7 +45,6 @@ public function __construct(StripePaymentConfigsProvider $paymentConfigsProvider
3245

3346
public function createEventFromRequest(Request $request): StripeEventInterface
3447
{
35-
$data = $request->getContent();
3648
$configuredPaymentConfigs = $this->paymentConfigsProvider->getConfigs();
3749

3850
$event = null;
@@ -42,11 +54,7 @@ public function createEventFromRequest(Request $request): StripeEventInterface
4254
/** @var StripePaymentConfig $paymentConfig */
4355
foreach ($configuredPaymentConfigs as $paymentConfig) {
4456
try {
45-
$event = \Stripe\Webhook::constructEvent(
46-
$data,
47-
$request->server->get(self::STRIPE_SIGNATURE),
48-
$paymentConfig->getSigningSecret()
49-
);
57+
$event = $this->constructEvent($request, $paymentConfig);
5058
$paymentMethodConfig = $paymentConfig;
5159
break;
5260
} catch (SignatureVerificationException $exception) {
@@ -63,13 +71,6 @@ public function createEventFromRequest(Request $request): StripeEventInterface
6371
$eventObject = $event->data->object;
6472
$responseObject = $this->createResponseObject($eventObject);
6573

66-
if (null === $responseObject) {
67-
throw new \LogicException(sprintf(
68-
'Received object Type %s is not supported by Stripe Integration.',
69-
get_class($eventObject)
70-
));
71-
}
72-
7374
return new StripeEvent($event->type, $paymentMethodConfig, $responseObject);
7475
}
7576

@@ -78,10 +79,19 @@ protected function createResponseObject($eventObject): ?ResponseObjectInterface
7879
$type = get_class($eventObject);
7980
$responseObject = $this->responseTypeClassMapping[$type] ?? null;
8081

81-
if (null === $responseObject) {
82-
throw new \LogicException(sprintf('"%s" response type is not supported', $type));
82+
if ($responseObject) {
83+
return new $responseObject($eventObject->toArray());
8384
}
8485

85-
return new $responseObject($eventObject->toArray());
86+
return new UnsupportedResponse();
87+
}
88+
89+
protected function constructEvent(Request $request, StripePaymentConfig $paymentConfig): ?Event
90+
{
91+
return \Stripe\Webhook::constructEvent(
92+
$request->getContent(),
93+
$request->server->get(self::STRIPE_SIGNATURE),
94+
$paymentConfig->getSigningSecret()
95+
);
8696
}
8797
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Oro\Bundle\StripeBundle\Model;
4+
5+
/**
6+
* Used for unsupported events handling
7+
*/
8+
class UnsupportedResponse extends AbstractResponseObject implements ResponseObjectInterface
9+
{
10+
public function getStatus(): string
11+
{
12+
return 'failed';
13+
}
14+
15+
public function getIdentifier(): string
16+
{
17+
return 'null';
18+
}
19+
20+
public function getData(): array
21+
{
22+
return [];
23+
}
24+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
namespace Oro\Bundle\StripeBundle\Tests\Unit\Event;
4+
5+
use Oro\Bundle\StripeBundle\Event\StripeSDKEventFactory;
6+
use Oro\Bundle\StripeBundle\Method\Config\Provider\StripePaymentConfigsProvider;
7+
use Oro\Bundle\StripeBundle\Method\Config\StripePaymentConfig;
8+
use Oro\Bundle\StripeBundle\Model\PaymentIntentResponse;
9+
use Oro\Bundle\StripeBundle\Model\UnsupportedResponse;
10+
use PHPUnit\Framework\MockObject\MockObject;
11+
use PHPUnit\Framework\TestCase;
12+
use Stripe\Event;
13+
use Stripe\PaymentIntent;
14+
use Symfony\Component\HttpFoundation\Request;
15+
16+
class StripeSDKEventFactoryTest extends TestCase
17+
{
18+
private MockObject|StripePaymentConfigsProvider $paymentConfigsProvider;
19+
private MockObject|StripeSDKEventFactory $eventFactory;
20+
21+
protected function setUp(): void
22+
{
23+
$this->paymentConfigsProvider = $this->createMock(StripePaymentConfigsProvider::class);
24+
$this->eventFactory = $this->getMockBuilder(StripeSDKEventFactory::class)
25+
->setConstructorArgs([$this->paymentConfigsProvider])
26+
->disableOriginalClone()
27+
->disableArgumentCloning()
28+
->disallowMockingUnknownTypes()
29+
->onlyMethods(['constructEvent'])
30+
->getMock();
31+
}
32+
33+
public function testInvalidStripeEventException()
34+
{
35+
$request = Request::createFromGlobals();
36+
$config = $this->createMock(StripePaymentConfig::class);
37+
$this->paymentConfigsProvider->expects(self::once())
38+
->method('getConfigs')
39+
->willReturn([$config]);
40+
41+
$this->eventFactory->expects(self::once())
42+
->method('constructEvent')
43+
->with($request, $config)
44+
->willReturn(null);
45+
46+
self::expectException(\LogicException::class);
47+
self::expectExceptionMessage('There are no any configured Stripe payment methods available to handle event');
48+
49+
$this->eventFactory->createEventFromRequest($request);
50+
}
51+
52+
public function testNoStripePaymentConfigsException()
53+
{
54+
$request = Request::createFromGlobals();
55+
$this->paymentConfigsProvider->expects(self::once())
56+
->method('getConfigs')
57+
->willReturn([]);
58+
59+
$this->eventFactory->expects(self::never())
60+
->method('constructEvent');
61+
62+
self::expectException(\LogicException::class);
63+
self::expectExceptionMessage('There are no any configured Stripe payment methods available to handle event');
64+
65+
$this->eventFactory->createEventFromRequest($request);
66+
}
67+
68+
public function testSupportedEvent()
69+
{
70+
$request = Request::createFromGlobals();
71+
$config = $this->createMock(StripePaymentConfig::class);
72+
$this->paymentConfigsProvider->expects(self::once())
73+
->method('getConfigs')
74+
->willReturn([$config]);
75+
76+
$stripeEventValues = [
77+
'data' => (object) ['object' => new PaymentIntent()],
78+
'type' => 'customer.created',
79+
];
80+
$stripeEvent = $this->createMock(Event::class);
81+
$stripeEvent->expects(self::exactly(2))
82+
->method('__get')
83+
->willReturnCallback(function ($name) use ($stripeEventValues) {
84+
return $stripeEventValues[$name];
85+
});
86+
87+
$this->eventFactory->expects(self::once())
88+
->method('constructEvent')
89+
->with($request, $config)
90+
->willReturn($stripeEvent);
91+
92+
$event = $this->eventFactory->createEventFromRequest($request);
93+
94+
self::assertInstanceOf(PaymentIntentResponse::class, $event->getData());
95+
}
96+
public function testUnsupportedEvent()
97+
{
98+
$request = Request::createFromGlobals();
99+
$config = $this->createMock(StripePaymentConfig::class);
100+
$this->paymentConfigsProvider->expects(self::once())
101+
->method('getConfigs')
102+
->willReturn([$config]);
103+
104+
$stripeEventValues = [
105+
'data' => (object) ['object' => new \stdClass()],
106+
'type' => 'customer.created',
107+
];
108+
$stripeEvent = $this->createMock(Event::class);
109+
$stripeEvent->expects(self::exactly(2))
110+
->method('__get')
111+
->willReturnCallback(function ($name) use ($stripeEventValues) {
112+
return $stripeEventValues[$name];
113+
});
114+
115+
$this->eventFactory->expects(self::once())
116+
->method('constructEvent')
117+
->with($request, $config)
118+
->willReturn($stripeEvent);
119+
120+
$event = $this->eventFactory->createEventFromRequest($request);
121+
122+
self::assertInstanceOf(UnsupportedResponse::class, $event->getData());
123+
}
124+
}

0 commit comments

Comments
 (0)