Skip to content

Commit

Permalink
Merge pull request #36 from FLUX-SE/expire-session-instead-of-cancel-…
Browse files Browse the repository at this point in the history
…payment-intent

Expire session instead of cancel payment intent
  • Loading branch information
Prometee authored Sep 20, 2022
2 parents 8e23fa3 + ea48106 commit 524dfc2
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 40 deletions.
133 changes: 119 additions & 14 deletions spec/Extension/CancelExistingPaymentIntentExtensionSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,41 @@

namespace spec\FluxSE\SyliusPayumStripePlugin\Extension;

use FluxSE\PayumStripe\Request\Api\Resource\AllInterface;
use FluxSE\PayumStripe\Request\Api\Resource\CustomCallInterface;
use FluxSE\SyliusPayumStripePlugin\Action\ConvertPaymentActionInterface;
use FluxSE\SyliusPayumStripePlugin\Factory\CancelPaymentIntentRequestFactoryInterface;
use FluxSE\SyliusPayumStripePlugin\Factory\AllSessionRequestFactoryInterface;
use FluxSE\SyliusPayumStripePlugin\Factory\ExpireSessionRequestFactoryInterface;
use Payum\Core\Action\ActionInterface;
use Payum\Core\Extension\Context;
use Payum\Core\Extension\ExtensionInterface;
use Payum\Core\GatewayInterface;
use Payum\Core\Request\Convert;
use PhpSpec\ObjectBehavior;
use Stripe\Checkout\Session;
use Stripe\Collection;
use Stripe\PaymentIntent;
use Stripe\SetupIntent;
use Sylius\Component\Core\Model\PaymentInterface;

