Skip to content

Commit

Permalink
Make CancelAction legacy because the Session is no longer creating it
Browse files Browse the repository at this point in the history
  • Loading branch information
Prometee committed Sep 28, 2023
1 parent 6039c6b commit a45565b
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 105 deletions.
27 changes: 6 additions & 21 deletions src/Action/StripeCheckoutSession/CancelAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
namespace FluxSE\PayumStripe\Action\StripeCheckoutSession;

use ArrayAccess;
use FluxSE\PayumStripe\Request\Api\Resource\AllSession;
use FluxSE\PayumStripe\Request\Api\Resource\ExpireSession;
use FluxSE\PayumStripe\Request\Api\Resource\RetrieveSession;
use Payum\Core\Action\ActionInterface;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\Exception\RequestNotSupportedException;
Expand All @@ -28,27 +28,13 @@ public function execute($request): void

$model = ArrayObject::ensureArrayObject($request->getModel());

// @link https://stripe.com/docs/api/payment_intents/cancel
// > You cannot cancel the PaymentIntent for a Checkout Session.
// > Expire the Checkout Session instead.
$allSessionRequest = new AllSession(
[
'payment_intent' => $model['id'],
]
);
$this->gateway->execute($allSessionRequest);

$sessions = $allSessionRequest->getApiResources();
$session = $sessions->first();
if (!$session instanceof Session) {
return;
}

if ($session->status !== Session::STATUS_OPEN) {
if ($model['status'] !== Session::STATUS_OPEN) {
return;
}

$this->gateway->execute(new ExpireSession($session->id));
/** @var string $id */
$id = $model['id'];
$this->gateway->execute(new ExpireSession($id));
}

public function supports($request): bool
Expand All @@ -62,7 +48,6 @@ public function supports($request): bool
return false;
}

// if capture_method=automatic it means the payment intent was created from a checkout session without authorization
return $model->offsetExists('capture_method') && $model->offsetGet('capture_method') === 'automatic';
return $model->offsetExists('object') && $model->offsetGet('object') === Session::OBJECT_NAME;
}
}
73 changes: 73 additions & 0 deletions src/Action/StripeCheckoutSession/LegacyCancelAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace FluxSE\PayumStripe\Action\StripeCheckoutSession;

use ArrayAccess;
use FluxSE\PayumStripe\Request\Api\Resource\AllSession;
use FluxSE\PayumStripe\Request\Api\Resource\ExpireSession;
use Payum\Core\Action\ActionInterface;
use Payum\Core\Bridge\Spl\ArrayObject;
use Payum\Core\Exception\RequestNotSupportedException;
use Payum\Core\GatewayAwareInterface;
use Payum\Core\GatewayAwareTrait;
use Payum\Core\Request\Cancel;
use Stripe\Checkout\Session;
use Stripe\PaymentIntent;

class LegacyCancelAction implements ActionInterface, GatewayAwareInterface
{
use GatewayAwareTrait;

/**
* @param Cancel $request
*/
public function execute($request): void
{
RequestNotSupportedException::assertSupports($this, $request);

$model = ArrayObject::ensureArrayObject($request->getModel());

// @link https://stripe.com/docs/api/payment_intents/cancel
// > You cannot cancel the PaymentIntent for a Checkout Session.
// > Expire the Checkout Session instead.
$allSessionRequest = new AllSession(
[
'payment_intent' => $model['id'],
]
);
$this->gateway->execute($allSessionRequest);

$sessions = $allSessionRequest->getApiResources();
$session = $sessions->first();
if (!$session instanceof Session) {
return;
}

if ($session->status !== Session::STATUS_OPEN) {
return;
}

$this->gateway->execute(new ExpireSession($session->id));
}

public function supports($request): bool
{
if (!$request instanceof Cancel) {
return false;
}

$model = $request->getModel();
if (!$model instanceof ArrayAccess) {
return false;
}

if (!$model->offsetExists('object') || $model->offsetGet('object') !== PaymentIntent::OBJECT_NAME) {
return false;
}

// if capture_method=automatic it means the payment intent was created from a checkout session without authorization
return $model->offsetExists('capture_method') && $model->offsetGet('capture_method') === 'automatic';
}
}
105 changes: 21 additions & 84 deletions tests/Action/StripeCheckoutSession/CancelActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,113 +28,50 @@ public function testShouldSupportOnlyCaptureAndArrayAccessModelWithCaptureMethod
{
$action = new CancelAction();

$this->assertTrue($action->supports(new Cancel(['capture_method' => 'automatic'])));
$this->assertFalse($action->supports(new Cancel(['capture_method' => 'manual'])));
$this->assertTrue($action->supports(new Cancel(['object' => Session::OBJECT_NAME])));
$this->assertFalse($action->supports(new Cancel([])));
$this->assertFalse($action->supports(new Cancel(['object' => ''])));
$this->assertFalse($action->supports(new Cancel(['object' => 'foo'])));
$this->assertFalse($action->supports(new Cancel(null)));
$this->assertFalse($action->supports(new Authorize(['capture_method' => 'manual'])));
}

