# Authorize.Net

- Tool: FlutterFlow
- Difficulty: Advanced
- Time required: 60 minutes
- Last updated: July 2026

## TL;DR

Connect FlutterFlow to Authorize.Net via a Firebase Cloud Function that injects the merchantAuthentication credentials server-side — the Transaction Key can never appear in a FlutterFlow API Call body. FlutterFlow sends only the charge amount and an Accept.js opaque nonce to the Function; the Function builds the full createTransactionRequest and forwards it to Authorize.Net's single JSON endpoint.

## Authorize.Net's unusual API design and why it changes the FlutterFlow architecture

Most payment APIs put your API key in a request header. Authorize.Net does not — it puts the API Login ID and Transaction Key inside a merchantAuthentication object in every JSON request body. This is unusual, and in FlutterFlow it creates a critical trap: if you build an API Call with a hardcoded JSON body containing your Transaction Key, that key ships inside every compiled app bundle. The Transaction Key is equivalent to a live credit card processing credential — anyone with it can charge cards against your merchant account.

The entire Authorize.Net integration therefore runs through a Firebase Cloud Function. The Function receives only safe parameters from FlutterFlow (the charge amount, an opaque tokenized card nonce, or a transaction ID for lookup/refund) and constructs the full request body with merchantAuthentication server-side before forwarding to Authorize.Net's JSON endpoint. This is not optional for production — it is structurally required by PCI DSS.

There is a second PCI concern specific to in-app card capture: you must never handle raw card numbers (PAN) in Dart code. Authorize.Net's solution is Accept.js and Accept Mobile — tokenization libraries that capture the card number in a secure context and return an opaqueData nonce. Your Flutter app passes only the nonce to the Firebase Function, keeping raw card data entirely out of your codebase and out of PCI scope. Note that FlutterFlow's built-in Stripe and Braintree payment widgets do not apply to Authorize.Net — this is a fully manual integration.

Authorize.Net pricing is approximately $25/month for the gateway fee plus per-transaction fees (verify current rates on their website). Sandbox and production are separate base URLs — the sandbox at https://apitest.authorize.net and production at https://api2.authorize.net — and you must test thoroughly in the sandbox before switching the Function to production.

## Before you start

- An Authorize.Net merchant account (sandbox accounts are free at developer.authorize.net)
- Your Authorize.Net API Login ID and Transaction Key (Account → Settings → API Credentials & Keys; get both sandbox and production pairs)
- A Firebase project with Cloud Functions on the Blaze pay-as-you-go plan (required for outbound HTTP calls)
- A FlutterFlow project on any plan (API Calls are available on all plans)
- Basic understanding of PCI DSS requirements for card data handling

## Step-by-step guide

### 1. Get Authorize.Net credentials and understand the sandbox setup

Log in to your Authorize.Net merchant account at login.authorize.net. Navigate to Account → Settings → API Credentials & Keys. You will see your API Login ID (not secret — this is a merchant identifier) and can generate a Transaction Key (this IS the secret — treat it like a password for your merchant account).

For the sandbox, go to developer.authorize.net and create a free sandbox account separately. The sandbox issues its own API Login ID and Transaction Key that are completely independent from production credentials. Never mix sandbox and production credentials.

IMPORTANT: Authorize.Net's API has a single endpoint for all operations — the operation is specified by the top-level key in the JSON body, not by the URL path:
- Sandbox: https://apitest.authorize.net/xml/v1/request.api
- Production: https://api2.authorize.net/xml/v1/request.api

Your Firebase Function will have an environment variable to toggle which URL to use. Testing in the sandbox charges no real cards and lets you see full transaction details in the sandbox dashboard at sandbox.authorize.net.

If you plan to charge cards in the app, also download the Authorize.Net Accept SDK from their developer portal. The Accept Mobile library for iOS and Android handles the card tokenization — it sends the card details directly to Authorize.Net and returns an opaqueData object containing a nonce that your app passes to the Firebase Function. Your Flutter app never sees the card number itself.

**Expected result:** You have both sandbox and production API Login ID + Transaction Key pairs stored securely (not in any code). The Authorize.Net sandbox dashboard is accessible for verifying test transactions.

### 2. Deploy a Firebase Cloud Function with merchantAuthentication injection

