Skip to content

Commit

Permalink
BB-22354: Stripe - Invalid processing of unsupported events
Browse files Browse the repository at this point in the history
  • Loading branch information
dsyrvachov authored Apr 27, 2023
1 parent 6e5b815 commit 5ee4fdd
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 17 deletions.
44 changes: 27 additions & 17 deletions src/Oro/Bundle/StripeBundle/Event/StripeSDKEventFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
use Oro\Bundle\StripeBundle\Method\Config\Provider\StripePaymentConfigsProvider;
use Oro\Bundle\StripeBundle\Method\Config\StripePaymentConfig;
use Oro\Bundle\StripeBundle\Model\ChargeResponse;
use Oro\Bundle\StripeBundle\Model\CustomerResponse;
use Oro\Bundle\StripeBundle\Model\PaymentIntentResponse;
use Oro\Bundle\StripeBundle\Model\RefundResponse;
use Oro\Bundle\StripeBundle\Model\ResponseObjectInterface;
use Oro\Bundle\StripeBundle\Model\SetupIntentResponse;
use Oro\Bundle\StripeBundle\Model\UnsupportedResponse;
use Stripe\Balance;
use Stripe\Charge;
use Stripe\Customer;
use Stripe\Event;
use Stripe\Exception\SignatureVerificationException;
use Stripe\PaymentIntent;
use Stripe\Refund;
use Stripe\SetupIntent;
use Symfony\Component\HttpFoundation\Request;

/**
Expand All @@ -22,7 +31,11 @@ class StripeSDKEventFactory implements StripeEventFactoryInterface

protected array $responseTypeClassMapping = [
PaymentIntent::class => PaymentIntentResponse::class,
Charge::class => ChargeResponse::class
Balance::class => PaymentIntentResponse::class,
Charge::class => ChargeResponse::class,
Customer::class => CustomerResponse::class,
Refund::class => RefundResponse::class,
SetupIntent::class => SetupIntentResponse::class,
];

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

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

$event = null;
Expand All @@ -42,11 +54,7 @@ public function createEventFromRequest(Request $request): StripeEventInterface
/** @var StripePaymentConfig $paymentConfig */
foreach ($configuredPaymentConfigs as $paymentConfig) {
try {
$event = \Stripe\Webhook::constructEvent(
$data,
$request->server->get(self::STRIPE_SIGNATURE),
$paymentConfig->getSigningSecret()
);
$event = $this->constructEvent($request, $paymentConfig);
$paymentMethodConfig = $paymentConfig;
break;
} catch (SignatureVerificationException $exception) {
Expand All @@ -63,13 +71,6 @@ public function createEventFromRequest(Request $request): StripeEventInterface
$eventObject = $event->data->object;
$responseObject = $this->createResponseObject($eventObject);

if (null === $responseObject) {
throw new \LogicException(sprintf(
'Received object Type %s is not supported by Stripe Integration.',
get_class($eventObject)
));
}

return new StripeEvent($event->type, $paymentMethodConfig, $responseObject);
}

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

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

return new $responseObject($eventObject->toArray());
return new UnsupportedResponse();
}

protected function constructEvent(Request $request, StripePaymentConfig $paymentConfig): ?Event
{
return \Stripe\Webhook::constructEvent(
$request->getContent(),
$request->server->get(self::STRIPE_SIGNATURE),
$paymentConfig->getSigningSecret()
);
}
}
24 changes: 24 additions & 0 deletions src/Oro/Bundle/StripeBundle/Model/UnsupportedResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Oro\Bundle\StripeBundle\Model;

