Skip to content

Commit

Permalink
Feature: Basic membership and things around it
Browse files Browse the repository at this point in the history
  • Loading branch information
JanMikes committed Dec 30, 2024
1 parent 0a7f139 commit 843c5eb
Show file tree
Hide file tree
Showing 29 changed files with 862 additions and 15 deletions.
36 changes: 36 additions & 0 deletions migrations/Version20241230144600.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace SpeedPuzzling\Web\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241230144600 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE membership (ends_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, card_last4_digits VARCHAR(255) DEFAULT NULL, id UUID NOT NULL, stripe_subscription_id VARCHAR(255) DEFAULT NULL, stripe_plan_id VARCHAR(255) DEFAULT NULL, billing_period_ends_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, player_id UUID NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_86FFD28599E6F5DF ON membership (player_id)');
$this->addSql('ALTER TABLE membership ADD CONSTRAINT FK_86FFD28599E6F5DF FOREIGN KEY (player_id) REFERENCES player (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE player ADD stripe_customer_id VARCHAR(255) DEFAULT NULL');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE membership DROP CONSTRAINT FK_86FFD28599E6F5DF');
$this->addSql('DROP TABLE membership');
$this->addSql('ALTER TABLE player DROP stripe_customer_id');
}
}
33 changes: 33 additions & 0 deletions migrations/Version20241230144632.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace SpeedPuzzling\Web\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241230144632 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX idx_86ffd28599e6f5df');
$this->addSql('CREATE UNIQUE INDEX UNIQ_86FFD28599E6F5DF ON membership (player_id)');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX UNIQ_86FFD28599E6F5DF');
$this->addSql('CREATE INDEX idx_86ffd28599e6f5df ON membership (player_id)');
}
}
33 changes: 33 additions & 0 deletions migrations/Version20241230165038.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace SpeedPuzzling\Web\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241230165038 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE membership DROP card_last4_digits');
$this->addSql('ALTER TABLE membership DROP stripe_plan_id');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE membership ADD card_last4_digits VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE membership ADD stripe_plan_id VARCHAR(255) DEFAULT NULL');
}
}
47 changes: 47 additions & 0 deletions src/Controller/BillingPortalController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);

namespace SpeedPuzzling\Web\Controller;

use Auth0\Symfony\Models\User;
use SpeedPuzzling\Web\Services\MembershipManagement;
use SpeedPuzzling\Web\Services\RetrieveLoggedUserProfile;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

final class BillingPortalController extends AbstractController
{
public function __construct(
readonly private MembershipManagement $membershipManagement,
readonly private RetrieveLoggedUserProfile $retrieveLoggedUserProfile,
) {
}

#[Route(
path: [
'cs' => '/clenstvi/platebni-portal',
'en' => '/en/memberstip/billing-portal',
],
name: 'billing_portal',
)]
public function __invoke(#[CurrentUser] User $user): Response
{
$player = $this->retrieveLoggedUserProfile->getProfile();

if ($player === null) {
return $this->redirectToRoute('homepage');
}

$customerId = $player->stripeCustomerId;

if ($customerId === null) {
return $this->redirectToRoute('membership');
}

$portalUrl = $this->membershipManagement->getBillingPortalUrl($customerId);

return $this->redirect($portalUrl, 303);
}
}
41 changes: 41 additions & 0 deletions src/Controller/BuyMembershipController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);

namespace SpeedPuzzling\Web\Controller;

use Auth0\Symfony\Models\User;
use SpeedPuzzling\Web\Services\MembershipManagement;
use SpeedPuzzling\Web\Services\RetrieveLoggedUserProfile;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

final class BuyMembershipController extends AbstractController
{
public function __construct(
readonly private MembershipManagement $membershipManagement,
readonly private RetrieveLoggedUserProfile $retrieveLoggedUserProfile,
) {
}

#[Route(
path: [
'cs' => '/koupit-clenstvi',
'en' => '/en/buy-membership',
],
name: 'buy_membership',
)]
public function __invoke(#[CurrentUser] User $user): Response
{
$player = $this->retrieveLoggedUserProfile->getProfile();

if ($player === null) {
return $this->redirectToRoute('homepage');
}

$paymentUrl = $this->membershipManagement->getMembershipPaymentUrl();

return $this->redirect($paymentUrl, 303);
}
}
48 changes: 48 additions & 0 deletions src/Controller/MembershipController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);

namespace SpeedPuzzling\Web\Controller;

use Auth0\Symfony\Models\User;
use SpeedPuzzling\Web\Exceptions\MembershipNotFound;
use SpeedPuzzling\Web\Query\GetPlayerMembership;
use SpeedPuzzling\Web\Services\RetrieveLoggedUserProfile;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