public function testDoesNothingForNotACheckoutSession(): void
{
$request = new Cancel(['capture_method' => 'automatic', 'id' => 'pi_abc123']);
$action = new CancelAction();

$allSessionRequest = new AllSession(['payment_intent' => 'pi_abc123']);
$gatewayMock = $this->createGatewayMock();
$gatewayMock
->expects($this->once())
->method('execute')
->with($allSessionRequest)
->willReturn(
$this->returnCallback(
function (AllSession $request) {
$sessions = Collection::emptyCollection();
$request->setApiResources($sessions);
return true;
}
)
);

$action->setGateway($gatewayMock);
$action->execute($request);
}

public function testDoesNothingForNotAnOpenedCheckoutSession(): void
{
$request = new Cancel(['capture_method' => 'automatic', 'id' => 'pi_abc123']);
$request = new Cancel([
'object' => Session::OBJECT_NAME,
'id' => 'cs_abc123',
'status' => Session::STATUS_COMPLETE,
]);
$action = new CancelAction();

$allSessionRequest = new AllSession(['payment_intent' => 'pi_abc123']);
$gatewayMock = $this->createGatewayMock();

$gatewayMock
->expects($this->once())
->expects($this->never())
->method('execute')
->with($allSessionRequest)
->willReturn(
$this->returnCallback(
function (AllSession $request) {
$sessions = Collection::constructFrom(
[
'data' => [
[
'id' => 'cs_1',
'object' => Session::OBJECT_NAME,
'status' => Session::STATUS_COMPLETE,
],
],
]
);
$request->setApiResources($sessions);
return true;
}
)
);
;

$action->setGateway($gatewayMock);
$action->execute($request);
}

public function testExpiresAnOpenedCheckoutSession(): void
{
$request = new Cancel(['capture_method' => 'automatic', 'id' => 'pi_abc123']);
$request = new Cancel([
'object' => Session::OBJECT_NAME,
'id' => 'pi_abc123',
'status' => Session::STATUS_OPEN,
]);
$action = new CancelAction();

$gatewayMock = $this->createGatewayMock();
$gatewayMock
->expects($this->exactly(2))
->expects($this->once())
->method('execute')
->withConsecutive(
[
$this->callback(
function (AllSession $request): bool {
$this->assertSame(['payment_intent' => 'pi_abc123'], $request->getParameters());
return true;
}
),
],
[new ExpireSession('cs_1')]
)
->willReturnOnConsecutiveCalls(
$this->returnCallback(
function (AllSession $request) {
$sessions = Collection::constructFrom(
[
'data' => [
[
'id' => 'cs_1',
'object' => Session::OBJECT_NAME,
'status' => Session::STATUS_OPEN,
],
],
]
);
$request->setApiResources($sessions);
return true;
}
),
$this->anything()
);
->willReturnCallback(function (ExpireSession $request): void {
return;
})
;

$action->setGateway($gatewayMock);
$action->execute($request);
Expand Down
Loading

0 comments on commit a45565b

Please sign in to comment.