Merge pull request #215 from goteo/feat/update-gateway-api-resource-shape

feat: update gateway api resource shape
This commit is contained in:
2026-07-09 15:01:13 +02:00
committed by GitHub
17 changed files with 123 additions and 54 deletions
+30
View File
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260709122602 extends AbstractMigration
{
public function getDescription(): string
{
return 'Change gateway_name to gateway_id';
}
public function up(Schema $schema): void
{
$this->addSql('DROP INDEX IDX_AF382D4E3D4E91C8BF396750 ON checkout');
$this->addSql('ALTER TABLE checkout CHANGE gateway_name gateway_id VARCHAR(255) NOT NULL');
$this->addSql('CREATE INDEX IDX_AF382D4EBF396750 ON checkout (id)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX IDX_AF382D4EBF396750 ON checkout');
$this->addSql('ALTER TABLE checkout CHANGE gateway_id gateway_name VARCHAR(255) NOT NULL');
$this->addSql('CREATE INDEX IDX_AF382D4E3D4E91C8BF396750 ON checkout (gateway_name, id)');
}
}
@@ -12,7 +12,7 @@ use App\Gateway\CheckoutStatus;
use App\Gateway\GatewayLink;
use App\Gateway\RefundStrategy;
use App\Gateway\Tracking;
use App\Mapping\Transformer\GatewayNameMapTransformer;
use App\Mapping\Transformer\GatewayIdMapTransformer;
use App\State\ApiResourceStateProvider;
use App\State\Gateway\CheckoutStateProcessor;
use AutoMapper\Attribute\MapFrom;
@@ -47,8 +47,8 @@ class CheckoutApiResource
* The desired Gateway to checkout with.
*/
#[Assert\NotBlank()]
#[MapFrom(property: 'gatewayName', transformer: GatewayNameMapTransformer::class)]
#[MapTo(property: 'gatewayName', transformer: 'source.gateway.name')]
#[MapFrom(property: 'gatewayId', transformer: GatewayIdMapTransformer::class)]
#[MapTo(property: 'gatewayId', transformer: 'source.gateway.id')]
public GatewayApiResource $gateway;
/**
@@ -21,9 +21,16 @@ use App\State\Gateway\GatewayStateProvider;
class GatewayApiResource
{
#[API\ApiProperty(identifier: true)]
public string $id;
/**
* The publicly known name of this payment method.
*/
public string $name;
/**
* The types of GatewayCheckout this Gateway can process.
*
* @var array<int, \App\Gateway\ChargeType>
*/
public array $supports;
+8 -8
View File
@@ -117,7 +117,7 @@ class InvestsPump implements PumpInterface
$checkout->setOrigin($user->getAccounting());
$checkout->setStatus($this->getCheckoutStatus($record));
$checkout->setGatewayName($this->getCheckoutGateway($record));
$checkout->setGatewayId($this->getCheckoutGatewayId($record));
$checkout->setReturnUrl(self::PLATFORM_RETURN_URL);
foreach ($this->getCheckoutTrackings($record) as $tracking) {
@@ -396,21 +396,21 @@ class InvestsPump implements PumpInterface
}
}
private function getCheckoutGateway(array $record): string
private function getCheckoutGatewayId(array $record): string
{
switch ($record['method']) {
case 'tpv':
return CecaGateway::getName();
return CecaGateway::getId();
case 'paypal':
return PaypalGateway::getName();
return PaypalGateway::getId();
case 'pool':
return WalletGateway::getName();
return WalletGateway::getId();
case 'cash':
return CashGateway::getName();
return CashGateway::getId();
case 'drop':
return DropGateway::getName();
return DropGateway::getId();
case 'stripe_subscription':
return StripeGateway::getName();
return StripeGateway::getId();
default:
return '';
}
+3 -3
View File
@@ -7,7 +7,7 @@ use App\ApiResource\Accounting\AccountingApiResource;
use App\ApiResource\Gateway\GatewayApiResource;
use App\Entity\Gateway\Checkout;
use App\Gateway\RefundStrategy;
use App\Mapping\Transformer\GatewayNameMapTransformer;
use App\Mapping\Transformer\GatewayIdMapTransformer;
use AutoMapper\Attribute\MapFrom;
use AutoMapper\Attribute\MapTo;
use Symfony\Component\Validator\Constraints as Assert;
@@ -18,8 +18,8 @@ class CheckoutCreationDto
* The desired Gateway to checkout with.
*/
#[Assert\NotBlank()]
#[MapFrom(property: 'gatewayName', transformer: GatewayNameMapTransformer::class)]
#[MapTo(property: 'gatewayName', transformer: 'source.gateway.name')]
#[MapFrom(property: 'gatewayId', transformer: GatewayIdMapTransformer::class)]
#[MapTo(property: 'gatewayId', transformer: 'source.gateway.id')]
public GatewayApiResource $gateway;
/**
+7 -7
View File
@@ -29,7 +29,7 @@ use Symfony\Component\Validator\Constraints as Assert;
#[MapProvider(EntityMapProvider::class)]
#[Gedmo\Loggable()]
#[ORM\Index(fields: ['migratedId'])]
#[ORM\Index(fields: ['gatewayName', 'id'])]
#[ORM\Index(fields: ['gateway', 'id'])]
#[ORM\Entity(repositoryClass: CheckoutRepository::class)]
class Checkout
{
@@ -43,11 +43,11 @@ class Checkout
private ?int $id = null;
/**
* The name of the Gateway implementation to checkout with.
* The ID of the Gateway implementation to checkout with.
*/
#[Assert\NotBlank()]
#[ORM\Column(length: 255)]
private ?string $gatewayName = null;
private ?string $gatewayId = null;
/**
* The Accounting that will issue the Transactions of the GatewayCharges after a successful checkout.
@@ -114,14 +114,14 @@ class Checkout
return $this->id;
}
public function getGatewayName(): ?string
public function getGatewayId(): ?string
{
return $this->gatewayName;
return $this->gatewayId;
}
public function setGatewayName(string $gatewayName): static
public function setGatewayId(string $id): static
{
$this->gatewayName = $gatewayName;
$this->gatewayId = $id;
return $this;
}
+5 -5
View File
@@ -50,29 +50,29 @@ final class GatewayFilter extends AbstractFilter
[$alias] = $this->addJoinsForNestedProperty($property, $rootAlias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::LEFT_JOIN);
}
$aliasedField = \sprintf('%s.gatewayName', $alias);
$aliasedField = \sprintf('%s.gatewayId', $alias);
if (\count($values) > 1) {
$queryBuilder
->andWhere($queryBuilder->expr()->in($aliasedField, $parameterName))
->setParameter($parameterName, array_map(fn($v) => $this->getGatewayName($v), $values));
->setParameter($parameterName, array_map(fn($v) => $this->getGatewayId($v), $values));
return;
}
$queryBuilder
->andWhere($queryBuilder->expr()->eq($aliasedField, $parameterName))
->setParameter($parameterName, $this->getGatewayName($values[0]));
->setParameter($parameterName, $this->getGatewayId($values[0]));
}
private function getGatewayName(mixed $value): string
private function getGatewayId(mixed $value): string
{
$value = \array_slice(\explode('/', $value), -1)[0];
try {
$gateway = $this->gatewayLocator->get($value);
return $gateway::getName();
return $gateway::getId();
} catch (MissingGatewayException $e) {
throw new NotFoundHttpException($e->getMessage());
}
+6 -1
View File
@@ -11,11 +11,16 @@ use Symfony\Component\HttpFoundation\Response;
class CashGateway implements GatewayInterface
{
public static function getName(): string
public static function getId(): string
{
return 'cash';
}
public static function getName(): string
{
return 'Cash';
}
public static function getSupportedChargeTypes(): array
{
return [];
+6 -1
View File
@@ -14,11 +14,16 @@ class CecaGateway implements GatewayInterface
{
public const LEGACY_MESSAGE = 'This Gateway implementation is only meant to give support to legacy-records. DO NOT USE. DO NOT IMPLEMENT.';
public static function getName(): string
public static function getId(): string
{
return 'tpv';
}
public static function getName(): string
{
return 'Credit Card';
}
public static function getSupportedChargeTypes(): array
{
return [
+5
View File
@@ -27,6 +27,11 @@ interface GatewayInterface
/**
* @return string A short, unique, descriptive string for this Gateway
*/
public static function getId(): string;
/**
* @return string the commercially-preferred displayable name of the Gateway platform
*/
public static function getName(): string;
/**
+18 -18
View File
@@ -9,7 +9,7 @@ use App\Gateway\Exception\MissingGatewayException;
class GatewayLocator
{
/** @var GatewayInterface[] */
private array $gatewaysByName = [];
private array $gatewaysById = [];
/** @var GatewayInterface[] */
private array $gatewaysByClass = [];
@@ -20,10 +20,10 @@ class GatewayLocator
$this->gatewaysByClass[$gateway::class] = $gateway;
}
self::validateGatewayNames($this->gatewaysByClass);
self::validateGatewayIDs($this->gatewaysByClass);
foreach ($this->gatewaysByClass as $class => $gateway) {
$this->gatewaysByName[$gateway::getName()] = $gateway;
$this->gatewaysById[$gateway::getId()] = $gateway;
}
}
@@ -32,21 +32,21 @@ class GatewayLocator
*/
public function getAll(): array
{
return $this->gatewaysByName;
return $this->gatewaysById;
}
/**
* @param string $name Name of the Gateway interface implementation
* @param string $id ID of the Gateway interface implementation
*
* @throws \Exception When the `$name` does not match to that of an implemented Gateway
* @throws \Exception When the `$id` does not match to that of an implemented Gateway
*/
public function get(string $name): GatewayInterface
public function get(string $id): GatewayInterface
{
if (!\array_key_exists($name, $this->gatewaysByName)) {
throw new MissingGatewayException($name);
if (!\array_key_exists($id, $this->gatewaysById)) {
throw new MissingGatewayException($id);
}
return $this->gatewaysByName[$name];
return $this->gatewaysById[$id];
}
/**
@@ -54,31 +54,31 @@ class GatewayLocator
*/
public function getForCheckout(Checkout $checkout): GatewayInterface
{
return $this->get($checkout->getGatewayName());
return $this->get($checkout->getGatewayId());
}
/**
* Ensures the gateway names are unique for each gateway.
* Ensures the gateway IDs are unique for each gateway.
*
* @param array $gatewayClasses Fully-qualified Gateway class names
*
* @throws \Exception If there are two different Gateway classes that return the same name string
*/
private static function validateGatewayNames(array $gatewayClasses): void
private static function validateGatewayIDs(array $gatewayClasses): void
{
$gatewaysValidated = [];
foreach ($gatewayClasses as $gatewayClass) {
$gatewayName = $gatewayClass::getName();
$gatewayId = $gatewayClass::getId();
if (\array_key_exists($gatewayName, $gatewaysValidated)) {
if (\array_key_exists($gatewayId, $gatewaysValidated)) {
throw new DuplicateGatewayException(
$gatewayName,
$gatewayId,
$gatewayClass::class,
$gatewaysValidated[$gatewayName]::class
$gatewaysValidated[$gatewayId]::class
);
}
$gatewaysValidated[$gatewayName] = $gatewayClass;
$gatewaysValidated[$gatewayId] = $gatewayClass;
}
}
}
+6 -1
View File
@@ -44,11 +44,16 @@ class PaypalGateway extends AbstractGateway
private PaypalService $paypal,
) {}
public static function getName(): string
public static function getId(): string
{
return 'paypal';
}
public static function getName(): string
{
return 'PayPal';
}
public static function getSupportedChargeTypes(): array
{
return [
+6 -1
View File
@@ -37,11 +37,16 @@ class StripeGateway extends AbstractGateway
$this->stripe = new StripeClient($stripeApiKey);
}
public static function getName(): string
public static function getId(): string
{
return 'stripe';
}
public static function getName(): string
{
return 'Stripe';
}
public static function getSupportedChargeTypes(): array
{
return [
+6 -1
View File
@@ -18,11 +18,16 @@ use Symfony\Component\HttpFoundation\Response;
class WalletGateway implements GatewayInterface
{
public static function getName(): string
public static function getId(): string
{
return 'wallet';
}
public static function getName(): string
{
return 'Wallet';
}
public static function getSupportedChargeTypes(): array
{
return [
@@ -6,7 +6,7 @@ use App\ApiResource\Gateway\GatewayApiResource;
use App\Gateway\GatewayLocator;
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface;
class GatewayNameMapTransformer implements PropertyTransformerInterface
class GatewayIdMapTransformer implements PropertyTransformerInterface
{
public function __construct(
private GatewayLocator $gatewayLocator,
@@ -17,6 +17,7 @@ class GatewayNameMapTransformer implements PropertyTransformerInterface
$gateway = $this->gatewayLocator->get($value);
$resource = new GatewayApiResource();
$resource->id = $gateway::getId();
$resource->name = $gateway::getName();
$resource->supports = $gateway::getSupportedChargeTypes();
+1 -1
View File
@@ -34,7 +34,7 @@ class CheckoutService
GatewaysController::REDIRECT,
[
'type' => $type,
'gateway' => $checkout->getGatewayName(),
'gateway' => $checkout->getGatewayId(),
'checkoutId' => $checkout->getId(),
...$parameters,
],
+4 -3
View File
@@ -24,7 +24,7 @@ class GatewayStateProvider implements ProviderInterface
case API\GetCollection::class:
return $this->getGateways();
case API\Get::class:
return $this->getGateway($uriVariables['name']);
return $this->getGateway($uriVariables['id']);
default:
return $this->getGateways();
}
@@ -48,10 +48,10 @@ class GatewayStateProvider implements ProviderInterface
return $gateways;
}
private function getGateway(string $name): GatewayApiResource
private function getGateway(string $id): GatewayApiResource
{
try {
$gateway = $this->gateways->get($name);
$gateway = $this->gateways->get($id);
foreach ($gateway::getAllowedRoles() as $role) {
$isGranted = $this->security->isGranted($role, $this->security->getUser());
@@ -70,6 +70,7 @@ class GatewayStateProvider implements ProviderInterface
private function toResource(GatewayInterface $gateway): GatewayApiResource
{
$resource = new GatewayApiResource();
$resource->id = $gateway::getId();
$resource->name = $gateway::getName();
$resource->supports = $gateway::getSupportedChargeTypes();