final class MembershipController extends AbstractController
{
public function __construct(
readonly private GetPlayerMembership $getPlayerMembership,
readonly private RetrieveLoggedUserProfile $retrieveLoggedUserProfile,
) {
}

#[Route(
path: [
'cs' => '/clenstvi/',
'en' => '/en/membership',
],
name: 'membership',
)]
public function __invoke(#[CurrentUser] User $user): Response
{
$player = $this->retrieveLoggedUserProfile->getProfile();

if ($player === null) {
return $this->redirectToRoute('homepage');
}

try {
$membership = $this->getPlayerMembership->byId($player->playerId);
} catch (MembershipNotFound) {
$membership = null;
}

return $this->render('membership.html.twig', [
'membership' => $membership,
]);
}
}
7 changes: 0 additions & 7 deletions src/Controller/RemovePuzzleFromCollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,7 @@
namespace SpeedPuzzling\Web\Controller;

use Auth0\Symfony\Models\User;
use SpeedPuzzling\Web\Exceptions\CanNotFavoriteYourself;
use SpeedPuzzling\Web\Exceptions\PlayerIsAlreadyInFavorites;
use SpeedPuzzling\Web\Exceptions\PlayerIsNotInFavorites;
use SpeedPuzzling\Web\Exceptions\PlayerNotFound;
use SpeedPuzzling\Web\Exceptions\PuzzleNotFound;
use SpeedPuzzling\Web\Message\AddPlayerToFavorites;
use SpeedPuzzling\Web\Message\AddPuzzleToCollection;
use SpeedPuzzling\Web\Message\RemovePlayerFromFavorites;
use SpeedPuzzling\Web\Message\RemovePuzzleFromCollection;
use SpeedPuzzling\Web\Services\RetrieveLoggedUserProfile;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
Expand Down
23 changes: 23 additions & 0 deletions src/Controller/StripeCheckoutCancelController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);

namespace SpeedPuzzling\Web\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class StripeCheckoutCancelController extends AbstractController
{
#[Route(
path: [
'cs' => '/nakup-clenstvi-zrusen',
'en' => '/en/membership-checkout-cancel',
],
name: 'stripe_checkout_cancel',
)]
public function __invoke(null|string $code): Response
{
return $this->render('membership-checkout-cancel.html.twig');
}
}
49 changes: 49 additions & 0 deletions src/Controller/StripeCheckoutSuccessController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);

namespace SpeedPuzzling\Web\Controller;

use SpeedPuzzling\Web\Message\SubscribeMembership;
use SpeedPuzzling\Web\Services\RetrieveLoggedUserProfile;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Http\Attribute\CurrentUser;

final class StripeCheckoutSuccessController extends AbstractController
{
public function __construct(
readonly private MessageBusInterface $messageBus,
readonly private RetrieveLoggedUserProfile $retrieveLoggedUserProfile,
) {
}

#[Route(
path: [
'cs' => '/uspesny-nakup-clenstvi/{sessionId}',
'en' => '/en/membership-checkout-success/{sessionId}',
],
name: 'stripe_checkout_success',
)]
public function __invoke(#[CurrentUser] UserInterface $user, string $sessionId): Response
{
$player = $this->retrieveLoggedUserProfile->getProfile();

if ($player === null) {
return $this->redirectToRoute('my_profile');
}

$this->messageBus->dispatch(
new SubscribeMembership(
$player->playerId,
$sessionId,
),
);

$this->addFlash('success', 'flashes.membership_subscribed_successfully');

return $this->redirectToRoute('membership');
}
}
22 changes: 22 additions & 0 deletions src/Controller/StripeWebhookController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);

namespace SpeedPuzzling\Web\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class StripeWebhookController extends AbstractController
{
#[Route(

Check failure on line 12 in src/Controller/StripeWebhookController.php

View workflow job for this annotation

GitHub Actions / phpstan

Method SpeedPuzzling\Web\Controller\StripeWebhookController::__invoke() should return Symfony\Component\HttpFoundation\Response but return statement is missing.

Check failure on line 12 in src/Controller/StripeWebhookController.php

View workflow job for this annotation

GitHub Actions / phpstan

Method SpeedPuzzling\Web\Controller\StripeWebhookController::__invoke() should return Symfony\Component\HttpFoundation\Response but return statement is missing.
path: [
'cs' => '/scan-puzzli/{code}',
'en' => '/en/scan-puzzle/{code}',
],
name: 'stripe_webhook',
)]
public function __invoke(null|string $code): Response
{
}
}
Loading

0 comments on commit 843c5eb

Please sign in to comment.