Quickstart

Learn about the Forage JS methods and Payments API endpoints that you need to interact with to accept EBT SNAP online.

Forage recommends splitting your development process into three phases:

  1. Add the core EBT payment flow
  2. Implement transaction confirmation and refunds
  3. Catalog EBT eligible items

This guide includes sample code and lists the relevant reference documents for each phase.

Phase 1: Add the core EBT payment flow

Set up authentication

  1. Create a backend API route that generates session tokens that your front-end can access (Create a session token).
const axios = require('axios')
const express = require('express')
const bodyParser = require('body-parser')

const app = express()
const port = 5000

// middleware to parse the request body
app.use(bodyParser.urlencoded({ extended: true }))

app.post('/api/session_token', async (req, res) => {
  try {
    const apiUrl = 'https://api.sandbox.joinforage.app/api/session_token/'
    // replace with your authentication token
    const authenticationToken = '<authentication-token>'
    // replace with your Forage merchant ID
    const merchantId = 'mid/<merchant-id>'

    const headers = {
      Accept: 'application/json',
      Authorization: `Bearer ${authenticationToken}`,
      'Merchant-Account': merchantId, // replace with your Forage merchant ID
      'Content-Type': 'application/x-www-form-urlencoded'
    }

    const response = await axios.post(apiUrl, null, { headers })

    res.json(response.data)
  } catch (error) {
    res
      .status(error.response ? error.response.status : 500)
      .json({ error: 'Something went wrong' })
  }
})

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`)
})
# app.rb
require 'sinatra'
require 'net/http'
require 'json'

class ApiController < Sinatra::Base
  post '/session_token' do
    begin
      api_url = 'https://api.sandbox.joinforage.app/api/session_token/'
      # replace with your authentication token
      authentication_token = '<authentication-token>'
      # replace with your Forage merchant ID
      merchant_id = 'mid/<merchant-id>'

      uri = URI(api_url)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true

      headers = {
        'Accept' => 'application/json',
        'Authorization' => "Bearer #{authentication_token}",
        'Merchant-Account' => merchant_id,
        'Content-Type' => 'application/x-www-form-urlencoded',
      }

      response = http.post(uri.path, nil, headers)

      if response.is_a?(Net::HTTPSuccess)
        json_response = JSON.parse(response.body)
        content_type :json
        json_response.to_json
      else
        status response.code
        { error: 'Something went wrong' }.to_json
      end
    rescue StandardError => e
      status e.respond_to?(:status) ? e.status : 500
      { error: 'Something went wrong' }.to_json
    end
  end
end

📘

For more information on authenticating with Forage, refer to the authentication guide.

Collect and store an EBT Card

  1. Create a parent container for the Forage Element, as in the following example:
<div id="card-tokenization-form-container" style="width: 300px; height: 80px;"></div>
  1. After initializing Forage JS, create and add the ForageCardElement component to your front-end checkout flow to collect a customer’s EBT Card number (Create a ForageCardElement).
const tokenizeCardElement = forage.create('tokenize_card')

tokenizeCardElement.mount('card-tokenization-form-container')
  1. Call the tokenizeCard() submit method from your front-end to submit a customer’s EBT Card number to Forage (Submit a ForageCardElement).
    1. tokenizeCard() creates a Forage PaymentMethod object that represents the EBT Card number. It also returns a ref for the PaymentMethod.
    2. Handle potential errors (Forage JS errors).
async function submitCardNumber() {
  try {
    const tokenizationResult = await forage.tokenizeCard(
      tokenizeCardElement
    )
    return tokenizationResult
  } catch (error) {
    const { httpStatusCode, message, code } = error ?? {}
  }
}
  1. Create a backend API route to associate the ref for the PaymentMethod with the customer’s account in your database. You need this ref later to check the balance of an EBT Card or Authorize a Payment.
// replace with your database setup
const database = new Map()

// middleware to parse the request body
app.use(bodyParser.json())

app.post('/api/add_forage_payment_method', (req, res) => {
  try {
    const { ref } = req.body

    // replace with your database logic
    database.set(customerId, ref)

    res.json({
      success: true,
      message: 'Forage PaymentMethod added successfully'
    })
  } catch (error) {
    res.status(500).json({ success: false, error: 'Something went wrong' })
  }
})
# app.rb
require 'sinatra'
require 'net/http'
require 'json'

class ApiController < Sinatra::Base
  post '/add_forage_payment_method' do
    begin
      payment_method_ref = params[:ref]

      # Replace the following logic with your ORM model or database setup
      forage_payment_method = ForagePaymentMethod.new(customer_id: current_user.id, ref: ref)

      if forage_payment_method.save
        content_type :json
        { success: true, message: 'Forage PaymentMethod added successfully' }.to_json
      else
        status 500
        { success: false, error: 'Something went wrong' }.to_json
      end
    rescue StandardError => e
      status 500
      { success: false, error: 'Something went wrong' }.to_json
    end
  end
end
  1. Send the response from Step 3 to your backend using the endpoint you created in Step 4.
async function addForagePaymentMethod() {
  // tokenize the EBT card number and get the result
  // this is the function you wrote in Step 2
  const tokenizationResult = await handleSubmit()

  if (tokenizationResult) {
    const { ref: ref, type, balance, card } = tokenizationResult

    fetch('http://localhost:5000/api/add_forage_payment_method', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ ref, type, balance, card })
    })
      .then((response) => response.json())
      .then((data) => {
        console.log('Response from the backend:', data)
      })
      .catch((error) => {
        console.error('Error occurred during fetch:', error)
      })
  } else {
    console.error(
      'Failed to add Forage PaymentMethod:',
      tokenizationResult.error
    )
  }
}

Check and display the balance of an EBT Card

🚧

FNS requirements for balance inquiries

FNS prohibits balance inquiries on sites that offer guest checkout. Skip this section of the tutorial if your site offers a guest checkout option.

If your site doesn't offer guest checkout, then it's up to you whether or not to add a balance inquiry feature. No FNS regulations apply.

  1. Add the ForageBalanceElement component to your front-end checkout flow to collect a customer's EBT Card PIN to perform a balance inquiry (Create a ForageBaanceElement).
const balanceElement = forage.create('collect_pin')

balanceElement.mount('balance-check-container')
  1. Call the checkBalance() submit method from your front-end to submit a customer’s EBT PIN to Forage (Submit a ForageBalanceElement).
    1. checkBalance() returns the snap and non_snap (EBT Cash) balance available on the EBT Card, in addition to a timestamp for when the balance was last updated.
    2. Handle potential errors (Forage JS errors).
async function handleSubmit() {
  try {
    const balanceResult = await forage.checkBalance(
      balanceElement,
      paymentMethodRef
    )
    const { ebt, updated } = balanceResult
    setSnapBalance(ebt?.snap)
    setCashBalance(ebt?.cash)
  } catch (error) {
    const { httpStatusCode, message, code } = error ?? {}
  }
}
  1. Display the customer’s balance.
<div>
   SNAP Balance: <span id="balance-snap">{snapBalance}</span>
</div>
<div>
   Cash Balance: <span id="balance-cash">{cashBalance}</span>
</div>

Authorize an EBT Payment

  1. Prompt the customer to set the SNAP and/or EBT Cash amount to apply to the purchase.
    1. You need to pass these values to the POST /payments/ request to create the Payment object in the next step.
<form onSubmit={handleSubmit} aria-label="SNAP amount form">
  <label htmlFor="snapAmount">Enter SNAP amount to apply:</label>
  <input 
    type="number" 
    id="snapAmount" 
    name="snapAmount" 
    aria-required="true"
  />
  <button type="submit">Complete purchase</button>
</form>
  1. From your backend, call the Forage API to create a Payment object (Create a Payment).
    1. Pass the amounts that the customer indicated in Step 1 in the body of the request as the amount and funding_type params.
    2. The API returns a Forage Payment object that represents the EBT transaction and returns a ref for the Payment. You need to pass the ref as one of the parameters to the capturePayment() method in Step 4.

🚧

To keep your app secure, requests to create a payment should only be generated server-side.

const express = require('express')
const bodyParser = require('body-parser')
const axios = require('axios')

const app = express()
const port = 5000

app.use(bodyParser.json())

app.post('/api/create_payment', async (req, res) => {
  try {
    const { snapAmount } = req.body

    if (isNaN(snapAmount) || snapAmount <= 0) {
      return res.status(400).json({ success: false, error: 'Invalid amount' })
    }

    const paymentPayload = {
      amount: snapAmount,
      funding_type: 'ebt_snap',
      payment_method: '<payment-method-ref>', // replace with the ref for the Payment Method to be charged
      description: 'Test EBT SNAP payment',
      metadata: {},
      is_delivery: true,
      delivery_address: {
        country: 'US',
        city: 'Los Angeles',
        line1: '4121 Santa Monica Blvd',
        line2: 'Unit 513',
        zipcode: '90029',
        state: 'CA'
      }
    }

    const headers = {
      Accept: 'application/json',
      Authorization: `Bearer ${authenticationToken}`,
      'Merchant-Account': 'mid/<merchant-id>', // replace with your Forage merchant ID
      'Content-Type': 'application/json',
      'IDEMPOTENCY-KEY': Math.random() * 10000 // udpate me in production: https://docs.joinforage.app/reference/idempotency
    }

    const response = await axios.post(
      'https://api.sandbox.joinforage.app/api/payments/',
      paymentPayload,
      { headers }
    )

    const foragePayment = response.data
    console.log('Forage Payment created:', foragePayment)

    res.json({ success: true, payment: foragePayment })
  } catch (error) {
    console.error('Error occurred:', error.message)
    res.status(500).json({ success: false, error: 'Something went wrong' })
  }
})

app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`)
})
# app.rb
require 'sinatra'
require 'net/http'
require 'json'