final class CancelExistingPaymentIntentExtensionSpec extends ObjectBehavior
{
public function let(
CancelPaymentIntentRequestFactoryInterface $cancelPaymentIntentRequestFactory
ExpireSessionRequestFactoryInterface $expireSessionRequestFactory,
AllSessionRequestFactoryInterface $allSessionRequestFactory
): void {
$this->beConstructedWith($cancelPaymentIntentRequestFactory);
$this->beConstructedWith(
$expireSessionRequestFactory,
$allSessionRequestFactory
);
}

public function it_is_initializable(): void
{
$this->shouldBeAnInstanceOf(ExtensionInterface::class);
}

public function it_do_nothing_when_action_is_not_the_convert_payment_action_targeted(
public function it_does_nothing_when_action_is_not_the_convert_payment_action_targeted(
Context $context,
ActionInterface $action
): void {
Expand All @@ -39,7 +47,7 @@ public function it_do_nothing_when_action_is_not_the_convert_payment_action_targ
$this->onExecute($context);
}

public function it_do_nothing_when_payment_details_are_empty(
public function it_does_nothing_when_payment_details_are_empty(
Context $context,
ConvertPaymentActionInterface $action,
Convert $request,
Expand All @@ -54,7 +62,7 @@ public function it_do_nothing_when_payment_details_are_empty(
$this->onExecute($context);
}

public function it_do_nothing_when_payment_details_are_something_else_than_a_payment_intent(
public function it_does_nothing_when_payment_details_are_something_else_than_a_payment_intent(
Context $context,
ConvertPaymentActionInterface $action,
Convert $request,
Expand All @@ -71,14 +79,14 @@ public function it_do_nothing_when_payment_details_are_something_else_than_a_pay
$this->onExecute($context);
}

public function it_found_a_previous_payment_intent_and_cancel_it(
public function it_doesnt_found_the_related_session_and_do_nothing(
Context $context,
ConvertPaymentActionInterface $action,
Convert $request,
PaymentInterface $payment,
GatewayInterface $gateway,
CancelPaymentIntentRequestFactoryInterface $cancelPaymentIntentRequestFactory,
CustomCallInterface $cancelPaymentIntentRequest
AllSessionRequestFactoryInterface $allSessionRequestFactory,
AllInterface $allSessionRequest
): void {
$context->getAction()->willReturn($action);
$context->getRequest()->willReturn($request);
Expand All @@ -91,13 +99,110 @@ public function it_found_a_previous_payment_intent_and_cancel_it(
'object' => PaymentIntent::OBJECT_NAME,
]);

$cancelPaymentIntentRequest->beConstructedWith([$piId]);
$allSessionRequestFactory
->createNew()
->willReturn($allSessionRequest);

$cancelPaymentIntentRequestFactory
->createNew($piId)
->willReturn($cancelPaymentIntentRequest);
$allSessionRequest->setParameters([
'payment_intent' => $piId
])->shouldBeCalled();
$allSessionRequest->getApiResources()->willReturn(Collection::constructFrom(['data'=>[]]));

$gateway->execute($cancelPaymentIntentRequest)->shouldBeCalled();
$gateway->execute($allSessionRequest)->shouldBeCalled();

$this->onExecute($context);
}

public function it_founds_a_related_expired_session_and_does_nothing(
Context $context,
ConvertPaymentActionInterface $action,
Convert $request,
PaymentInterface $payment,
GatewayInterface $gateway,
AllSessionRequestFactoryInterface $allSessionRequestFactory,
AllInterface $allSessionRequest
): void {
$context->getAction()->willReturn($action);
$context->getRequest()->willReturn($request);
$request->getSource()->willReturn($payment);
$context->getGateway()->willReturn($gateway);

$piId = 'pi_test_0000000000000000000';
$csId = 'cs_test_0000000000000000000';
$payment->getDetails()->willReturn([
'id' => $piId,
'object' => PaymentIntent::OBJECT_NAME,
]);

$allSessionRequestFactory
->createNew()
->willReturn($allSessionRequest);

$allSessionRequest->setParameters([
'payment_intent' => $piId
])->shouldBeCalled();
$allSessionRequest->getApiResources()->willReturn(Collection::constructFrom([
'data'=>[
[
'id' => $csId,
'status' => Session::STATUS_EXPIRED,
]
]
]));

$gateway->execute($allSessionRequest)->shouldBeCalled();

$this->onExecute($context);
}

public function it_founds_a_related_session_and_expires_it(
Context $context,
ConvertPaymentActionInterface $action,
Convert $request,
PaymentInterface $payment,
GatewayInterface $gateway,
AllSessionRequestFactoryInterface $allSessionRequestFactory,
AllInterface $allSessionRequest,
ExpireSessionRequestFactoryInterface $expireSessionRequestFactory,
CustomCallInterface $expireSessionRequest
): void {
$context->getAction()->willReturn($action);
$context->getRequest()->willReturn($request);
$request->getSource()->willReturn($payment);
$context->getGateway()->willReturn($gateway);

$piId = 'pi_test_0000000000000000000';
$csId = 'cs_test_0000000000000000000';
$payment->getDetails()->willReturn([
'id' => $piId,
'object' => PaymentIntent::OBJECT_NAME,
]);

$allSessionRequestFactory
->createNew()
->willReturn($allSessionRequest);

$allSessionRequest->setParameters([
'payment_intent' => $piId
])->shouldBeCalled();
$allSessionRequest->getApiResources()->willReturn(Collection::constructFrom([
'data'=>[
[
'id' => $csId,
'status' => Session::STATUS_OPEN,
]
]
]));

$gateway->execute($allSessionRequest)->shouldBeCalled();

$expireSessionRequest->beConstructedWith([$csId]);

$expireSessionRequestFactory
->createNew($csId)
->willReturn($expireSessionRequest);

$gateway->execute($expireSessionRequest)->shouldBeCalled();

$this->onExecute($context);
}
Expand Down
60 changes: 43 additions & 17 deletions src/Extension/CancelExistingPaymentIntentExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,40 @@
namespace FluxSE\SyliusPayumStripePlugin\Extension;

use FluxSE\SyliusPayumStripePlugin\Action\ConvertPaymentActionInterface;
use FluxSE\SyliusPayumStripePlugin\Factory\CancelPaymentIntentRequestFactoryInterface;
use FluxSE\SyliusPayumStripePlugin\Factory\AllSessionRequestFactoryInterface;
use FluxSE\SyliusPayumStripePlugin\Factory\ExpireSessionRequestFactoryInterface;
use Payum\Core\Extension\Context;
use Payum\Core\Extension\ExtensionInterface;
use Payum\Core\Request\Convert;
use Stripe\Exception\ApiErrorException;
use Stripe\Checkout\Session;
use Stripe\Collection;
use Stripe\PaymentIntent;
use Sylius\Component\Core\Model\PaymentInterface;

/**
* This extension will cancel a PaymentIntent if there is an existant one
* inside the payment details
*
* UPDATE [09/2022] : Instead of canceling the PaymentIntent now it will Expire the related session
*
* @see https://stripe.com/docs/api/payment_intents/cancel
* You cannot cancel the PaymentIntent for a Checkout Session. Expire the Checkout Session instead
* @see https://github.com/FLUX-SE/SyliusPayumStripePlugin/issues/32
*/
final class CancelExistingPaymentIntentExtension implements ExtensionInterface
{
/** @var CancelPaymentIntentRequestFactoryInterface */
private $cancelPaymentIntentRequestFactory;
/** @var ExpireSessionRequestFactoryInterface */
private $expireSessionRequestFactory;

public function __construct(CancelPaymentIntentRequestFactoryInterface $cancelPaymentIntentRequestFactory)
{
$this->cancelPaymentIntentRequestFactory = $cancelPaymentIntentRequestFactory;
/** @var AllSessionRequestFactoryInterface */
private $allSessionRequestFactory;

public function __construct(
ExpireSessionRequestFactoryInterface $expireSessionRequestFactory,
AllSessionRequestFactoryInterface $allSessionRequestFactory
) {
$this->expireSessionRequestFactory = $expireSessionRequestFactory;
$this->allSessionRequestFactory = $allSessionRequestFactory;
}

public function onPreExecute(Context $context): void
Expand Down Expand Up @@ -63,17 +77,29 @@ public function onExecute(Context $context): void
}

$gateway = $context->getGateway();
$cancelPaymentIntentRequest = $this->cancelPaymentIntentRequestFactory->createNew($id);

try {
$gateway->execute($cancelPaymentIntentRequest);
} catch (ApiErrorException $e) {
// Avoid error message like :
// "You cannot cancel this PaymentIntent because it has a status of canceled.
// Only a PaymentIntent with one of the following statuses may be canceled:
// requires_payment_method, requires_capture, requires_confirmation,
// requires_action, processing."

//Retrieve the corresponding Session
$allSessionRequest = $this->allSessionRequestFactory->createNew();
$allSessionRequest->setParameters([
'payment_intent' => $id,
]);

$gateway->execute($allSessionRequest);

/** @var Collection $sessions */
$sessions = $allSessionRequest->getApiResources();
/** @var Session|null $session */
$session = $sessions->first();
if (null === $session) {
return;
}

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

$expireSessionRequest = $this->expireSessionRequestFactory->createNew($session->id);
$gateway->execute($expireSessionRequest);
}

public function onPostExecute(Context $context): void
Expand Down
16 changes: 16 additions & 0 deletions src/Factory/AllSessionRequestFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace FluxSE\SyliusPayumStripePlugin\Factory;

use FluxSE\PayumStripe\Request\Api\Resource\AllInterface;
use FluxSE\PayumStripe\Request\Api\Resource\AllSession;

final class AllSessionRequestFactory implements AllSessionRequestFactoryInterface
{
public function createNew(): AllInterface
{
return new AllSession();
}
}
12 changes: 12 additions & 0 deletions src/Factory/AllSessionRequestFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace FluxSE\SyliusPayumStripePlugin\Factory;

use FluxSE\PayumStripe\Request\Api\Resource\AllInterface;

interface AllSessionRequestFactoryInterface
{
public function createNew(): AllInterface;
}
16 changes: 16 additions & 0 deletions src/Factory/ExpireSessionRequestFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace FluxSE\SyliusPayumStripePlugin\Factory;

use FluxSE\PayumStripe\Request\Api\Resource\CustomCallInterface;
use FluxSE\PayumStripe\Request\Api\Resource\ExpireSession;

final class ExpireSessionRequestFactory implements ExpireSessionRequestFactoryInterface
{
public function createNew(string $id): CustomCallInterface
{
return new ExpireSession($id);
}
}
12 changes: 12 additions & 0 deletions src/Factory/ExpireSessionRequestFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace FluxSE\SyliusPayumStripePlugin\Factory;

use FluxSE\PayumStripe\Request\Api\Resource\CustomCallInterface;

interface ExpireSessionRequestFactoryInterface
{
public function createNew(string $id): CustomCallInterface;
}
8 changes: 7 additions & 1 deletion src/Resources/config/services/factory.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ services:
class: FluxSE\SyliusPayumStripePlugin\Factory\CaptureRequestFactory

flux_se.sylius_payum_stripe.factory.cancel_payment_intent_request:
class: FluxSE\SyliusPayumStripePlugin\Factory\CancelPaymentIntentRequestFactory
class: FluxSE\SyliusPayumStripePlugin\Factory\CancelPaymentIntentRequestFactory

flux_se.sylius_payum_stripe.factory.all_session_request:
class: FluxSE\SyliusPayumStripePlugin\Factory\AllSessionRequestFactory

flux_se.sylius_payum_stripe.factory.expire_session_request:
class: FluxSE\SyliusPayumStripePlugin\Factory\ExpireSessionRequestFactory
5 changes: 3 additions & 2 deletions src/Resources/config/services/payum.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ services:
public: true
class: FluxSE\SyliusPayumStripePlugin\Extension\CancelExistingPaymentIntentExtension
arguments:
$cancelPaymentIntentRequestFactory: '@flux_se.sylius_payum_stripe.factory.cancel_payment_intent_request'
$expireSessionRequestFactory: '@flux_se.sylius_payum_stripe.factory.expire_session_request'
$allSessionRequestFactory: '@flux_se.sylius_payum_stripe.factory.all_session_request'
tags:
- name: payum.extension
factory: stripe_checkout_session
alias: flux_se.sylius_payum_stripe.extension.cancel_existing_payment_intent
alias: flux_se.sylius_payum_stripe.extension.cancel_existing_payment_intent
Loading

0 comments on commit 524dfc2

Please sign in to comment.