madcoders / sylius-giftcard-plugin
Gift card plugin for Sylius 2.x - sell gift cards, spend them against what a customer owes and let customers track their remaining balance.
Package info
github.com/mad-coders/sylius-giftcard-plugin
Type:sylius-plugin
pkg:composer/madcoders/sylius-giftcard-plugin
Requires
- php: ^8.3
- sylius/sylius: ^2.0
Requires (Dev)
- behat/behat: ^3.16
- dbrekelmans/bdi: ^1.4
- dmore/behat-chrome-extension: ^1.4
- dmore/chrome-mink-driver: ^2.9
- friends-of-behat/mink: ^1.11
- friends-of-behat/mink-browserkit-driver: ^1.6
- friends-of-behat/mink-debug-extension: ^2.1
- friends-of-behat/mink-extension: ^2.7
- friends-of-behat/page-object-extension: ^0.4
- friends-of-behat/suite-settings-extension: ^1.1
- friends-of-behat/symfony-extension: ^2.6
- friends-of-behat/variadic-extension: ^1.6
- nyholm/psr7: ^1.8
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-doctrine: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpstan/phpstan-webmozart-assert: ^2.0
- phpunit/phpunit: ^10.5
- rector/rector: ^2.0
- sylius-labs/coding-standard: ^4.4
- sylius-labs/suite-tags-extension: ~0.2
- sylius/sylius-rector: ^3.8
- sylius/test-application: ^2.0.0@alpha
- symfony/browser-kit: ^6.4 || ^7.4
- symfony/debug-bundle: ^6.4 || ^7.4
- symfony/dotenv: ^6.4 || ^7.4
- symfony/http-client: ^6.4 || ^7.4
- symfony/intl: ^6.4 || ^7.4
- symfony/lock: ^6.4 || ^7.4
- symfony/rate-limiter: ^6.4 || ^7.4
- symfony/runtime: ^6.4 || ^7.4
- symfony/web-profiler-bundle: ^6.4 || ^7.4
- symfony/webpack-encore-bundle: ^2.2
Suggests
- symfony/lock: Makes the redemption rate limiter's counter atomic. Without it concurrent attempts can each read the same count, so the effective allowance per round trip is the number of PHP workers rather than the configured limit.
- symfony/rate-limiter: Throttles failed gift card redemption attempts. Without it the plugin boots and works, but the redeem field accepts unlimited guesses - see docs/INSTALLATION.md.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-02 13:05:40 UTC
README
Sell gift cards in your Sylius store, spend them against what a customer owes, and let customers track what is left on them.
Status: 1.0.0-RC.1. Feature-complete and green across the supported Sylius, Symfony and database matrix. Being a release candidate it wants real-world use before a stable tag - see
CHANGELOG.mdfor what is in it and what is deliberately left out.
Requires "minimum-stability": "RC" (or composer require madcoders/sylius-giftcard-plugin:^1.0@RC)
until 1.0.0 is tagged.
What it does
- Sell gift cards as ordinary Sylius products. A card is generated per purchased unit when the order is paid, and emailed to the customer.
- Redeem gift cards against what the customer owes, from the cart or from the checkout. A card is money, not a discount: the order total stays at the full value of the goods and the payment is what shrinks. Cards stack, and together they can never cover more than is owed. See ADR 0010.
- Track the balance. A gift card knows both the customer who bought it and the customer who uses it, so the person actually spending the card sees its remaining balance and history in their account - even though someone else paid for it.
- Administer cards from the Sylius admin: create them manually, adjust balances, and configure code format, validity, whether cards are sold at all, and how a customer chooses an amount - all per channel. Every gift card has an expiry date, and every balance change leaves a ledger entry.
- Treat codes as money. The redeem field is rate limited per client, refusals say the same thing
whether or not the code exists, and no code ever reaches a log, a flash or an exception message.
Needs
symfony/rate-limiter; seedocs/INSTALLATION.md.
Not in 1.0: PDF gift cards, API Platform resources, bulk generation. See
docs/adr-log/0009-no-pdf-in-1-0.md.
Requirements
| PHP | ^8.3 |
| Sylius | ^2.0 (tested against ~2.0, ~2.1, ~2.2) |
| Symfony | ^6.4 || ^7.4 |
Installation
composer require madcoders/sylius-giftcard-plugin
Then follow docs/INSTALLATION.md - the plugin needs its bundle registered,
its configuration and routes imported, and its entity extensions applied to your Product, Order
and OrderItemUnit.
How redemption works
A gift card is money, not a discount. The order stays worth what the goods are worth; the card comes off what the customer has to pay. A 100 order paid with a 40 card is still a 100 order, and the customer pays 60.
That matters beyond bookkeeping: tax is owed on the value of the goods sold and was already settled on the card when it was bought, refunds and reporting see an order worth what was actually sold, and a card cannot switch off a "spend over X" promotion by moving the total it tests.
Read the split with Order::getAmountToPay() and Order::getGiftCardTotal(). getTotal() is the
value of the goods, not what the customer pays. The reasoning is in
docs/adr-log/0010-gift-card-as-tender.md.
Development
make setup # deps + docker (MySQL on 3307) + assets + database make install-hooks # pre-commit quality gate and commit template make app # fresh schema + demo data, ready to look at make serve # http://127.0.0.1:8080 make verify # fast gate: composer validate + phpstan + ecs + unit tests make test # phpunit + behat make install-test # install into a throwaway Sylius by following docs/INSTALLATION.md make help # every available target
The demo data
make app loads a gift card in every state the plugin can produce, so the admin grid, the account
pages and the cart panel all have something to show:
| Card | What it demonstrates |
|---|---|
GIFT-FULL0001, GIFT-SMALL001 |
Spendable; two of them on one cart proves cards stack |
GIFT-LARGE001 |
Worth more than the cart - covers it all and keeps the change |
GIFT-USED0001 |
Partly spent, with the ledger entry explaining the balance |
GIFT-EMPTY001 |
Spent out: still listed, no longer redeemable |
GIFT-EXPIRED1, GIFT-DISABLED |
The two ways a card stops being redeemable |
GIFT-EXPSOON1, GIFT-LONGLIFE |
Expiring in a week, and in twenty-five years - there is no card without an expiry, because there is no way to make one |
GIFT-GIFTED01 |
Bought by one customer, not yet used by anybody |
GIFT-SHARED01 |
The two-customer model: bought by one, spent by another |
GIFT-SELFUSE1 |
Bought and used by the same person |
giftcard.buyer@example.com and giftcard.holder@example.com (password sylius) are the two
customers those last three cards link, and the first two products in the catalogue are marked as
gift card products so the "buying one issues a card" flow can be walked through.
Usage guide: docs/USAGE.md. Contributor guide: docs/CONTRIBUTING.md. Working on this with an AI agent?
Start from AGENTS.md.
The primary branch is 1.0 - this repository has no main or master, following the Sylius
version-branch model.
Credits
Functionally inspired by Setono/SyliusGiftCardPlugin. Built and maintained by Madcoders.