class PaymentsController < Sinatra::Base
  post '/create_payment' do
    begin
      snap_amount = params[:snapAmount]

      if snap_amount.to_i <= 0
        status :bad_request
        return { success: false, error: 'Invalid amount' }.to_json
      end

      payment_payload = {
        amount: snap_amount,
        funding_type: 'ebt_snap',
        payment_method: '<payment-method-ref>', # replace with the ref for the Payment Method to be charged
        description: 'Test EBT SNAP payment',
        metadata: {},
        is_delivery: true,
        delivery_address: {
          country: 'US',
          city: 'Los Angeles',
          line1: '4121 Santa Monica Blvd',
          line2: 'Unit 513',
          zipcode: '90029',
          state: 'CA'
        }
      }

      api_url = 'https://api.sandbox.joinforage.app/api/payments/'
      authentication_token = '<authentication-token>' # replace with your authentication token

      uri = URI(api_url)
      http = Net::HTTP.new(uri.host, uri.port)
      http.use_ssl = true

      headers = {
        'Accept' => 'application/json',
        'Authorization' => "Bearer #{authentication_token}",
        'Merchant-Account' => 'mid/<merchant-id>', # replace with your Forage merchant ID
        'Content-Type' => 'application/json',
        'IDEMPOTENCY-KEY' => rand(10000).to_s # update me in production: https://docs.joinforage.app/reference/idempotency
      }

	    response = http.post(uri.path, payment_payload.to_json, headers)

      if response.is_a?(Net::HTTPSuccess)
        forage_payment = JSON.parse(response.body)
        puts 'Forage Payment created:', forage_payment

        content_type :json
        { success: true, payment: forage_payment }.to_json
      else
        status :internal_server_error
        { success: false, error: 'Something went wrong' }.to_json
      end
    rescue StandardError => e
      status :internal_server_error
      { success: false, error: 'Something went wrong' }.to_json
    end
  end