Create your Firebase Cloud Function that acts as the secure proxy between FlutterFlow and Authorize.Net. This Function holds the Transaction Key as an environment variable and builds the merchantAuthentication block for every request.

In Firebase Console → Functions → Configuration, add four environment variables: AUTHORIZE_LOGIN_ID (your API Login ID), AUTHORIZE_TRANSACTION_KEY (your Transaction Key), and AUTHORIZE_SANDBOX set to 'true' for sandbox testing. Never put these values in source code.

The Function receives an action field from FlutterFlow (chargeCard, refundTransaction, searchTransactions, voidTransaction) along with the action-specific parameters. For each action, it builds the correct Authorize.Net JSON request type with merchantAuthentication injected server-side, posts to the appropriate endpoint, and returns the response to FlutterFlow.

Note the single-endpoint design: in the Function, you switch between operation types by changing the top-level request type key in the JSON body (createTransactionRequest, searchTransactionsRequest, etc.) — all going to the same URL. This means one Firebase Function route can handle all operations by reading the action field from the request body.

Authorize.Net can return XML on some legacy endpoints — verify JSON responses by setting the content type in the request and checking the response. Modern JSON API responses include a resultCode ('Ok' or 'Error') and messages array that your Function should check before returning success to FlutterFlow.

```
// functions/index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const fetch = require('node-fetch');
admin.initializeApp();

const LOGIN_ID = process.env.AUTHORIZE_LOGIN_ID;
const TRANSACTION_KEY = process.env.AUTHORIZE_TRANSACTION_KEY;
const SANDBOX = process.env.AUTHORIZE_SANDBOX === 'true';
const BASE_URL = SANDBOX
  ? 'https://apitest.authorize.net/xml/v1/request.api'
  : 'https://api2.authorize.net/xml/v1/request.api';

const merchantAuthentication = {
  name: LOGIN_ID,
  transactionKey: TRANSACTION_KEY,
};

exports.authorizeNet = functions.https.onRequest(async (req, res) => {
  res.set('Access-Control-Allow-Origin', '*');
  if (req.method === 'OPTIONS') { res.status(204).send(''); return; }

  try {
    const { action, amount, opaqueData, transactionId, refundAmount } = req.body;
    let requestBody;

    if (action === 'chargeCard') {
      requestBody = {
        createTransactionRequest: {
          merchantAuthentication,
          transactionRequest: {
            transactionType: 'authCaptureTransaction',
            amount,
            payment: { opaqueData },
          },
        },
      };
    } else if (action === 'refundTransaction') {
      requestBody = {
        createTransactionRequest: {
          merchantAuthentication,
          transactionRequest: {
            transactionType: 'refundTransaction',
            amount: refundAmount,
            refTransId: transactionId,
          },
        },
      };
    } else if (action === 'voidTransaction') {
      requestBody = {
        createTransactionRequest: {
          merchantAuthentication,
          transactionRequest: {
            transactionType: 'voidTransaction',
            refTransId: transactionId,
          },
        },
      };
    } else if (action === 'searchTransactions') {
      const { startDate, endDate, offset = 1 } = req.body;
      requestBody = {
        getSettledBatchListRequest: {
          merchantAuthentication,
          firstSettlementDate: startDate,
          lastSettlementDate: endDate,
        },
      };
    } else {
      res.status(400).json({ error: 'Unknown action' });
      return;
    }

    const authRes = await fetch(BASE_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(requestBody),
    });
    const data = await authRes.json();
    res.json(data);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});
```

**Expected result:** The authorizeNet Firebase Function is deployed. Calling it with {"action": "searchTransactions", "startDate": "2024-01-01T00:00:00Z", "endDate": "2024-01-31T23:59:59Z"} returns settled batch data from the Authorize.Net sandbox.

### 3. Configure FlutterFlow API Calls for each operation

In FlutterFlow, open the left nav → API Calls → + Add → Create API Group. Name the group AuthorizeNet. Set the Base URL to your Firebase Function HTTPS trigger URL. No headers needed — authentication is handled server-side.

Add these API Calls inside the group (all POST to the empty path):

chargeCard: Body {"action": "chargeCard", "amount": "{{ amount }}", "opaqueData": {"dataDescriptor": "{{ descriptor }}", "dataValue": "{{ dataValue }}"}}
Variables: amount (String — use string representation of the decimal amount, e.g., '9.99'), descriptor (String), dataValue (String). The descriptor and dataValue come from the Accept.js/Accept Mobile tokenization step.

