Magento 2
- Download · Composer package (.zip)v1.0.0 · 94 KB · SHA-256: 4d84711a1a4ff07a51bd8509d7c6795642f22c18d4f2235dd20b21be00cd0a6a
- Download · app/code archive (.zip)v1.0.0 · 98 KB · SHA-256: 1f659782c1624ffa19813854f4a66758b0cdc1b4a40ebf0c1aee5134606871d7
CryptoLike for Magento 2
Magento module CryptoLike_Payment 1.0.0 (composer package cryptolike/module-payment): payments
in BTC, USDT and other coins through CryptoLike for Magento Open Source / Adobe Commerce 2.4.6+
(PHP 8.1 – 8.4), the Luma checkout. Built on the Magento Payment Gateway (a Method\Adapter
facade, initialize command, no capture online). Contract it follows:
docs/INTEGRATIONS.md.
Verified by unit tests, phpstan (level 6) against the real Magento 2.4 classes, the Magento 2
coding standard, and a live smoke on Mage-OS 3.5 (Magento Open Source 2.4 distribution, PHP 8.3).
The smoke (dev/smoke.py) runs setup:di:compile, the settings, a REST guest checkout, signed
webhooks (holded → processing + invoice, duplicate, out of order) and the status page. The
end-to-end run against the stage instance is QA's (Manual check).
For the merchant
Install
Composer (recommended). Put cryptolike-module-payment-1.0.0.zip into a directory of the
server, e.g. /var/www/packages, and in the Magento root:
composer config repositories.cryptolike artifact /var/www/packages
composer require cryptolike/module-payment:1.0.0
bin/magento module:enable CryptoLike_Payment
bin/magento setup:upgrade
bin/magento setup:di:compile # production mode
bin/magento setup:static-content:deploy -f en_US uk_UA ru_RU # production mode
bin/magento cache:flushWithout Composer. Unzip cryptolike-magento-1.0.0-app-code.zip in the Magento root (it
creates app/code/CryptoLike/Payment), then the same bin/magento commands.
Both archives come from the CI build or ./build.sh → dist/. The CryptoLike PHP SDK is inside the
module (lib/). You do not need another Composer package or a Marketplace key.
Connect it to your CryptoLike account
- CryptoLike cabinet → Settings → API keys → Create: permissions
create_invoiceandreadonly (a shop never needswithdraw). Copy the key ID (ck_live_…) and the secret. The cabinet shows the secret only once. - Magento admin → Stores → Configuration → Sales → Payment Methods → CryptoLike — crypto payments. Paste the API key ID and the API secret.
- Copy the Webhook URL shown there (
https://your-shop/cryptolike/webhook/, per store view). Cabinet → Webhooks → Add endpoint → paste the URL, tick all invoice events (invoice.created,pending,paid,underpaid,expired,cancelled,late_payment,reverted) → save → copy the endpoint's secret into Webhook secret. - Enable crypto payments = Yes → Save Config. Check connection then shows «Connected: N currencies available.». It checks the saved key each time the page opens.
| Setting | What it does |
|---|---|
| Test mode + API URL | Test instance with testnet coins and a ck_test_… key. Saving a live key in test mode, or a test key in live mode, is refused. The settings page shows a «Test mode» badge, and the checkout shows the test-mode line under the method. |
| API secret / Webhook secret | Stored encrypted (core_config_data, Magento's crypt key) and marked sensitive, so they never appear in config:show or app:config:dump. The form shows them as ******. Saving the form unchanged keeps the stored value. |
| Payment page | Redirect to the CryptoLike payment page after «Place Order», or embedded on the order's payment status page. For the embedded page, add your shop's address in the cabinet: Settings → Integration → Allowed origins. |
| Time to pay, minutes | Empty = your account setting (30 min). |
| When a payment expires | Cancel the order (stock returns), or keep it pending payment. If you keep it, the customer can create a new payment from the status page. |
| Order statuses | One per state: awaiting payment (pending_payment), after payment (processing), after a partial payment (holded), review (payment_review), expired/cancelled (canceled). Only statuses assigned to that state can be chosen (Stores → Order Status). |
| Debug log | var/log/cryptolike.log. Never contains secrets, signatures, webhook bodies or customer data. |
Multistore: every setting except the title and the payment page mode is per website. Each
website can use its own CryptoLike account. An event signed with one website's webhook secret
never changes an order of another website. Outside the default store, order_id in the API is
<store_id>-<increment_id>.
What happens to an order
| Payment | Magento order (state / default status) | The customer sees (status page, My account → order comments) |
|---|---|---|
| Created at «Place Order» | pending_payment; comment «payment … created» |
«Awaiting payment.» + the payment page |
| Transfer detected | unchanged; comment with the transaction id | «Payment detected. Waiting for confirmation.» |
| Confirmed | processing + an invoice (captured offline, transaction id = CryptoLike payment id). A virtual order goes to complete, as Magento decides. |
«Payment confirmed.» |
| Partial payment, before the deadline | holded (the admin can Unhold it); comment with the missing amount |
how much is missing, to the same address, and the deadline |
| Top-up completes it | released from hold → processing + invoice |
«Payment confirmed.» |
| Partial payment after the deadline, late payment, a second payment, reversal after paid, amount of an edited order | payment_review + a comment explaining the decision |
«Payment received. The store is checking it…» |
| Expired | canceled (Order::cancel(): stock back), or unchanged (setting) |
«Payment expired. Create a new payment.» |
Money for an order that is already canceled (a late payment after the expiry cancel, or an order you cancelled by hand) |
stays canceled; comments: what arrived + «stays cancelled: refund from the cabinet or create a new order» |
«Payment received after the deadline…» (late payment) or «Payment received. The store is checking it…» |
Payment review. The order page shows Magento's Accept Payment / Deny Payment buttons.
The funds are already on your CryptoLike balance. Accept moves the order to processing, and you
invoice it as usual. Deny cancels the order; refund the customer from the cabinet. The module
never marks such an order paid on its own.
A cancelled order is not reopened. Magento cannot bring back a cancelled order: its items,
totals and stock are already released, and Accept on it would only close it with nothing to
invoice or ship. So money for a cancelled order leaves it canceled with a comment; the funds are
on your CryptoLike balance — refund the customer from the cabinet, or create a new order for them
(admin → the order → Reorder) and ship that one. To keep orders open after the time to pay,
set «When a payment expires» to «Keep the order pending payment»: a late payment then goes to
payment_review with Accept / Deny.
Nothing that needs a person is marked paid automatically. A double click, a reload or «Create a new payment» later returns the same open payment page. An order that already received money never gets a second one.
Screenshots
Taken on the dev stand (dev/) by QA. Placeholders until then:
docs/integrations/magento/1-settings.png (settings with the test badge and webhook URL),
2-checkout.png (the method in the Luma checkout), 3-status-paid.png (status page after payment),
4-order-comments.png (order comments written by the webhooks).
Troubleshooting
| Symptom | Check |
|---|---|
| The method is not in the checkout | Enabled; key ID + secret + webhook secret saved for that website; key prefix matches the mode; bin/magento cache:flush. |
| Orders stay «Pending Payment» after paying | The settings page shows «Last verified webhook». If it is empty, the webhook does not reach the shop. Check that the shop is reachable over HTTPS, that a WAF/CDN lets POST /cryptolike/webhook/ through without a challenge, and that maintenance mode is off (Magento answers 503 to everything; the platform retries for 24 h). var/log/cryptolike.log: verification failed {"reason":"invalid_signature"} = the webhook secret is not the one of that endpoint. The cron job cryptolike_reconcile (every 15 min, Magento cron must run) catches up. |
| «Connection failed: unauthorized» | Wrong secret or key, or the key's IP whitelist does not include the shop's server. |
Webhook answered 503 not_configured |
The webhook secret is empty for the store view the URL belongs to. |
| Embedded page does not load | The shop's origin is not in the cabinet's allowed origins, or a custom CSP blocks the payment host (script-src/frame-src of the API URL's host). |
For developers
src/app/code/CryptoLike/Payment/ the module (as it lies in app/code)
registration.php, composer.json module + Marketplace package; loads lib/ (vendored SDK + common)
etc/ module, config (defaults), di (Adapter facade, commands, logger,
sensitive paths), adminhtml/system.xml, db_schema, routes, cron, events
Gateway/Command/ initialize (pending_payment, nothing captured), accept/deny (review)
Model/Settings.php, StateMap.php rules without Magento (order_id, amounts, TTL, URL, key/mode, state map)
Model/Bridge.php API client per store, payment creation, status view, webhook, reconcile
Model/PaymentStore.php OrderStore of the shared WebhookProcessor: lock, snapshot, state/status, invoice
Model/PaymentRows.php the only SQL: cryptolike_order, cryptolike_event, row lock, last-webhook flag
Controller/ payment/redirect, payment/status, payment/retry, webhook (CsrfAware, POST)
Observer/CreatePayment.php checkout_submit_all_after → the payment of the order just saved
view/frontend/ checkout renderer (Knockout), status page template
i18n/*.csv generated from integrations/common/i18n (admin.*) — do not edit
build.sh → dist/cryptolike-module-payment-<v>.zip + dist/cryptolike-magento-<v>-app-code.zip
tests/Unit/ PHPUnit: webhook end to end (FakeRows), settings, texts/templates
tests/docker.sh `make integrations-magento-test`: lint 7.4/8.3, PHPUnit, phpstan, phpcs
tools/composer.json + .lock dev tools: PHPUnit 9, phpstan, magento-coding-standard, Magento 2.4
framework/modules from the Mage-OS mirror (no Marketplace keys); tools/install.sh
dev/ docker-compose: Magento (Mage-OS 2.4.x, PHP 8.3) + MySQL 8.4 + OpenSearch 2Design notes:
- order_id = the increment id (default store) or
<store_id>-<increment_id>, kept incryptolike_order.order_ref(unique); the webhook finds the order only there. A foreign or deleted order gets200 order_not_found. - The payment is created after the order is saved (
checkout_submit_all_after). An API outage never loses the order. «Place Order» then goes to/cryptolike/payment/redirect. It uses the open invoice (one per order,idempotency_by_order_id), or creates it if the observer failed. - Webhook transaction:
SELECT … FOR UPDATEonsales_orderandcryptolike_order; theevent_idis checked, then the state, invoice and comments are written, theevent_idis recorded, and the transaction commits. Only then the endpoint answers200. A failure rolls everything back, answers500, and the platform retries. - The status page (
/cryptolike/payment/status?order=…&token=…, also the invoice'sreturn_url) only shows state; it never changes the order. The token is an HMAC of order id + protect code with the store's secrets. - Customer texts come from
integrations/common/i18nat runtime (store localeuk_UA→ uk,ru_RU→ ru, else en). Admin labels are the same English texts, translated byi18n/*.csv.
Checks
make integrations-magento-test # lint 7.4/8.3 · PHPUnit (30) · phpstan level 6 · phpcs Magento2 (0 errors)
integrations/tools/ci.sh # + builds, archive contents, CSV drift (CI ci-integrations)The phpcs gate is errors only. The remaining warnings are long lines and static helpers of the
pure rule classes. registration.php loads the vendored autoloaders with a documented
phpcs:ignore: Magento's app/code autoloader does not know the SDK namespaces.
Local stand
cd integrations/magento && ./build.sh
cd dev && ./setup.sh # first run: image build + composer create-project (Mage-OS) + setup:install
open http://localhost:8087 # admin: /admin_dev, admin / admin-dev-only1 (2FA modules disabled)setup.sh installs the module from ../dist, then runs setup:upgrade,
setup:di:compile (which validates the DI and XML) and cache:flush. Measured on this server:
image build 2.5 min, composer 2 min, install + module + compile 4 min. Memory ≈ 2.5 GB.
Run bin/magento as www-data (docker compose exec -u www-data magento …): root-owned cache
files make Apache answer 500. GET requests must use the base URL host (localhost:8087);
Magento redirects any other host to it.
./smoke.py # live smoke without the CryptoLike API (API URL = closed port)It configures the module through bin/magento config:set, then checks:
- the secrets are encrypted in
core_config_dataand hidden fromconfig:show; - a live key is refused in test mode;
- the webhook answers
404to GET,401to a bad signature,400to a stale timestamp and200to a signed ping.
Then it creates a product and runs a REST guest checkout: the method is offered, and the order is placed although the API is down. The next checks:
- the order is
pending_paymentand has itsorder_refrow; - the API error is logged;
- signed webhooks move the order: underpaid →
holded(withhold_before_state), then paid →processingwith one offline invoice (transaction id = the payment id); - the same delivery again →
duplicate; an olderpending→ ignored; - the comments are written, 3 event ids are recorded, and the log holds no secret.
Manual check (live Magento)
Against the stage instance, following the PrestaShop dev/e2e.py pattern: the shop on the stage
gateway 172.30.1.1:8087 with webhook_allowed_hosts += 172.30.1.1, a ck_test_ key and the
stage CA in dev/ca/.
- Settings: a live key in test mode is refused; saving with empty secrets keeps them; «Check connection» shows the currency count; the Webhook URL is shown.
- Guest checkout of a simple product in USD with «Crypto» → redirect to the payment page; the order is
pending_payment, comment «payment … created»,cryptolike_orderrow with the invoice id. - Back button + «Place Order» again / reload
/cryptolike/payment/redirect→ the same invoice. - Pay on regtest (fresh sender,
stage_e2e.pay_btc) →invoice.pendingcomment →invoice.paid→processing+ an invoice (offline, transaction id = the payment id), the status page says «Payment confirmed.», My account → order shows the comment. - Resend the paid webhook from the cabinet → nothing changes (log
webhook: duplicate). - Partial payment →
holded, the missing amount on the status page; top-up →processing+ invoice. - Time to pay 1 min, no payment →
canceledwith stock back; with «Keep pending» the status page shows «Create a new payment» → a new payment page. - «Keep pending»: late payment →
payment_review, Accept →processing; Deny →canceled. «Cancel the order»: late payment → the order stayscanceled(no Accept / Deny), the comment says it stays cancelled, the status page says the payment arrived after the deadline. curl -X POSTthe webhook URL with a wrong signature →401, the order is untouched;GET→404.- Float-sensitive totals (
dev/e2e.py --amounts-only): products at 19.99, 0.29 and a cart of 10.10 + 0.20 with a free flat rate → the invoice amount equalssales_order.grand_totalexactly (19.99 / 0.29 / 10.30) — the amount comes from the saved DECIMAL string, never a float. grep -iE 'ck_(live|test)_|secret|signature' var/log/cryptolike.log→ no secret values.