/**
* Used for unsupported events handling
*/
class UnsupportedResponse extends AbstractResponseObject implements ResponseObjectInterface
{
public function getStatus(): string
{
return 'failed';
}

public function getIdentifier(): string
{
return 'null';
}

public function getData(): array
{
return [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace Oro\Bundle\StripeBundle\Tests\Unit\Event;

use Oro\Bundle\StripeBundle\Event\StripeSDKEventFactory;
use Oro\Bundle\StripeBundle\Method\Config\Provider\StripePaymentConfigsProvider;
use Oro\Bundle\StripeBundle\Method\Config\StripePaymentConfig;
use Oro\Bundle\StripeBundle\Model\PaymentIntentResponse;
use Oro\Bundle\StripeBundle\Model\UnsupportedResponse;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Stripe\Event;
use Stripe\PaymentIntent;
use Symfony\Component\HttpFoundation\Request;

class StripeSDKEventFactoryTest extends TestCase
{
private MockObject|StripePaymentConfigsProvider $paymentConfigsProvider;
private MockObject|StripeSDKEventFactory $eventFactory;

protected function setUp(): void
{
$this->paymentConfigsProvider = $this->createMock(StripePaymentConfigsProvider::class);
$this->eventFactory = $this->getMockBuilder(StripeSDKEventFactory::class)
->setConstructorArgs([$this->paymentConfigsProvider])
->disableOriginalClone()
->disableArgumentCloning()
->disallowMockingUnknownTypes()
->onlyMethods(['constructEvent'])
->getMock();
}

public function testInvalidStripeEventException()
{
$request = Request::createFromGlobals();
$config = $this->createMock(StripePaymentConfig::class);
$this->paymentConfigsProvider->expects(self::once())
->method('getConfigs')
->willReturn([$config]);

$this->eventFactory->expects(self::once())
->method('constructEvent')
->with($request, $config)
->willReturn(null);

self::expectException(\LogicException::class);
self::expectExceptionMessage('There are no any configured Stripe payment methods available to handle event');

$this->eventFactory->createEventFromRequest($request);
}

public function testNoStripePaymentConfigsException()
{
$request = Request::createFromGlobals();
$this->paymentConfigsProvider->expects(self::once())
->method('getConfigs')
->willReturn([]);

$this->eventFactory->expects(self::never())
->method('constructEvent');

self::expectException(\LogicException::class);
self::expectExceptionMessage('There are no any configured Stripe payment methods available to handle event');

$this->eventFactory->createEventFromRequest($request);
}

public function testSupportedEvent()
{
$request = Request::createFromGlobals();
$config = $this->createMock(StripePaymentConfig::class);
$this->paymentConfigsProvider->expects(self::once())
->method('getConfigs')
->willReturn([$config]);

$stripeEventValues = [
'data' => (object) ['object' => new PaymentIntent()],
'type' => 'customer.created',
];
$stripeEvent = $this->createMock(Event::class);
$stripeEvent->expects(self::exactly(2))
->method('__get')
->willReturnCallback(function ($name) use ($stripeEventValues) {
return $stripeEventValues[$name];
});

$this->eventFactory->expects(self::once())
->method('constructEvent')
->with($request, $config)
->willReturn($stripeEvent);

$event = $this->eventFactory->createEventFromRequest($request);

self::assertInstanceOf(PaymentIntentResponse::class, $event->getData());
}
public function testUnsupportedEvent()
{
$request = Request::createFromGlobals();
$config = $this->createMock(StripePaymentConfig::class);
$this->paymentConfigsProvider->expects(self::once())
->method('getConfigs')
->willReturn([$config]);

$stripeEventValues = [
'data' => (object) ['object' => new \stdClass()],
'type' => 'customer.created',
];
$stripeEvent = $this->createMock(Event::class);
$stripeEvent->expects(self::exactly(2))
->method('__get')
->willReturnCallback(function ($name) use ($stripeEventValues) {
return $stripeEventValues[$name];
});

$this->eventFactory->expects(self::once())
->method('constructEvent')
->with($request, $config)
->willReturn($stripeEvent);

$event = $this->eventFactory->createEventFromRequest($request);

self::assertInstanceOf(UnsupportedResponse::class, $event->getData());
}
}

0 comments on commit 5ee4fdd

Please sign in to comment.