Skip to content

Repository files navigation

NetPay for Magento 2 (Netpay_Payment)

Magento PHP 3-D Secure Multi-store

Community port of NetPay's official Magento module (which shipped only as a ZIP built for Magento 2.4.6) to Magento Open Source 2.4.8 on PHP 8.4, hardened against NetPay's WooCommerce plugin as the reference implementation.

It adds NetPay card payments with 3-D Secure, saved cards (vault) and OXXO Pay (cash), and is self-contained — NetPay's PHP SDK is vendored under Sdk/ and autoloaded by registration.php, so there is no separate netpay/custom Composer dependency. Guzzle is not vendored (Magento ships it).


Features

  • Card payments with 3-D Secure (Cardinal/Songbird), frictionless + challenge flows.
  • OXXO Pay (cash) with an OXXO-Pay checkout method and a reference/receipt block.
  • Saved cards (vault) — save at checkout, manage in My Account, delete; client id scoped per store so cards never leak across stores / NetPay accounts.
  • Webhook receiver — OXXO settlement + generic card reconciliation (re-verifies the transaction against the gateway; idempotent).
  • Refunds — admin online credit memos refund through NetPay (full refunds).
  • Pre-authorization (Check-in / Check-out) — optional mode where checkout only holds the amount and the merchant captures the real total from the admin invoice (PostAuth, ±20%); cancelling releases the hold.
  • Multi-store aware — every config read (keys / mode / gateway host) is scoped to the order's store, including the webhook (which carries no store context) and the charge.
  • Friendly error messages — ~60 raw gateway responses mapped to friendly Spanish messages.
  • Retry-safe 3-D Secure — sticky Cardinal/Songbird state is cleared between attempts.
  • Self-contained SDK, declarative schema, phpcs --standard=Magento2 clean (excluding Sdk/).

Compatibility

Magento Open Source 2.4.8 (tested on 2.4.8-p5)
PHP 8.1 – 8.4
HTTP client guzzlehttp/guzzle ^7.5 (provided by Magento)
3-D Secure Cardinal Commerce (Songbird), sandbox + live

Install

Composer

composer require netpay/module-payment
bin/magento module:enable Netpay_Payment
bin/magento setup:upgrade
bin/magento cache:flush

Manual (app/code)

Copy this repository to app/code/Netpay/Payment, then:

bin/magento module:enable Netpay_Payment
bin/magento setup:upgrade
bin/magento cache:flush

Configuration

Stores → Configuration → Sales → Payment Methods → NetPay

  • Enable the gateway and the credit-card method.
  • Set the mode (test / live) and the matching public/secret keys.
  • (OXXO) Enable OXXO Pay and register the webhook from the config page button.

All fields are store-scoped (showInStore=1), so each store view can have its own NetPay account.

Config paths (if you script it with bin/magento config:set):

payment/netpay/enable            1
payment/netpay/active            1     # credit-card method
payment/netpay/payment_mode      test  # or live
payment/netpay/public_key_test   pk_...
payment/netpay/secret_key_test   sk_...
payment/netpay/public_key_live   pk_...
payment/netpay/secret_key_live   sk_...
payment/netpay/preauth_mode      authorize_capture   # or `authorize` (pre-authorization)

The secret key is only ever used server-side (to build the SDK PaymentManager); it is never exposed to the browser. The checkout config only carries the public key.

Pre-authorization (Check-in / Check-out)

Payment Methods → NetPay → NetPay Credit Card → Payment Action:

  • Direct sale (charge immediately) — default (authorize_capture). Unchanged behavior: the charge is captured at checkout and the order is invoiced automatically.
  • Pre-authorization (hold now, capture on invoice)authorize. The checkout only holds the order total (Check-in, sent as transactionType: "PreAuth" on the same POST v3.5/charges); the order stays in Processing with an open authorization transaction and no invoice.

To charge the real amount (items missing or removed while picking the order): Sales → Orders → View → Invoice, adjust Qty to Invoice (0 for what is missing), set Amount: Capture Online and Submit Invoice → that fires Check-out / PostAuth for the invoice total. NetPay accepts a final amount between −20% and +20% of the held one; outside that range the capture is rejected with an explicit message — cancel the order (releasing the hold) and charge again.

Also in this mode:

  • Cancelling the order releases the hold (the cancelation endpoint shared with refunds). An uncaptured hold expires on its own after 5 business days (Visa/Mastercard) or 7 (Amex); capturing after that fails, so cancel and re-charge instead.
  • The customer gets the invoice email automatically when the capture succeeds — it lists only the invoiced items and the amount actually charged, i.e. the final receipt.
  • The admin order view shows a banner with the state of the hold (held / captured / released / never confirmed) plus the capture instructions.
  • Installments (MSI) are disabled: a plan fixed at PreAuth time is incompatible with capturing a different amount later.
  • OXXO / cash (netpaycash) is unaffected.
  • The NetPay account must have Check-in/Check-out enabled — confirm it with your account manager before going live.

Magento sends no email when an order is cancelled, and its "New Order" email does not mention holds, so two operational steps are recommended: add a line to the order-confirmation template explaining that the amount shown is a hold whose final charge may be lower, and when cancelling a pre-authorized order add a status comment with Notify Customer by Email checked.

3-D Secure flow