refundTransaction: Body {"action": "refundTransaction", "transactionId": "{{ transactionId }}", "refundAmount": "{{ amount }}"}. Variables: transactionId (String), amount (String).

voidTransaction: Body {"action": "voidTransaction", "transactionId": "{{ transactionId }}"}. Variable: transactionId (String).

searchTransactions: Body {"action": "searchTransactions", "startDate": "{{ startDate }}", "endDate": "{{ endDate }}"}. Variables: startDate, endDate (String, ISO-8601 format).

In the Response & Test tab for each call, paste a sample successful Authorize.Net response and use Generate JSON Paths to extract the fields you need. For chargeCard, key paths include $.createTransactionResponse.messages.resultCode, $.createTransactionResponse.transactionResponse.transId, $.createTransactionResponse.transactionResponse.authCode.

For the refund and void operations, check $.createTransactionResponse.messages.resultCode for 'Ok' to confirm success in your Action Flow with a conditional branch.

**Expected result:** Four API Calls (chargeCard, refundTransaction, voidTransaction, searchTransactions) appear in the AuthorizeNet API group. Each can be test-called from the Response & Test tab against the sandbox.

### 4. Implement Accept.js tokenization for in-app card capture

Because raw card numbers must never appear in Dart code, Authorize.Net provides the Accept Mobile SDK for tokenization. This step shows how to set up tokenization so the opaqueData nonce that feeds the chargeCard API Call is generated securely.