end
  1. Add the ForagePinElement to your front-end checkout flow (Create a ForagePinElement ).
    1. This element collects a customer’s EBT Card PIN. You need to pass the ForagePinElement as one of the parameters to the capturePayment() method in the next step.
const snapPaymentCaptureElement = forage.create('collect_pin')

snapPaymentCaptureElement.mount('payment-capture-container')
  1. Pass the ForageEbtPaymentElement and a ref to the Payment object returned in Step 2 to the capturePayment() method (Submit a ForageEbtPaymentElement).
    1. This method authorizes and captures the EBT payment.
async function handleSubmit() {
  try {
    const paymentCaptureResult = await forage.capturePayment(
      snapPaymentCaptureElement,
      paymentRef
    )
    // handle successful paymentCaptureResult
  } catch (error) {
    const { code, httpStatusCode, message } = error ?? {}
  }
}

Other features to implement during Phase 1:

  • Tax handling
  • Split tender , by using Forage JS alongside a credit/debit PSP
  • Errors/refunds for non-EBT payment methods through your PSP

Phase 2: Implement transaction confirmation and refunds

  1. Add transaction confirmation and receipts to your integration flow.
  2. Handle EBT refunds (Create a refund for a payment, partial refunds guide).

Phase 3: Catalog EBT eligible items

  1. Tag EBT eligible products in your database and reflect eligibility in the front-end UI.
  2. Increase discoverability of EBT eligible products: make sure that customers can search and filter for EBT eligible items.

Next steps

Add input validation

Listen for the change event on a front-end Forage Element to validate the customer’s input and to enable the submit function, as in the below example.

balanceElement.on('change', (event) => {
  if (event.error) {
    // show validation error to customer
    setValidationMessage(event.error.message);
  } else {
    // hide validation error
    setValidationMessage('');
  }

  if (event.complete) {
    // enable submit button
    setDisabled(false);
  }
});

Style Forage Elements

The width and height must be set on an Forage Element’s parent container in order to adjust the size of the element, as in the following example.

<div id="card-tokenization-form-container" style="width: 300px; height: 80px;"></div>

Explore more resources