The card flow follows NetPay's contract (aligned with NetPay's WooCommerce plugin):

  1. Tokenize the card client-side (NetPay.token.create) — the PAN never reaches the server.
  2. Device Data Collection: the checkout generates a Cardinal ReferenceId and blocks the charge until it is ready, then sends it on the charge.
  3. POST /v3.5/charges. If the charge returns status: review:
    • Challenge (response carries acsUrl + paReq + authenticationTransactionID): run the Cardinal step-up, then confirm with the returned processorTransactionId.
    • Frictionless (no acsUrl): read the real state first (GET /v3/transactions/{id}) and branch:
      • WAIT_THREEDS → confirm (frictionless confirm sends the literal processorTransactionId=null).
      • DONE / CHARGEABLE → already approved.
      • FAILED / REJECTED / REJECT → surface a friendly reason and cancel the pending order.

Decision Manager: a charge can be sent to review and then FAILED by NetPay's anti-fraud Decision Manager (responseCode 88, "Enviada a review por DM"). That is an account/gateway-side decision, not a client bug. This module surfaces that state instead of masking it (the raw confirm returns HTTP 409 for such transactions).

Saved cards (vault)

  • Cards are saved via NetPay's client/vault API and shown in My Account → My Credit Cards and in the checkout saved-card selector.
  • The link between a Magento customer and their NetPay client id lives in netpay_customer (customer_id, store_id, netpay_id), unique on (customer_id, store_id). This makes the vault correct in multi-store setups whether stores share one NetPay account or use different ones.

Webhook

The URL is registered once per NetPay account, not per transaction: the Update in Netpay button in the admin config (OXXO section) calls POST/PUT /v3/webhooks/ with {"webhook": "<url>"}, authenticated with the secret key. The charge request does not carry it (merchantRedirectUrl on the charge is the 3DS browser return, a different thing). Remember to Save Config afterwards — the button registers in NetPay, it does not persist the Magento value.

The receiver (Controller/Payment/ApiController, route netpay/payment/apicontroller) handles the same two events as NetPay's WooCommerce plugin, which this module is ported from. Both are asynchronous payment notifications — a card charge settles inline at checkout, not here. No notification is trusted: each one is re-verified server-to-server against the gateway.

Event Verified against Settles when Order located by
cep.paid (SPEI/CEP) GET /v3/transactions/{data.transactionId} status is DONE/CHARGEABLE and transactionTokenId and amount match the notification sales_order.token = data.transactionId
oxxopay.paid GET /v3/oxxopay/transaction/{data.transactionId} status is C and transactionId and amount match sales_order.token = data.reference
  • data.merchantRefCode (the order's entity id, sent as billing.merchantReferenceCode on the charge) is the fallback locator for both events — the WooCommerce plugin resolves OXXO through it.
  • Any other event is acknowledged with 200 and logged with its name, never guessed at: a non-payment event (a refund, a chargeback) would still read as a settled transaction on the gateway and must not invoice the order. The log is where an unknown event name will surface.
  • The order's grand total is checked against the notification amount before any gateway call.
  • Terminal declines (FAILED/REJECT) cancel the still-pending order — an addition over the WooCommerce plugin, which leaves it untouched.
  • Invoicing and the order note are idempotent: a repeated notification does not duplicate either.
  • Source IPs are checked against NetPay's ranges, anchored on the real TCP peer.
  • Config (keys/host) is scoped to the order's store, not the default store.

Architecture

Netpay/Payment/
├── registration.php          # module + self-contained SDK PSR-4 autoloader
├── composer.json             # magento2-module
├── etc/                      # di, system.xml, config.xml, webapi, db_schema, routes
├── Api/  Model/  Block/  Controller/  Helper/  Setup/  Logger/  view/
└── Sdk/                      # vendored NetPay PHP SDK (Swagger)
    ├── lib/                  # Netpay\Client\  (API + models)
    ├── businessLayer/        # BusinessLayer\Netpay\  (features, mapping)
    └── config/               # SDK .ini config (hosts, feature map)
  • The payment charge runs out-of-band via the POST /V1/payment/charges webapi (ChargesApiManagement), not through Magento's payment gateway commands.
  • Schema is declarative (etc/db_schema.xml): a token column on sales_order and the netpay_customer table.

Development

# Coding standard (excluding the vendored Swagger SDK)
vendor/bin/phpcs --standard=Magento2 --severity=10 --ignore="*/Sdk/*" app/code/Netpay/Payment

Known limitations / roadmap

  • Extends the deprecated Magento\Payment\Model\Method\AbstractMethod (works on 2.4.8; charging is out-of-band). Migration to the Payment Provider Gateway is planned.
  • Refunds are full only (NetPay's refund endpoint takes no amount); partial refunds are not supported. Capture is online at charge time.

See CHANGELOG.md for the full list of fixes (~40 across 6 audit passes, ~6 fatal).

License / provenance

Licensed under PolyForm Noncommercial 1.0.0 — see LICENSE. In short:

  • Free for noncommercial use, with attribution (keep the notice) and please let the author know you're using it (open an issue or contact @lordmacu).
  • Commercial / for-profit use requires a separate paid commercial license — contact @lordmacu.

Provenance: this is a community port of NetPay's official Magento module and it bundles NetPay's PHP SDK (Sdk/). Those underlying components are NetPay's proprietary property and are subject to NetPay's own terms. The license above is granted over the port contributions only — it does not grant rights to NetPay's software; obtain the appropriate authorization from NetPay for your use case. (Not legal advice.)

About

NetPay payment gateway for Magento 2 (Open Source 2.4.8 / PHP 8.4). Community port: self-contained SDK, WooCommerce-aligned 3-D Secure, multi-store, OXXO Pay + card vault. phpcs Magento2 clean.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages