paymentsuite/authorizenet-bundle
PaymentSuite integration for Authorize.Net that lets PHP applications route card payments through Authorize.Net’s gateway using configurable API credentials and PaymentSuite’s standard payment workflow, simplifying gateway setup within existing PaymentSuite-based projects.
First, the framing: despite the Laravel prompt, paymentsuite/authorizenet-bundle is a Symfony 2.x bundle, not a Laravel package. It ships a DependencyInjection/AuthorizenetExtension, a Resources/config/services.xml, and Symfony routes. On a modern Laravel app you cannot composer require it and use it — the constraints (symfony/symfony ~2.3`, PHP 5.3+) and the 2014 last-release date mean it will not resolve on PHP 8 / Laravel 10+. Treat it as reference source code, not a dependency.
What to read first:
AuthorizenetMethod.php — the thing that implements PaymentMethodInterface. This is where the authorize/capture flow lives and where the Authorize.net AIM payload is assembled.AuthorizenetManager.php — the network layer that actually posts to Authorize.net. This is the only piece genuinely worth porting.Resources/config/services.xml — shows the service IDs and constructor args (login_id, transaction_key, test), which tells you exactly which credentials the manager needs.Resources/config/routing.yml — the execute/success route names you'll need to replicate (or skip) if you reimplement.Minimal Symfony wiring (for context — what the bundle expects):
# config.yml
authorizenet_bundle:
login_id: "%authorizenet.login_id%"
transaction_key: "%authorizenet.transaction_key%"
test: true
method_name: authorizenet
// any controller
$method = $this->get('authorizenet.method'); // AuthorizenetMethod
$method->processPayment(); // hits Authorize.net AIM
First real use case in Laravel: open AuthorizenetManager.php, copy the AIM POST body (x_login, x_tran_key, x_version, x_delim_data, x_amount, x_card_num, x_exp_date, x_type=AUTH_CAPTURE), and wrap it in a Laravel service using the Http facade. That is the practical "getting started" — you're harvesting the API call, not the bundle.
The bundle follows PaymentSuite's core-bundle contract. Understanding the three moving parts makes the code readable even if you never run it:
1. Method lifecycle (PaymentMethodInterface)
interface PaymentMethodInterface {
public function processPayment(); // charge / authorize
public function getView(); // returns a PaymentMethodView (Blade-ish form data)
public function getName(); // "authorizenet"
}
AuthorizenetMethod::processPayment() pulls the amount from the bound PaymentBridge, calls AuthorizenetManager::process(), and dispatches a payment event. getView() returns the fields your form template renders — card number, expiry, CVC. In Laravel terms: a PaymentGateway class with a charge(Order $order, array $card): TransactionResult method plus a viewData() that feeds your Blade form.
2. Bridge pattern (PaymentBridgeInterface)
PaymentSuite decouples the bundle from your domain model. Your Order implements PaymentBridgeInterface (getAmount(), getCurrency(), getOrderId()), and the bundle only ever talks to the bridge. This is the one pattern worth keeping in a Laravel port:
interface PaymentBridge {
public function getAmount(): int; // in minor units
public function getCurrency(): string;
public function getOrderId(): string;
}
3. Events
The bundle dispatches authorizenet.payment.success / authorizenet.payment.failed via PaymentEvent. In Laravel this maps cleanly to Event::dispatch(new PaymentSucceeded($order)) from inside your service — listeners handle order fulfilment and receipts.
Day-to-day workflow you'd actually build (Laravel):
final class AuthorizeNetGateway
{
public function __construct(
private string $loginId,
private string $transactionKey,
private bool $test,
) {}
public function charge(Order $order, Card $card): TransactionResult
{
$response = Http::asForm()->post(
$this->test
? 'https://test.authorize.net/gateway/transact.dll'
: 'https://secure.authorize.net/gateway/transact.dll',
[
'x_login' => $this->loginId,
'x_tran_key' => $this->transactionKey,
'x_version' => '3.1',
'x_delim_data' => 'TRUE',
'x_delim_char' => '|',
'x_relay_response' => 'FALSE',
'x_type' => 'AUTH_CAPTURE',
'x_method' => 'CC',
'x_amount' => number_format($order->total / 100, 2, '.', ''),
'x_card_num' => $card->number,
'x_exp_date' => $card->expiry, // MMYY
'x_invoice_num' => $order->id,
],
);
return TransactionResult::fromDelimited($response->body());
}
}
Bind it in a service provider and inject it into a ProcessPayment job. That's the whole package, ported.
Integration tips
AUTH_ONLY + PRIOR_AUTH_CAPTURE (pass x_trans_id) if you ship-then-capture; AUTH_CAPTURE is the default in this bundle and captures immediately.payment_responses table alongside the parsed fields — you will need it for reconciliation.DB::transaction(...) around the order-state update after a successful charge, and always store x_trans_id before firing fulfilment.composer install will fail on PHP 8.2; Symfony 2.x bundles are not compatible with Symfony 5/6 either. Do not attempt to wire this into a Laravel app — extract and rewrite.| and indexes positionally. If a customer response or AVS message contains |, positions shift. Use x_delim_char that won't appear in the payload (e.g. | is still risky — many ports switch to , plus x_encap_char), or better, use Authorize.net's JSON/XML API for new work.PaymentBridge::getAmount() is in minor units but AIM x_amount is a decimal string. The bundle divides by 100 internally; if you port carelessly you'll charge 100× or 1/100×. Assert on amount in tests.x_test_request=TRUE rather than switching endpoints — the transact.dll URL is the same. A common bug is pointing at the sandbox hostname with test credentials; the two must agree.login_id/transaction_key are plain parameters. In a port, use config/services.php + env() and never log the request body (it contains the PAN).MMYY (not MM/YY) before sending, or Authorize.net returns a generic decline.authorizenet.manager; the "supported" way to customise was a compiler pass or overriding the service definition. In a Laravel port, make AuthorizeNetGateway an interface so you can swap in a fake in tests without hitting the network.2 = decline, 3 = error, 4 = held for review. The bundle collapses these into a boolean-ish failure. Capture x_response_reason_text (field 4) — that's the message support will ask for.x_type=VOID / REFUND / CREDIT) you'll implement yourself. Don't assume the bundle covers lifecycle beyond a single charge.How can I help you explore Laravel packages today?