Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

PS Web Dev Tech Task

Task: Subscription Entitlements Service

Background

When someone buys a subscription, the payment provider handles the money, but the product still has to decide what that person can use. An entitlement is that decision: "user 42 can use Export to PDF and Cloud Sync until 31 March". Entitlements come from the user's plan (for example, Pro includes a fixed set of features) and are valid for the billing period that was paid for.

The payment provider reports changes as webhooks: HTTP requests sent to our service when a subscription is created, renewed or canceled, or when a payment is refunded. Like any real provider, it may deliver an event more than once, deliver events out of order, or retry after failures.

The entitlements service sits between the two. It turns provider events into an up-to-date answer to one question that the rest of the system asks constantly: what is this user allowed to use right now? It also tells other services when the answer changes.

Payment provider ──webhooks──▶ Entitlements service ──EntitlementsChanged──▶ Other services
                                       ▲
                                       └── GET /api/v1/users/{id}/entitlements ── Other services

Brief

Build a small Symfony service that tracks which features a user is entitled to, based on webhooks from a fictional payment provider.

Time budget: 6–8 hours. Deadline: one week from receiving the task.

Requirements

  1. Webhook endpoint. POST /webhooks/payments receives subscription.created, subscription.renewed, subscription.canceled and payment.refunded events. Requests are signed with HMAC in a header. Requests with an invalid signature must be rejected.
  2. Entitlement model. Plans map to feature sets. A user has a plan's features until the period end date. Refunds and cancellations affect access according to rules you define and briefly document (for example, a refund revokes access immediately, a cancellation keeps access until period end).
  3. Read API. GET /api/v1/users/{id}/entitlements returns the user's current features and their validity. The endpoint is protected by JWT (include a test key pair). It must be fast, because other services call it on every request.
  4. Notifications. Whenever a user's entitlements change, publish an EntitlementsChanged message reliably. Symfony Messenger is fine. A real broker is not required.
  5. Stack. Use this repository as the starting point: Symfony, PHP and MySQL as already set up here. Everything must start with a single make up (see below).
  6. Tests. Unit and integration tests for what you consider most important.
  7. README. Update this README with how to run the service, your key decisions and trade-offs, what you would do with more time, and where you used AI.
  8. Optional. A minimal React page showing a user's entitlements and change history.

Sample events

A JSON file of sample webhook events is provided with this task. Replay it through the webhook endpoint and make sure the resulting state is correct. Treat it as you would real provider traffic.

Using AI

AI tools are allowed. Disclose where and how you used them in the README. In the follow-up session you will walk through your solution and discuss specific parts of the code, so make sure you understand and can explain every decision in it.

What we value

A lean, working solution with clearly explained reasoning. We prefer this over an elaborate one. Avoid complexity the problem doesn't need, and state your trade-offs honestly.

Follow-up session

After submission there is a 60-minute session to walk through your architecture, discuss specific code and failure scenarios, and talk through a small change request.


Using the service

The service is a Symfony 8.1 application (PHP 8.5, Doctrine ORM 3, MySQL 8.4) that runs entirely in Docker. The only things needed on the host are Docker (with Compose v2) and make.

Stack

Container Purpose Host port
php-web Apache + PHP serving public/, the project is mounted live 4444 → http://localhost:4444
php-cli PHP CLI with Xdebug for console commands, migrations, tests —
mysql MySQL 8.4, databases web_dev_task_dev (dev) and web_dev_task_dev_test (test) 33444

The source directory is mounted into both PHP containers, so code changes apply without a rebuild.

First run

  1. Install dependencies and start the stack:

    make install   # composer install via the composer:2.11.1 image
    make up        # build images and start mysql, php-cli, php-web

    make install must come first: the project directory is mounted into the containers, so they use the host's vendor/.

  2. On start, php-cli applies the Doctrine migrations to the dev and test databases, then exits. Check that it succeeded with docker compose logs php-cli. If MySQL was still initialising on the very first start, run make migrate.

  3. Open http://localhost:4444. The response Hey there! means the stack is up.

The database connection is preconfigured in .env (mysql://root:@mysql:3306/web_dev_task_dev). In the test environment Doctrine appends _test to the database name, which resolves to web_dev_task_dev_test. Put local overrides in .env.local (git-ignored).

Make targets

Target What it does
make install Installs Composer dependencies into vendor/
make composer Opens a shell in the Composer container, e.g. for composer require ...
make up Starts the stack in the background
make down Stops the stack and keeps the database volume
make shell Opens bash inside the running php-web container
make migrate Applies Doctrine migrations to the dev and test databases
make test Runs PHPUnit in php-cli with APP_ENV=test
make stan Runs PHPStan
make cs-check Checks code style with PHP-CS-Fixer (dry run, shows the diff)
make cs-fix Fixes code style with PHP-CS-Fixer
make clean Removes vendor/ and var/cache/*
make destroy clean, then removes the containers, the MySQL volume and the locally built images

Everyday workflow

  • Console commands run in the CLI container:

    docker compose run --rm php-cli bin/console <command>
  • Schema changes go through migrations: change an entity under src/Entity, generate a migration in migrations/, then run make migrate.

  • Add packages with make composer, then composer require <package> inside it, so Symfony Flex recipes register bundles and generate their config.

  • To reach MySQL from the host, connect to 127.0.0.1:33444 as root with an empty password.

  • Logs are in var/log/.

Quality tools

PHPUnit, PHPStan and PHP-CS-Fixer are pre-installed as dev dependencies and run inside php-cli:

Tool Run with Configuration
PHPUnit make test phpunit.dist.xml, bootstrap in tests/bootstrap.php
PHPStan make stan phpstan.neon (level 6: bin, public, src, tests)
PHP-CS-Fixer make cs-check / make cs-fix .php-cs-fixer.dist.php (@Symfony ruleset)

Tests live under tests/Application, which is the suite phpunit.dist.xml runs. A smoke test for GET / is included as a starting point. Tests run with APP_ENV=test against the web_dev_task_dev_test database.

Resetting the environment

make destroy && make install && make up

This recreates both databases from scratch and re-applies all migrations.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages