This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
- Module name:
Pstk_Paystack - PHP namespace:
Pstk\Paystack - Composer package:
pstk/paystack-magento2-module - Magento payment method code:
pstk_paystack(constantPstk\Paystack\Model\Payment\Paystack::CODE) - Requires: Magento 2.4.x, PHP 8.2+ per README — but treat that claim as unverified:
magento/framework 103.0.9(the 2.4.9 line) requires~8.3.0||~8.4.0||~8.5.0, so PHP 8.2 cannot even install it, and CI covers 8.5 only.composer.jsonhas an emptyrequire: {}, so none of this is enforced by Composer.
# Build zip for Adobe Commerce Marketplace
./build-adobe-zip.sh
# Output: pstk-paystack-magento2-module-<version>.zipbuild-adobe-zip.sh reads the version from composer.json and always rebuilds from scratch (removes any stale zip first). Its exclusion list is .git*, .DS_Store, .claude/, dev/, dev-ee/, dev-repro/, marketplace/, vendor/, .env, auth.json, CLAUDE.md, docs/, graphify-out/, node_modules/, phpunit.xml, Test/Unit/, the build script itself, and prior *.zip builds — so CLAUDE.md, internal QA artifacts, and tooling caches never ship to Marketplace.
⚠️ Anything added to the repo root after the build script was written must be added to its exclusion list explicitly. This has already gone wrong once:dev-ee/was created after the script and had to be retro-fitted before a release could ship without bundling the entire EE harness. When you add a new top-level directory that is not package content, add its-xline in the same commit.
The version string lives in three places that must be kept in sync on a version bump:
composer.json(version) — source of truth used by the build scriptetc/module.xml(setup_versionon<module>)README.md(the Version: line)
The Marketplace documentation is deliberately not a fourth: marketplace/bin/build-guide.sh injects the version from composer.json at build time, and no file in marketplace/src/ contains a version string. Keep it that way.
marketplace/ holds everything uploaded to the Adobe submission that is not the extension package, laid out as src/ (Markdown sources — the tracked originals), pdf/ (generated, gitignored like the package zip), and bin/ (build script, stdlib-only Markdown converter, print CSS), plus long-description.md recording the listing copy.
Three documents are uploaded, matching Adobe's slots: Installation Guide (getting it installed), User Guide (configuring and operating), Reference Manual (config paths, routes, events, webhook signature, CSP hosts, DI scoping). They must stay distinct — the marketing review guidelines reject duplicate documents while also requiring documentation to cover all features, so content belongs in exactly one and is cross-referenced from the others.
Regenerate with ./marketplace/bin/build-guide.sh (needs python3 and any Chromium-family browser). Run it before every upload — a fresh checkout has no PDFs, which is deliberate: a committed PDF goes stale silently when a source changes without a rebuild.
Conventions there are load-bearing, from Adobe's August 2026 marketing-review rejection of submission fc2xb678ho: document titles read "Paystack Payments for Magento 2" with the document type as subtitle, never Magento-first; no Adobe or Magento logos; and Long Description bullets must be re-entered using the Marketplace editor's own bullet button rather than pasted. See marketplace/README.md.
The guides are merchant-facing and intentionally diverge from README.md — they omit the Docker development environment and contribution sections. Changes to one do not automatically belong in the other.
cd dev
cp .env.example .env
docker compose up -d
bash setup.shThe dev/ directory contains a full Docker-based Magento 2 environment (Magento 2.4.8 via the Mage-OS mirror — no Adobe Marketplace auth needed). docker compose up -d builds the image and installs Magento on first run (~8 min total); setup.sh enables the module, disables 2FA, sets developer mode, and seeds test data (dev/seed-products.php). Containers: paystack-magento, paystack-db, paystack-search.
- Storefront:
http://localhost:8080· Admin:http://localhost:8080/admin(admin/Admin12345!) - Paystack test card:
4084 0840 8408 4081, exp12/30, CVV408, PIN0000, OTP123456 docker compose down -vresets all data.
The local dev/ env uses the Mage-OS Community Edition mirror. It cannot reproduce Adobe Commerce (Enterprise) issues — those depend on EE-only layers (Varnish FPC, Content Staging). For EE-specific reproduction there is a separate dev-ee/ harness (see below).
dev-ee/ is a standalone EE + Varnish + Selenium + MFTF harness (run-ee-baseline.sh) built to determine whether two EE-only MFTF failures reported in Adobe's QA (MC-84 AdminConfigurableProductCreateTest, MC-26602 AdminCreateGroupedProductTest) are caused by this module or are pre-existing EE core-test flakiness. It runs each test N times in two arms (no-module vs with-module) and compares failure rates. It is the empirical counterpart to docs/EE-NO-MODULE-BASELINE.md.
Status: written without valid Adobe Commerce keys and never executed end-to-end — the local auth.json keys return HTTP 401, and check-ee-keys.sh preflight refuses to run until they work. Treat run-ee-baseline.sh as a runbook, not a one-shot; fragile spots are marked SEAM: with manual fallbacks in its README.
Tests use the Magento Functional Testing Framework (MFTF), located in Test/Mftf/. MFTF tests run against a live Magento instance:
# From Magento root (not this repo root)
vendor/bin/mftf run:test PaystackPaymentConfigAvailableTest
vendor/bin/mftf run:test StorefrontPaystackCheckoutRendersTestCurrent tests (Test/Mftf/Test/): PaystackPaymentConfigAvailableTest.xml and StorefrontPaystackCheckoutRendersTest.xml, backed by the page object Test/Mftf/Page/PaystackPaymentConfigPage.xml. Test/Mftf/Suite/ exists but is empty — there are no suites, so vendor/bin/mftf run:suite has nothing to run.
There are 98 PHPUnit tests in Test/Unit/, and they are not runnable from a fresh checkout — the shipped composer.json has an empty require block and no require-dev on purpose. The test dependencies live in a CI-only manifest:
cd Test/Unit && composer install # 184 packages, pinned by the committed lock
cd - && Test/Unit/vendor/bin/phpunit -c phpunit.xml --no-coverageDo not move those deps into the root composer.json: the committed root composer.lock has a stale content-hash (it locks yabacon/paystack-php, a package absent from require), so composer install would refuse until regenerated — and the regenerated lock ships in the Marketplace zip. 3.0.10 passed Adobe review with an empty require; keep that surface untouched.
phpunit.xml at the repo root is the single config shared by CI and local runs. It bootstraps Test/Unit/vendor/autoload.php and excludes Test/Unit/vendor from discovery, because magento/framework ships its own *Test.php files that fatal when loaded. Its cache lives in Test/Unit/.phpunit.cache so it cannot leak into the package.
dev/docker-compose.yml masks Test/Unit/vendor with an anonymous volume — the repo is bind-mounted as a Magento module, and a second magento/framework inside it would be scanned by setup:di:compile. dev-repro/ needs the same line but is gitignored and untracked, so that fix is local-only; anyone recreating dev-repro/ must re-add it.
There is no configured linter or static-analysis tooling (no PHPCS/PHPStan config, no composer scripts); match the surrounding code style by hand.
CI is .github/workflows/phpunit.yml (unit tests, PHP 8.5) and .github/workflows/codeql-analysis.yml (CodeQL — note it scans JavaScript only, so the PHP money path gets no static analysis, and it still pins retired action/CodeQL v1 versions).
The docs/ directory (gitignored, never shipped) holds local MFTF Allure report artifacts (mftfmagento/, mftfvendor/) plus the EE-NO-MODULE-BASELINE.md analysis — it is not module code.
There are two integration types, selectable in admin config:
Inline (default): Paystack popup opens in the browser after order is placed.
- JS calls
afterPlaceOrder()→ Paystack popup opens - On success, JS calls
GET /V1/paystack/verify/{reference}_{quoteId}(REST API, anonymous) PaymentManagement::verifyPayment()verifies with Paystack API, dispatchespaystack_payment_verify_afterObserverAfterPaymentVerifysets order to Processing and sends confirmation email
Standard (redirect): Customer is redirected to Paystack's hosted page.
/paystack/payment/setup— initializes transaction, redirects to Paystack/paystack/payment/callback— Paystack returns here; verifies transaction, dispatchespaystack_payment_verify_after/paystack/payment/recreate— retry path: cancels the failed/abandoned order, restores the quote, and redirects back to the checkout payment step
Webhook (independent, server-to-server):
/paystack/payment/webhook— receivescharge.successevents from Paystack- Validates HMAC-SHA512 signature, verifies transaction, dispatches
paystack_payment_verify_after - CSRF validation skipped via
Plugin/CsrfValidatorSkip.php
The custom event paystack_payment_verify_after is the single point where order status is updated to Processing and confirmation email is sent (Observer/ObserverAfterPaymentVerify.php). Initial order confirmation email is suppressed by ObserverBeforeSalesOrderPlace until payment is verified.
| Class | Responsibility |
|---|---|
Gateway/PaystackApiClient.php |
All Paystack API calls: initialize transaction, verify, validate webhook signature |
Model/PaymentManagement.php |
REST API endpoint for inline payment verification |
Model/Ui/ConfigProvider.php |
Injects public key, integration type, and URLs into checkout JS config |
Controller/Payment/AbstractPaystackStandard.php |
Base controller with shared utilities (quote loading, message handling) |
etc/csp_whitelist.xml |
Whitelists Paystack domains in Magento's Content Security Policy (additive; the Magento-standard mechanism) |
This is critical — the payment method is intentionally unavailable in admin order creation:
etc/frontend/di.xml— registersConfigProvider,PaymentManagementInterfacepreference, CSRF-skip plugin (CSP is handled byetc/csp_whitelist.xml, not here)etc/adminhtml/di.xml— intentionally empty (prevents EE admin crash on order create)etc/webapi_rest/di.xml—PaymentManagementInterfacepreference for REST API callsetc/di.xml— root scope (minimal)
All settings live under payment/pstk_paystack/ in Magento config. Secret keys use the Encrypted backend model. Test mode toggles between test/live key pairs in PaystackApiClient.
For inline payments, Paystack generates the transaction reference on the client side. The quoteId is passed as metadata in the Paystack transaction so the webhook/verification can locate the correct order when no Magento-generated reference is available.