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:
- Add the core EBT payment flow
- Implement transaction confirmation and refunds
- Catalog EBT eligible items
This guide includes sample code and lists the relevant reference documents for each phase.
Prerequisites
- Sign up for a Forage account (contact us if you don’t yet have one)
Phase 1: Add the core EBT payment flow
Set up authentication
- 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, as in 123ab45c67.
const merchantId = '<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
- 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>
- 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 aForageCardElement
).
const tokenizeCardElement = forage.create('tokenize_card')
tokenizeCardElement.mount('card-tokenization-form-container')
- Call the
tokenizeCard()
submit method from your front-end to submit a customer’s EBT Card number to Forage (Submit aForageCardElement
).tokenizeCard()
creates a ForagePaymentMethod
object that represents the EBT Card number. It also returns aref
for thePaymentMethod
.- 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 ?? {}
}
}
- Create a backend API route to associate the
ref
for thePaymentMethod
with the customer’s account in your database. You need thisref
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
- 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.
- Add the
ForageBalanceElement
component to your front-end checkout flow to collect a customer's EBT Card PIN to perform a balance inquiry (Create aForageBaanceElement
).
const balanceElement = forage.create('collect_pin')
balanceElement.mount('balance-check-container')
- Call the
checkBalance()
submit method from your front-end to submit a customer’s EBT PIN to Forage (Submit aForageBalanceElement
).checkBalance()
returns thesnap
andnon_snap
(EBT Cash) balance available on the EBT Card, in addition to a timestamp for when the balance was lastupdated
.- 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 ?? {}
}
}
- 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
- Prompt the customer to set the SNAP and/or EBT Cash amount to apply to the purchase.
- You need to pass these values to the POST
/payments/
request to create the Payment object in the next step.
- You need to pass these values to the POST
<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>
- From your backend, call the Forage API to create a
Payment
object (Create a Payment).- Pass the amounts that the customer indicated in Step 1 in the body of the request as the
amount
andfunding_type
params. - The API returns a Forage
Payment
object that represents the EBT transaction and returns aref
for thePayment
. You need to pass theref
as one of the parameters to thecapturePayment()
method in Step 4.
- Pass the amounts that the customer indicated in Step 1 in the body of the request as the
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': '<merchant-id>', // replace with your Forage merchant ID, as in 123ab45c67.
'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
- Add the
ForagePinElement
to your front-end checkout flow (Create aForagePinElement
).- This element collects a customer’s EBT Card PIN. You need to pass the
ForagePinElement
as one of the parameters to thecapturePayment()
method in the next step.
- This element collects a customer’s EBT Card PIN. You need to pass the
const snapPaymentCaptureElement = forage.create('collect_pin')
snapPaymentCaptureElement.mount('payment-capture-container')
- Pass the
ForageEbtPaymentElement
and aref
to thePayment
object returned in Step 2 to thecapturePayment()
method (Submit aForageEbtPaymentElement
).- 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
- Add transaction confirmation and receipts to your integration flow.
- Handle EBT refunds (Create a refund for a payment, partial refunds guide).
Phase 3: Catalog EBT eligible items
- Tag EBT eligible products in your database and reflect eligibility in the front-end UI.
- 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
Updated 3 months ago