@@ -39,7 +39,7 @@ CRYPTO_SECRET=""
|
||||
###< doctrineencryptbundle/doctrine-encrypt-bundle ###
|
||||
|
||||
###> App\Gateway\Stripe\StripeGateway ###
|
||||
STRIPE_API_KEY=""
|
||||
STRIPE_API_KEY="!ChangeMe!"
|
||||
STRIPE_WEBHOOK_SECRET=""
|
||||
###< App\Gateway\Stripe\StripeGateway ###
|
||||
|
||||
|
||||
@@ -111,5 +111,5 @@ bin/docker mariadb mysql -u root -pgoteo goteo
|
||||
|
||||
2. Grant *goteo* all privileges.
|
||||
```mysql
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'goteo'@'%'
|
||||
GRANT ALL PRIVILEGES ON *.* TO 'goteo'@'%';
|
||||
```
|
||||
|
||||
@@ -60,7 +60,7 @@ class CheckoutApiResource
|
||||
*
|
||||
* @var ChargeApiResource[]
|
||||
*/
|
||||
#[API\ApiProperty(readableLink: true)]
|
||||
#[API\ApiProperty(readableLink: true, writableLink: true)]
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Count(min: 1)]
|
||||
public array $charges = [];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\ApiResource\Matchfunding;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\Accounting\AccountingApiResource;
|
||||
@@ -72,11 +73,13 @@ class MatchCallApiResource
|
||||
* Main display title.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'partial')]
|
||||
public string $title;
|
||||
|
||||
/**
|
||||
* Long-form secondary display text.
|
||||
*/
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'partial')]
|
||||
public string $description;
|
||||
|
||||
/**
|
||||
@@ -84,10 +87,16 @@ class MatchCallApiResource
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Valid]
|
||||
#[API\ApiFilter(
|
||||
SearchFilter::class,
|
||||
strategy: 'exact',
|
||||
properties: ['territory.country', 'territory.subLvl1', 'territory.subLvl2'],
|
||||
)]
|
||||
public Territory $territory;
|
||||
|
||||
/**
|
||||
* The current status of the MatchCall.
|
||||
*/
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public MatchCallStatus $status = MatchCallStatus::InEditing;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\ApiResource\Matchfunding;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\Project\ProjectApiResource;
|
||||
@@ -40,17 +41,20 @@ class MatchCallSubmissionApiResource
|
||||
* The MatchCall to which this MatchCallSubmission belongs to.
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public MatchCallApiResource $call;
|
||||
|
||||
/**
|
||||
* The Project that applied for the MatchCall.
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public ProjectApiResource $project;
|
||||
|
||||
/**
|
||||
* The status of the Project's application for the MatchCall.\
|
||||
* Only MatchCallSubmissions with an status `accepted` will receive matchfunding.
|
||||
*/
|
||||
#[API\ApiFilter(SearchFilter::class, strategy: 'exact')]
|
||||
public MatchCallSubmissionStatus $status = MatchCallSubmissionStatus::DEFAULT;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use ApiPlatform\Doctrine\Orm\State\Options;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\ApiResource\Accounting\AccountingApiResource;
|
||||
use App\ApiResource\LocalizedApiResourceTrait;
|
||||
use App\ApiResource\Matchfunding\MatchCallSubmissionApiResource;
|
||||
use App\ApiResource\User\UserApiResource;
|
||||
use App\Dto\ProjectCreationDto;
|
||||
use App\Dto\ProjectUpdationDto;
|
||||
@@ -172,4 +173,10 @@ class ProjectApiResource
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
public array $updates;
|
||||
|
||||
/**
|
||||
* @var array<int, MatchCallSubmissionApiResource>
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
public array $matchCallSubmissions;
|
||||
}
|
||||
|
||||
@@ -20,16 +20,16 @@ class EmbeddableMoney implements MoneyInterface
|
||||
* Expressed as the minor unit, e.g: cents, pennies, etc.
|
||||
*/
|
||||
#[ORM\Column(type: 'integer', nullable: true)]
|
||||
private ?int $amount;
|
||||
public ?int $amount;
|
||||
|
||||
/**
|
||||
* 3-letter ISO 4217 currency code.
|
||||
*/
|
||||
#[ORM\Column(type: 'string', nullable: true)]
|
||||
private ?string $currency;
|
||||
public ?string $currency;
|
||||
|
||||
#[ORM\Column(type: 'json', nullable: true)]
|
||||
private ?array $conversion;
|
||||
public ?array $conversion;
|
||||
|
||||
public function __construct(
|
||||
?int $amount = null,
|
||||
|
||||
@@ -21,6 +21,11 @@ class CashGateway implements GatewayInterface
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function getAllowedRoles(): array
|
||||
{
|
||||
return ['ROLE_ADMIN'];
|
||||
}
|
||||
|
||||
public function process(Checkout $checkout): Checkout
|
||||
{
|
||||
return $checkout;
|
||||
|
||||
@@ -26,6 +26,14 @@ class CecaGateway implements GatewayInterface
|
||||
];
|
||||
}
|
||||
|
||||
public static function getAllowedRoles(): array
|
||||
{
|
||||
// This Gateway only exists for historical purposes
|
||||
// DO NOT USE
|
||||
// DO NOT IMPLEMENT
|
||||
return [''];
|
||||
}
|
||||
|
||||
public function process(Checkout $checkout): Checkout
|
||||
{
|
||||
throw new \Exception(self::LEGACY_MESSAGE);
|
||||
|
||||
@@ -34,6 +34,11 @@ interface GatewayInterface
|
||||
*/
|
||||
public static function getSupportedChargeTypes(): array;
|
||||
|
||||
/**
|
||||
* @return string[] The roles that are allowed to process Checkouts with this Gateway
|
||||
*/
|
||||
public static function getAllowedRoles(): array;
|
||||
|
||||
/**
|
||||
* Connects with the payment gateway and creates a checkout session so the gateway can process the payment.
|
||||
*
|
||||
|
||||
@@ -28,7 +28,7 @@ class GatewayLocator
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <string, GatewayInterface>
|
||||
* @return array<string, GatewayInterface>
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
|
||||
@@ -56,6 +56,11 @@ class PaypalGateway extends AbstractGateway
|
||||
];
|
||||
}
|
||||
|
||||
public static function getAllowedRoles(): array
|
||||
{
|
||||
return ['PUBLIC_ACCESS'];
|
||||
}
|
||||
|
||||
public function process(Checkout $checkout): Checkout
|
||||
{
|
||||
$order = $this->paypal->postOrder([
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Entity\Gateway\Charge;
|
||||
use App\Entity\Gateway\Checkout;
|
||||
use App\Entity\Gateway\Tracking;
|
||||
use App\Entity\Project\Project;
|
||||
use App\Entity\Tipjar;
|
||||
use App\Entity\User\User;
|
||||
use App\Gateway\AbstractGateway;
|
||||
use App\Gateway\ChargeType;
|
||||
@@ -25,6 +26,8 @@ class StripeGateway extends AbstractGateway
|
||||
{
|
||||
public const TRACKING_TITLE_CHECKOUT = 'Stripe Checkout Session ID';
|
||||
|
||||
public const SUPPORTED_TARGETS = [Project::class, Tipjar::class];
|
||||
|
||||
private StripeClient $stripe;
|
||||
|
||||
public function __construct(
|
||||
@@ -47,6 +50,11 @@ class StripeGateway extends AbstractGateway
|
||||
];
|
||||
}
|
||||
|
||||
public static function getAllowedRoles(): array
|
||||
{
|
||||
return ['PUBLIC_ACCESS'];
|
||||
}
|
||||
|
||||
public function process(Checkout $checkout): Checkout
|
||||
{
|
||||
$session = $this->stripe->checkout->sessions->create([
|
||||
@@ -170,16 +178,22 @@ class StripeGateway extends AbstractGateway
|
||||
|
||||
private function getStripeProduct(Charge $charge)
|
||||
{
|
||||
/** @var Project|Tipjar */
|
||||
$target = $charge->getTarget()->getOwner();
|
||||
|
||||
if (!$target instanceof Project) {
|
||||
if (!\in_array($target::class, self::SUPPORTED_TARGETS)) {
|
||||
throw new \Exception(\sprintf(
|
||||
"Charges with Stripe must be to Projects, instance of '%s' supplied",
|
||||
$target::class
|
||||
"Charges instance of class '%s' not in supported Stripe targets: %s",
|
||||
$target::class,
|
||||
join(',', self::SUPPORTED_TARGETS)
|
||||
));
|
||||
}
|
||||
|
||||
$id = \sprintf('P%d', $target->getId());
|
||||
$id = \sprintf(
|
||||
'%s%d',
|
||||
(new \ReflectionClass($target::class))->getShortName(),
|
||||
$target->getId()
|
||||
);
|
||||
|
||||
try {
|
||||
$product = $this->stripe->products->retrieve($id);
|
||||
@@ -188,13 +202,10 @@ class StripeGateway extends AbstractGateway
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$name = $target->getTitle();
|
||||
$description = $target->getSubtitle();
|
||||
|
||||
$product = $this->stripe->products->create([
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'name' => $charge->getTitle(),
|
||||
'description' => $charge->getDescription(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,11 @@ class WalletGateway implements GatewayInterface
|
||||
];
|
||||
}
|
||||
|
||||
public static function getAllowedRoles(): array
|
||||
{
|
||||
return ['ROLE_USER'];
|
||||
}
|
||||
|
||||
public function __construct(
|
||||
private WalletService $wallet,
|
||||
private MoneyService $money,
|
||||
|
||||
@@ -8,12 +8,14 @@ use ApiPlatform\State\ProviderInterface;
|
||||
use App\ApiResource\Gateway\GatewayApiResource;
|
||||
use App\Gateway\GatewayInterface;
|
||||
use App\Gateway\GatewayLocator;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class GatewayStateProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private GatewayLocator $gateways,
|
||||
private Security $security,
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
@@ -23,6 +25,8 @@ class GatewayStateProvider implements ProviderInterface
|
||||
return $this->getGateways();
|
||||
case API\Get::class:
|
||||
return $this->getGateway($uriVariables['name']);
|
||||
default:
|
||||
return $this->getGateways();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +34,14 @@ class GatewayStateProvider implements ProviderInterface
|
||||
{
|
||||
$gateways = [];
|
||||
foreach ($this->gateways->getAll() as $gateway) {
|
||||
foreach ($gateway::getAllowedRoles() as $role) {
|
||||
$isGranted = $this->security->isGranted($role, $this->security->getUser());
|
||||
|
||||
if (!$isGranted) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$gateways[] = $this->toResource($gateway);
|
||||
}
|
||||
|
||||
@@ -41,6 +53,14 @@ class GatewayStateProvider implements ProviderInterface
|
||||
try {
|
||||
$gateway = $this->gateways->get($name);
|
||||
|
||||
foreach ($gateway::getAllowedRoles() as $role) {
|
||||
$isGranted = $this->security->isGranted($role, $this->security->getUser());
|
||||
|
||||
if (!$isGranted) {
|
||||
throw new \Exception('Unauthorized Gateway role');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->toResource($gateway);
|
||||
} catch (\Exception $e) {
|
||||
throw new NotFoundHttpException('Not Found');
|
||||
|
||||
Reference in New Issue
Block a user