Skip to content

Magento 2

Magento 2.4.6+ · Adobe Commerce

Set up in the cabinetKeys, webhook URLs and downloads for this platform are in the cabinet under Integrations.

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:

text
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:flush

Without 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.shdist/. 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

  1. CryptoLike cabinet → Settings → API keys → Create: permissions create_invoice and read only (a shop never needs withdraw). Copy the key ID (ck_live_…) and the secret. The cabinet shows the secret only once.
  2. Magento admin → Stores → Configuration → Sales → Payment Methods → CryptoLike — crypto payments. Paste the API key ID and the API secret.
  3. 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.
  4. 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

text
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 2

Design notes:

  • order_id = the increment id (default store) or <store_id>-<increment_id>, kept in cryptolike_order.order_ref (unique); the webhook finds the order only there. A foreign or deleted order gets 200 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 UPDATE on sales_order and cryptolike_order; the event_id is checked, then the state, invoice and comments are written, the event_id is recorded, and the transaction commits. Only then the endpoint answers 200. A failure rolls everything back, answers 500, and the platform retries.
  • The status page (/cryptolike/payment/status?order=…&token=…, also the invoice's return_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/i18n at runtime (store locale uk_UA → uk, ru_RU → ru, else en). Admin labels are the same English texts, translated by i18n/*.csv.

Checks

text
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

text
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.

text
./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_data and hidden from config:show;
  • a live key is refused in test mode;
  • the webhook answers 404 to GET, 401 to a bad signature, 400 to a stale timestamp and 200 to 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_payment and has its order_ref row;
  • the API error is logged;
  • signed webhooks move the order: underpaid → holded (with hold_before_state), then paid → processing with one offline invoice (transaction id = the payment id);
  • the same delivery again → duplicate; an older pending → 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/.

  1. 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.
  2. Guest checkout of a simple product in USD with «Crypto» → redirect to the payment page; the order is pending_payment, comment «payment … created», cryptolike_order row with the invoice id.
  3. Back button + «Place Order» again / reload /cryptolike/payment/redirect → the same invoice.
  4. Pay on regtest (fresh sender, stage_e2e.pay_btc) → invoice.pending comment → invoice.paidprocessing + an invoice (offline, transaction id = the payment id), the status page says «Payment confirmed.», My account → order shows the comment.
  5. Resend the paid webhook from the cabinet → nothing changes (log webhook: duplicate).
  6. Partial payment → holded, the missing amount on the status page; top-up → processing + invoice.
  7. Time to pay 1 min, no payment → canceled with stock back; with «Keep pending» the status page shows «Create a new payment» → a new payment page.
  8. «Keep pending»: late payment → payment_review, Accept → processing; Deny → canceled. «Cancel the order»: late payment → the order stays canceled (no Accept / Deny), the comment says it stays cancelled, the status page says the payment arrived after the deadline.
  9. curl -X POST the webhook URL with a wrong signature → 401, the order is untouched; GET404.
  10. 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 equals sales_order.grand_total exactly (19.99 / 0.29 / 10.30) — the amount comes from the saved DECIMAL string, never a float.
  11. grep -iE 'ck_(live|test)_|secret|signature' var/log/cryptolike.log → no secret values.