Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Authorizenet Bundle Laravel Package

nti/authorizenet-bundle

Integrate Authorize.Net payments into Symfony/PHP applications with this bundle. It simplifies API setup for card charges, customer profiles, subscriptions, and transaction handling, helping developers add secure payment flows with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package — there is no service provider, no auto-discovery, and no config/packages convention that Laravel will pick up. The first decision is whether you actually want to introduce symfony/framework-bundle into a Laravel app just for a payment client (usually you don't) or whether you're working in a Symfony service that happens to be called from Laravel. If it's the latter, pull the underlying authorizenet/authorizenet SDK in directly and skip this bundle.

Assuming you're in a Symfony app:

  1. composer require nti/authorizenet-bundle authorizenet/authorizenet (the bundle does not pin the SDK transitively in all versions, so check composer why authorizenet/authorizenet). Latest release at time of writing is 1.1.4 — pin it exactly.
  2. Register it in config/bundles.php:
    Nti\AuthorizeNetBundle\NtiAuthorizeNetBundle::class => ['all' => true],
    
  3. Configure credentials. Look at the bundle's DependencyInjection/Configuration.php first — it's the source of truth for the exact key names, and they have shifted between releases:
    # config/packages/nti_authorize_net.yaml
    nti_authorize_net:
        environment: sandbox        # or "production"
        api_credentials:
            login_id: '%env(AUTHNET_LOGIN_ID)%'
            transaction_key: '%env(AUTHNET_TRANSACTION_KEY)%'
    
  4. Find the service IDs you'll inject. Open vendor/nti/authorizenet-bundle/Resources/config/services.yaml — this is the only reliable place to learn what the bundle actually registers. Expect something along the lines of nti.authorize_net.service.aim, …service.cim, and …service.arb wrapping the legacy AuthorizeNetAIM / AuthorizeNetCIM / AuthorizeNetARB classes.

First use case — a single card charge (AIM), with proper exception handling:

use Nti\AuthorizeNetBundle\Exception\ANetRequestException;
use Nti\AuthorizeNetBundle\Service\AimService;

public function __construct(private AimService $aim) {}

public function charge(int $cents, string $opaqueCard): array
{
    $this->aim->setAmount(number_format($cents / 100, 2, '.', ''));
    $this->aim->setCardNum($opaqueCard);
    $this->aim->setCardExp('2512');
    $this->aim->setDescription('Order #1042');

    try {
        $response = $this->aim->authorizeAndCapture();
    } catch (ANetRequestException $e) {
        // As of 1.1.4 the exception separates getCode() (gateway/transport
        // code) from getMessage() (human-readable reason) for CC requests.
        // Log code structurally; surface message to the user only if safe.
        return [
            'ok'      => false,
            'code'    => $e->getCode(),
            'message' => $e->getMessage(),
        ];
    }

    return [
        'ok'      => $response->getResponseCode() === '1', // APPROVED
        'transId' => $response->getTransactionId(),
        'message' => $response->getResponseReasonText(),
    ];
}

Note the return is the legacy AuthorizeNetAIM_Response, not the newer net\authorize\api\contract\v1 objects — the two SDK styles coexist in the Authorize.Net PHP ecosystem and this bundle predates/wraps the older one. That's the single biggest source of confusion when following Authorize.Net's current docs.

Implementation Patterns

Isolate the bundle behind your own interface. Because this bundle has zero stars, zero dependents, and a very thin release history, treat it as a replaceable adapter, not a foundation:

interface PaymentGateway
{
    public function charge(Money $amount, CardToken $card): ChargeResult;
}

final class AuthorizeNetGateway implements PaymentGateway
{
    public function __construct(private AimService $aim) {}

    public function charge(Money $amount, CardToken $card): ChargeResult
    {
        // normalize errors, map response codes AND exception codes, never leak vendor objects upward
    }
}

When the bundle inevitably breaks on a future Symfony major, you rewrite one class.

Handle the exception path explicitly. ANetRequestException is thrown for transport/request failures (timeouts, malformed card data, gateway rejects). Since 1.1.4 separates the code from the message for CC requests, you can now branch on getCode() for programmatic handling (e.g. distinguishing a declined-vs-transport failure) while keeping getMessage() for logs/user-facing copy. Before 1.1.4 the code was often folded into the message string — if you're upgrading, audit any code that parsed the message:

try {
    $response = $this->aim->authorizeAndCapture();
} catch (ANetRequestException $e) {
    $code = $e->getCode();      // 1.1.4+: structured code, use this
    $text = $e->getMessage();   // human-readable reason
    // don't strpos($text, 'declined') anymore — check $code
}

Match the service to the job. AIM for one-shot charges, CIM for stored customer profiles (create profile → createCustomerProfileFromTransaction → charge the profile later), ARB for recurring subscriptions. Don't try to build subscriptions out of loops of AIM charges — you lose the retry/dunning semantics and the card-updater that ARB provides.

Normalize the response before it touches your domain. The legacy response object mixes strings, mixed ints, and occasionally null:

$resultCode = $response->getResponseCode(); // '1' approved, '2' declined, '3' error, '4' held
$approved   = $resultCode === '1';
$declined   = $resultCode === '2'; // safe to surface to the customer
// everything else is a gateway/transport failure → throw, log, alert

Test against the sandbox with Authorize.Net's published test card numbers, and keep sandbox creds in a separate env block so nobody accidentally ships environment: sandbox to prod. A config assertion on boot (APP_ENV=prod must imply environment: production) has caught this more than once.

Mock the bundle service, not the network. In unit tests, bind your PaymentGateway interface to a stub. Integration tests hit the sandbox and assert on transaction_id round-tripping via getTransactionDetailsRequest. Add a test that forces an ANetRequestException to prove your getCode()/getMessage() handling survives a future bundle bump.

Gotchas and Tips

  • Symfony-only. In a Laravel codebase the bundle's Extension/CompilerPass machinery does nothing. You'd be calling $bundle->boot() by hand. Just use the SDK.
  • Maintenance risk is real. Zero stars, zero dependents, sparse release history (1.1.4 is the latest, and prior releases were far apart). Pin the version exactly ("nti/authorizenet-bundle": "1.1.4"), audit it for composer audit, and have a plan to fork or swap. The Authorize.Net SDK itself is what you actually depend on; the bundle is a thin service-definition layer.
  • Exception API changed in 1.1.4. ANetRequestException now exposes the code and message as separate values for CC requests. If you were parsing message strings (e.g. regexing out a code), migrate to getCode(). Message format may differ from earlier versions, so log both if you forward to an error tracker and want historical comparability.
  • Symfony version ceiling. The bundle's composer.json likely caps symfony/framework-bundle at ^4.4 || ^5.0. On Symfony 6/7 you'll hit Conclusion: don't install and need to fork with a widened constraint or use --ignore-platform-reqs (don't — the ContainerAwareCommand APIs really did change).
  • Response codes are strings, not booleans. if ($response->getResponseCode()) is truthy for '2' (declined) and '3' (error). Always compare against explicit '1'. Exception codes are typically numeric — don't confuse the two namespaces.
  • authorizeAndCapture vs authorizeOnly. The former is what you want for most checkouts; if you use authorizeOnly you must have a capture job, or held authorizations will expire and silently release funds. There's no scheduler in the bundle.
  • Amounts are strings with two decimals. number_format($cents/100, 2, '.', '') — passing a float like 10.1 can serialize as 10.1 and get rejected or, worse, silently rounded differently than your ledger.
  • No idempotency. Authorize.Net will happily double-charge on a retried curl timeout. Wrap charges with your own idempotency key stored in a DB row before calling the gateway, and reconcile via getTransactionDetailsRequest on timeout rather than retrying blindly.
  • PCI scope. Card data should never hit your server in production — use Authorize.Net's Accept.js / Accept Hosted tokenization and pass the opaque data / token into the AIM request. The bundle has no opinion here, and logging any request object will put PANs in your logs.
  • Debugging: dump the raw response with var_dump($response->getResponseReasonCode(), $response->getResponseReasonText()) — the reason text is far more informative than the code alone, and Authorize.Net's docs list the numeric reason codes. Also dump $response->getTransactionId() immediately; it's your only handle on the transaction. On exceptions, log getCode() and getMessage() separately (1.1.4+) — collapsing them loses the structured signal.
  • Extension points: override the bundle's Resources/config/services.yaml from your app by registering services with the same IDs under services.yaml (Symfony last-wins for aliases, but constructor args need a compiler pass in some cases), or simply decorate. The bundle exposes no events — if you need hooks (e.g. "on decline, notify"), wrap the service as above rather than patching vendor.
  • Recurring (ARB): the ARB service requires you to separately handle Silent Post / Relay Response webhooks for subscription failures. The bundle ships no controller for this; you'll write the endpoint and it must validate Authorize.Net's source IP or a hash, not a request signature (older ARB has none).
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route