For mobile apps (iOS and Android), the recommended approach is to use a WebView widget in FlutterFlow that loads a lightweight tokenization HTML page hosted on your own domain. The page uses Accept.js (loaded from Authorize.Net's CDN) to collect the card number, expiry, and CVV in a form, submits them to Authorize.Net directly via the Accept.js dispatchData() function, and receives the opaqueData response without the card number ever hitting your server or your Flutter code.

The HTML page uses JavaScript postMessage to send the opaqueData back to Flutter. In FlutterFlow, add a Custom Widget that wraps the webview_flutter pub.dev package. The widget listens for postMessage events from the WebView and returns the descriptor and dataValue to App State.

Alternatively, for a simpler prototype path, you can use Authorize.Net's hosted payment form (a URL they provide) in a WebView — the user completes the payment on Authorize.Net's hosted page and you receive a confirmationCode via a redirect. This has less UI control but zero PCI scope on your side.

Once you have the opaqueData in App State (dataDescriptor and dataValue fields), bind them as variables in the chargeCard API Call and fire it from the checkout button's action flow. If you need help setting up the Accept.js tokenization flow end-to-end, RapidDev's team builds FlutterFlow payment integrations like this regularly — reach out at rapidevelopers.com/contact for a free scoping call.

```
<!-- accept_tokenization.html (hosted on your domain) -->
<!DOCTYPE html>
<html>
<head>
  <script type="text/javascript"
    src="https://jstest.authorize.net/v1/Accept.js"
    charset="utf-8">
  </script>
</head>
<body>
  <form id="paymentForm">
    <input type="text" id="cardNumber" placeholder="Card Number" />
    <input type="text" id="expMonth" placeholder="MM" />
    <input type="text" id="expYear" placeholder="YYYY" />
    <input type="text" id="cardCode" placeholder="CVV" />
    <button type="button" onclick="sendPaymentDataToAnet()">Tokenize</button>
  </form>
  <script>
    function sendPaymentDataToAnet() {
      var authData = {};
      authData.clientKey = 'YOUR_PUBLIC_CLIENT_KEY';
      authData.apiLoginID = 'YOUR_API_LOGIN_ID';
      var cardData = {};
      cardData.cardNumber = document.getElementById('cardNumber').value;
      cardData.month = document.getElementById('expMonth').value;
      cardData.year = document.getElementById('expYear').value;
      cardData.cardCode = document.getElementById('cardCode').value;
      var secureData = {};
      secureData.authData = authData;
      secureData.cardData = cardData;
      Accept.dispatchData(secureData, responseHandler);
    }
    function responseHandler(response) {
      if (response.messages.resultCode === 'Ok') {
        window.flutter_inappwebview.callHandler('opaqueData', {
          descriptor: response.opaqueData.dataDescriptor,
          value: response.opaqueData.dataValue
        });
      }
    }
  </script>
</body>
</html>
```

**Expected result:** The tokenization flow collects card details in a WebView, sends them to Authorize.Net, receives an opaqueData nonce, and passes it back to FlutterFlow App State. The chargeCard API Call then uses the nonce without any raw card data.

### 5. Verify in sandbox and switch to production

Before flipping to production, run a complete end-to-end test in the Authorize.Net sandbox. Use test card numbers from developer.authorize.net (e.g., 4111 1111 1111 1111 for Visa approval, 4000 0000 0000 0002 for a decline) through your Accept.js tokenization flow, verify the chargeCard call succeeds in the Firebase Function logs, and confirm the transaction appears in the sandbox dashboard at sandbox.authorize.net.

Test every operation: chargeCard (approve and decline scenarios), refundTransaction (requires a settled transaction — wait or use a test settled transaction ID), voidTransaction (works on unsettled transactions), and searchTransactions.

When you are ready for production: in Firebase Console → Functions → Configuration, update AUTHORIZE_LOGIN_ID and AUTHORIZE_TRANSACTION_KEY to your production credentials and set AUTHORIZE_SANDBOX=false. Redeploy the Function. The HTTPS trigger URL stays the same — FlutterFlow does not need any changes. In your Accept.js tokenization HTML, switch the script URL from jstest.authorize.net to js.authorize.net.

IMPORTANT: The production base URL is https://api2.authorize.net (note api2, not api) — confirm your Function uses the correct production URL when AUTHORIZE_SANDBOX is false. A misconfigured URL will fail silently with a connection error rather than an authentication error, making it hard to diagnose.

**Expected result:** Full end-to-end payment flow tested in sandbox with all four operations working correctly. Production credentials set in Firebase Functions configuration. Accept.js pointing to production CDN URL.

## Best practices

- Never place the Authorize.Net Transaction Key in any FlutterFlow API Call body, header, or App Values constant — it ships inside the compiled app and constitutes a PCI DSS violation.
- Use Accept.js or Accept Mobile tokenization for all in-app card capture — raw card numbers (PAN) must never enter your Dart code, Firebase Functions, or Firestore to minimize PCI DSS scope.
- Keep sandbox and production as completely separate Firebase Function configurations with distinct environment variable values, and test every operation thoroughly in sandbox before touching production credentials.
- Always check the resultCode field in Authorize.Net responses — the API returns HTTP 200 even for declined transactions, so HTTP status alone is not sufficient to determine success.
- Use voidTransaction for same-day cancellations before settlement; only use refundTransaction for settled transactions from previous days — mixing them up causes transaction errors.
- Note the production base URL difference: api2.authorize.net (not api.authorize.net) — test your Function's URL construction explicitly before going live.
- Rotate your Transaction Key periodically in Account → Settings → API Credentials & Keys and update the Firebase Functions configuration immediately — the old key is invalidated on rotation.

## Use cases

### In-app payment for a service booking app

A FlutterFlow service marketplace app charges customers when they book a service. The card is captured using an Accept Mobile tokenization step that returns an opaqueData nonce; the nonce is sent to a Firebase Function that calls Authorize.Net's createTransactionRequest. The Function returns the transaction ID and authorization code, which the app saves to Firestore.

Prompt example:

```
Build a checkout screen that collects card details via Accept Mobile tokenization, shows the total amount, and has a Pay button that sends the nonce and amount to a Firebase Function and displays a confirmation with the transaction ID.
```

### Admin dashboard for transaction search and refunds

A FlutterFlow internal ops tool lets customer support staff search Authorize.Net transactions by settlement date, view transaction details, and issue full or partial refunds. All calls go through the Firebase Function with the refundTransaction or searchTransactions operation types. A confirmation dialog prevents accidental refunds.

Prompt example:

```
Build a transactions page that searches by date range, shows results in a ListView with amount, status, and card last-four, and has a Refund button on the detail page that calls the Firebase Function and shows the refund confirmation.
```

### Subscription management panel

A FlutterFlow app surfaces Authorize.Net Automated Recurring Billing (ARB) subscriptions for a SaaS product. Support staff can view active subscriptions, cancel them, or update the billing amount. All ARB calls route through the Firebase Function since they use the same merchantAuthentication pattern.

Prompt example:

```
Build a subscriptions management screen that lists active ARB subscriptions with customer name, amount, and next billing date, and has a Cancel Subscription button with a confirm dialog.
```

## Troubleshooting

### Firebase Function returns a 200 response but the charge fails with resultCode='Error' and text 'User authentication failed'

Cause: The AUTHORIZE_LOGIN_ID or AUTHORIZE_TRANSACTION_KEY environment variable in Firebase Functions is wrong, empty, or from the wrong environment (e.g., sandbox credentials used with the production URL).

Solution: In Firebase Console → Functions → Configuration, verify both variables match the environment. If using sandbox, confirm the Function is pointing at https://apitest.authorize.net and using sandbox credentials from developer.authorize.net, not your production merchant credentials.

### The Accept.js tokenization returns an error 'E_WC_10 This clientKey is not valid'

Cause: The Public Client Key used in the Accept.js form is the production key but the Accept.js script URL is the sandbox URL (jstest.authorize.net), or vice versa. They must match environments.

Solution: In the Authorize.Net merchant interface, go to Account → Settings → Manage Public Client Key. Get the sandbox client key from the sandbox dashboard and the production key from the production merchant interface. Match them with their corresponding Accept.js CDN URLs.

### Refund call returns 'The referenced transaction does not meet the criteria for issuing a credit'

Cause: Refunds in Authorize.Net can only be issued against settled transactions (typically settled the next business day). Attempting to refund a transaction that is still pending or unsettled returns this error.

Solution: For same-day cancellations before settlement, use voidTransaction instead of refundTransaction. Implement logic in your FlutterFlow action flow that checks the transaction's settlement status and routes to void (for unsettled) or refund (for settled) accordingly.

### Firebase Function receives a response from Authorize.Net that looks like XML instead of JSON

Cause: Some Authorize.Net endpoint paths or configurations return XML by default. The modern JSON API should return JSON, but legacy endpoint paths can return XML.

Solution: Confirm the Function is posting to the correct endpoint (/xml/v1/request.api) with Content-Type: application/json in the request headers. If you still receive XML, add an Accept: application/json header to the fetch request in the Function.

## Frequently asked questions

### Does FlutterFlow have a built-in Authorize.Net payment widget like it does for Stripe?

No — FlutterFlow's native In-App Purchases panel supports Stripe and Braintree. Authorize.Net is not a native integration. You will need the Firebase Cloud Function proxy described in this guide plus Accept.js tokenization for card capture. This is more setup work than Stripe, but follows the same underlying principles.

### Is it safe to put the Authorize.Net API Login ID in FlutterFlow?

The API Login ID alone is not sufficient to charge cards — it must be combined with the Transaction Key. However, best practice is to keep both out of the client app and handle them entirely in the Firebase Function. The Transaction Key is the true secret and must never be in client code. Keeping both credentials exclusively server-side is the cleanest and safest approach.

### Can I test the full payment flow without a real Authorize.Net merchant account?

Yes — Authorize.Net offers free sandbox accounts at developer.authorize.net. The sandbox uses the same API and supports test card numbers that simulate approved, declined, and error scenarios. Sandbox transactions are never real charges and appear only in the sandbox dashboard. Always build and test in sandbox before applying for and connecting a live merchant account.

### What is the difference between void and refund in Authorize.Net?

A void cancels a transaction before it has been settled (typically the same day it was created). A refund (credit) is issued against a previously settled transaction, usually from a prior business day. Attempting to refund an unsettled transaction returns an error — use void instead. FlutterFlow apps handling same-day cancellations should detect the settlement status and route to the correct operation.

### Are there PCI DSS requirements I need to be aware of for this integration?

Yes. By using Accept.js tokenization and a Firebase Function proxy, your app achieves SAQ A or SAQ A-EP PCI compliance (the lightest scope), because raw card data never touches your Flutter code, your Functions, or your database. If you skip tokenization and handle card numbers anywhere in your system, you fall into a much broader and more complex PCI compliance scope. Consult Authorize.Net's developer documentation and your payment processor for the specific PCI questionnaire that applies to your integration design.

---

Source: https://www.rapidevelopers.com/flutterflow-integrations/authorize-net
© RapidDev — https://www.rapidevelopers.com/flutterflow-integrations/authorize-net
