November 21, 2024
cancel
Showing results for 
Search instead for 
Did you mean: 
Help
ColeCallahan
Community Manager
Community Manager

For companies seeking to build their own custom payment card programs, one of the most attractive features of the Marqeta platform is their Just-in-Time (JIT) funding model. During the transaction process, JIT funding provides automatic decision-making on whether to approve or decline a transaction request in real time.

One form of the model is Managed JIT Funding, in which Marqeta handles spend authorization decisions based on spend control rules that you establish ahead of time. For example, you can limit when, where, and how much a cardholder can spend with their card. With Managed JIT Funding, Marqeta handles the ledgering of accounts.

The other form of the model is Gateway JIT Funding. In this form, transaction decision requests are sent to your system as part of the approval process. You can apply your own business rules on a per-transaction basis, considering elements of the transaction in question along with any factors on your business end at the time of the request. This allows for a high level of fine-tuned spend control.

Your system’s JIT gateway is fundamental to making Gateway JIT Funding work. Your gateway not only provides the logic for deciding how to handle a transaction, but it also manages the ledgers for your users. In this article, we will walk through how to build a simple gateway for receiving and responding to JIT funding requests from Marqeta. When we’re finished here, you’ll have all of the fundamentals you need to implement a JIT gateway for your custom business needs.


Setup

Before we can get started, we need to have a few things in place. First, we need a Marqeta developer account, which allows us to work in a developer sandbox, along with access to the Marqeta Core API Explorer.

After logging in, we’ll have access to our Application token, Admin access token, and Base URL. We’ll use these credentials to authenticate our requests to the Core API.

ColeCallahan_0-1667927467408.png


Initial data

We’ll begin by setting up some initial data in Marqeta. This will simplify our task later when it comes time to test our JIT gateway. Here is what we’ll create:

  • A program gateway funding source
  • A card product linked to that funding source. Make sure to link to that funding source by using the config.jit_funding.program_funding_source field.
  • A user named John, along with a card for John, associated with our card product
  • A user named Jane, along with a card for Jane, associated with our card product

This file at GitHub is a bash script containing all of the curl commands for creating this initial data. You will need to insert your own Marqeta credentials and a Mockbin URL before you can run the script. Below, we display the beginning of the file as a helpful reference:

# From your Marqeta Developer dashboard page
MQ_APPLICATION_TOKEN="INSERT YOUR APPLICATION TOKEN HERE"
MQ_ADMIN_ACCESS_TOKEN="INSERT YOUR ADMIN ACCESS TOKEN HERE"

# Create a mockbin at https://mockbin.org, then insert mockbin URL here
MOCKBIN_URL="INSERT YOUR MOCKBIN URL HERE"

# Make sure to remove trailing slash from the URL
MQ_BASE_URL="https://sandbox-api.marqeta.com/v3"

GATEWAY_TOKEN="jit_gateway_01"
CARD_PRODUCT_TOKEN="jit_card_product_01"
USER_1_TOKEN="jit_john"
CARD_1_TOKEN="jit_john_card_01"
USER_2_TOKEN="jit_jane"
CARD_2_TOKEN="jit_jane_card_01"


...

When you create a program gateway funding source in Marqeta, you need to provide a URL. This is the endpoint that will receive JIT funding requests from Marqeta. 

As we set up our initial data, we’ll use a temporary endpoint as our URL. This way, we can examine the request that Marqeta sends so that we can better understand how to build our JIT gateway. There are several services that can help us quickly create throwaway HTTP endpoints. One example is Mockbin, which we’ll use to create an endpoint as our URL. . After we deploy our JIT gateway server, we’ll update our gateway funding source to use our deployment URL instead.

Note: The JIT gateway endpoint URL is a different endpoint than what you would use to handle Marqeta Webhooks. Webhook URLs are endpoints that Marqeta will send requests to when certain API events occur. You can read up on how to use webhooks here.

After you run the script with curl requests, your developer sandbox will have a funding source, a card product, and two users (John and Jane)—each with one card.


The JIT funding request and response

Now that we have initial data, we want to simulate a transaction on a card, and then examine the JIT funding request that’s sent to our Mockbin URL.

Send payment authorization request

In the Core API Explorer, we find the operation to simulate a payment authorization request.

 

ColeCallahan_1-1667927467425.png

We will simulate an authorization for $14.99 on the card with token jit_john_card_01. Click on Send request. We see the response showing that the authorization request was declined.

 

ColeCallahan_2-1667927467413.png

This makes sense, of course, since our JIT gateway URL is currently set to use Mockbin, which will not respond to the funding request according to requirements set out by Marqeta.

Check Mockbin request log

We can examine our Mockbin log to see what the JIT funding request looked like. The pertinent parts of the JIT funding request body are as follows:

{
  "type" : "authorization",
  "state" : "PENDING",

  …
  "card_token" : "jit_john_card_01",

  …
  "gpa_order" : {

    …
    "jit_funding" : {
      "token" : "67151cf3-0f94-45ad-a67b-422a42a89317",
      "method" : "pgfs.authorization",
      "user_token" : "jit_john",
      "acting_user_token" : "jit_john",
      "amount" : 14.99
    },

    …
  },

  …
  "card_acceptor" : {
    "mid" : "merchant-01",
    "mcc" : "6411",
    "name" : "Marqeta Storefront",
    "street_address" : "330 Central Ave. St.",
    "city" : "St. Petersburg",
    "state" : "CA",
    "postal_code" : "33705",
    "country_code" : "USA"
  },

  …
}

Our JIT gateway will receive request payloads that look similar to this. Ultimately, a JIT gateway takes these inputs and responds with the decision of whether to approve or deny a JIT funding request.


Expectations of the JIT Gateway

The documentation on JIT funding responses provides detailed information about what is required of your JIT gateway response. However, let’s highlight some important points here:

  • The JIT gateway must respond to the funding request from Marqeta within three seconds. Otherwise, the request will be denied automatically.
  • The JIT gateway should respond with 200 OK for approved funding and 402 Request Failed for denied funding.
  • The response body must be JSON format, with a jit_funding object that matches the request payload’s gpa_order.jit_funding object.

Managing ledgering

When you use a JIT gateway, you are responsible for handling your own ledgering. For proper accounting and spend controls, your JIT gateway needs to include the logic for maintaining ledger balances for your users. It’s important to be familiar with the jit funding object as you consider how transactions will impact your ledger.

Now that we know what to expect from the JIT funding request from Marqeta and we know what is expected of our JIT gateway in returning a JIT funding response, let’s build our server.


Build JIT Gateway as a Node.js Express Server

We’ll build our JIT gateway server with Node.js. Although there are several Node.js frameworks one could use to build an API that listens for requests on a single endpoint, we’ll use Express for our example. All of the code for this demonstration is available at GitHub, but we’ll walk through the steps one at a time.

Initialize project

Begin by creating a project folder and then initializing a new Node.js project. We’ll use yarn as our package manager.

 

~$ mkdir project && cd project
~/project$ yarn init

Next, we’ll add express to our dependencies.

 

~/project$ yarn add express

We’re just about ready to write some code.

Spend control rules for our demo

To keep our JIT gateway logic simple, we’ll build according to the following specifications:

John and Jane will each start with a ledger balance of $100. As funding requests are approved, the gateway will adjust the balance accordingly. If a user does not have sufficient funds, then the funding request will be denied.

In addition, we’ll implement two spend control rules.

For John, only transactions with merchant merchant-01 are allowed. All other merchants are denied. Jane has no merchant restrictions.

For Jane, only transactions for amounts less than $75 are allowed. John has no spend limit restrictions, so long as his ledger balance shows sufficient funds.

Now that we’re clear on what we need to build, let’s build it!

Implement server

In our project folder, we create a new file called index.js, which contains the entire implementation of our server:

 

const fs = require('fs');
const express = require('express');

const LEDGER_FILE = './ledger.json';
const ledger = require(LEDGER_FILE);

const app = new express();
app.use(express.json());

app.post('/', (req, res) => {
  try {
    const jit_funding = req.body.gpa_order?.jit_funding;
    const merchant_id = req.body.card_acceptor?.mid;
    const user = jit_funding.user_token;

    if (user === 'jit_john' && merchant_id !== 'merchant-01') {
      jit_funding.decline_reason = 'INVALID_MERCHANT'
    } else if (user === 'jit_jane' && jit_funding.amount >= 75) {
      jit_funding.decline_reason = 'AMOUNT_LIMIT_EXCEEDED'
    }

    if (!jit_funding.decline_reason) {
      if (fundsAvailable(user, jit_funding.amount)) {
        updateLedgerBalance(user, jit_funding.amount);
        res.status(200).send({ jit_funding });
        return
      }
      jit_funding.decline_reason = 'INSUFFICIENT_FUNDS';
    }
    res.status(402).send({ jit_funding });
  } catch {
    res.status(500).send({ message: 'Could not process request.' });
    return
  }
})

app.listen(8080, () => console.log('listening on 8080...'));

const fundsAvailable = (user, amount) => {
  try {
    return ledger[user] >= amount;
  } catch {
    return false
  }
}
     
const updateLedgerBalance = (user, amount) => {
  ledger[user] = parseFloat((ledger[user] - amount).toFixed(2));
  fs.writeFileSync(LEDGER_FILE, JSON.stringify(ledger));
}

Let’s walk through our code one piece at a time:

  1. Our demo “ledger” of account balances is a simple JSON file called ledger.json, and it can be placed in the same directory as index.js. In a fully-fledged implementation, you would use a database and implement tighter security controls and read/write mechanisms. Here, we simply read our ledger file for the latest balances, and we write updated balances back to our file. Initially, ledger.json looks like this:

{"jit_john":100,"jit_jane":100}

 

  1. Initialize our Express app.
  2. Listen for POST requests on the / endpoint.
  3. Obtain the jit_funding object and the merchant_id from the request body.
  4. If the user is jit_john, ensure that the merchant for this transaction is merchant-01. Otherwise, decline this request with the reason INVALID_MERCHANT.
  5. If the user is jit_jane, ensure that her transaction amount is less than $75. Otherwise, decline this request with the reason AMOUNT_LIMIT_EXCEEDED.
  6. Assuming all of our initial checks have passed, check to see if the user for this transaction has fundsAvailable for the requested amount.
  7. If the user has fundsAvailable, then updateLedgerBalance and return a status code 200, along with the original jit_funding object as the top-level object in the payload.
  8. If the user does not have fundsAvailable, then add the INSUFFICIENT_FUNDS decline reason to our jit_funding object. Deny the request with a status code 402, sending along the jit_funding object. Marqeta provides the list of allowable decline_reason values.
  9. Start the server, listening on port 8080.
  10. The fundsAvailable function simply checks the requested amount against the user’s remaining balance in our ledger object.
  11. The updateLedgerBalance function subtracts the requested amount from the user’s current balance, and then uses fs.writeFileSync to overwrite the ledger.json file with the latest ledger object.


Test our JIT gateway

We have a few final steps to take before we can test our gateway.

Use ngrok to tunnel to localhost:8080

Because we will be running our JIT gateway server on localhost, we will need to tunnel into localhost with a tool such as ngrok. Make sure you have installed ngrok on your machine. Then, in a terminal, run the following command:

 

~$ ngrok http 8080

 

ngrok will start, and you’ll see your ngrok session information:

 

ngrok by @inconshreveable                                                                                                                                                                                                                                            (Ctrl+C to quit)
                                                                                                                                                                                                                                                                                   
Status        online                                                                                                                                                                                                                                                
Account       Marqeta Demo User (Plan: Free)                                                                                                                                                                                                                                
Version       2.3.40                                                                                                                                                                                                                                                
Region        United States (us)                                                                                                                                                                                                                                    
Web Interface http://127.0.0.1:4040                                                                                                                                                                                                                                 
Forwarding    http://7180-98-161-186-106.ngrok.io -> http://localhost:8080                                                                                                                                                                                          
Forwarding    https://7180-98-161-186-106.ngrok.io -> http://localhost:8080 

Copy the HTTPS forwarding address that ends with ngrok.io. We will use this address as the URL for our JIT gateway funding source, replacing the Mockbin URL that we started with. If you’ve built your gateway to use a path other than the root path, then you’ll need to add that path to the end of the ngrok address (for example, https://7180-98-161-186-106.ngrok.io/jit_gateway).


Update JIT gateway funding source URL

In the Core API Explorer, send a PUT request to update your gateway program funding source. The request body is a JSON with URL set to the ngrok URL from above. The path parameter token to use is the token for our gateway funding source, jit_gateway_01.

ColeCallahan_3-1667927467427.png


Start JIT gateway server

With our ngrok tunnel up and running, and Marqeta pointing to our tunnel, all that’s left is for us to start up our server.

 

~/project$ node index.js

listening on 8080…


Send simulated payment authorization requests

Now that we have all of our pieces in place, we can use the Core API Explorer to send simulated payment authorization requests to test out our JIT gateway server implementation. This is similar to what we did when we first started, so that we could see the JIT funding request that was sent to Mockbin.

Request a payment for $60 by John to merchant-02

The request body for this first test looks like this:

{
  "card_token":"jit_john_card_01",
  "amount": 60,
  "mid":"merchant-02"
}

 

We send the request and receive the following response (abridged for brevity):

{
  "transaction": {
    "type": "authorization",
    "state": "DECLINED",
    "identifier": "93",
    "token": "1f7e5e8f-149c-453a-9235-86290b199418",
    "user_token": "jit_john",
    "gpa_order": {
      "state": "DECLINED",
      "response": {
        "code": "1902",
        "memo": "JIT Response Invalid Merchant"
      },
      "funding": {
        "amount": 60,
        "gateway_log": {
          "message": "Declined by gateway.",
          "response": {
            "code": "402",
            "data": {
              "jit_funding": {
                "decline_reason": "INVALID_MERCHANT",

As expected, this request was denied because our spend control rules only allow John to make transactions with merchant-01. This request, for merchant-02, is declined with the reason INVALID_MERCHANT.

Request a payment for $60 by John to merchant-01

The request body for this test looks like this:

{
  "card_token":"jit_john_card_01",
  "amount": 60,
  "mid":"merchant-01"
}

 

We expect that this request will be approved. We see from the response that the transaction state is PENDING, which means it has been approved by the JIT gateway.

{
  "transaction": {
    "type": "authorization",
    "state": "PENDING",
    "token": "5fc6fb35-7092-4cb0-8bb4-621a9df9ced3",
    "user_token": "jit_john",
    "gpa_order": {
      "state": "PENDING",
      "response": {
        "code": "0000",
        "memo": "Approved or completed successfully"
      },

Request another payment for $60 by John to merchant-01

Now, we’ll send another identical request. You’ll recall that our ledger balance for John started with $100. The previous transaction impacted his balance, and our ledger should carry an available balance of $40 remaining for John.

After sending the request again, we received the following response:

{
  "transaction": {
    "type": "authorization",
    "state": "DECLINED",
    "user_token": "jit_john",
    "gpa_order": {
      "state": "DECLINED",
      "response": {
        "code": "1884",
        "memo": "JIT Response Not Sufficient Funds"
      },
      "funding": {
        "gateway_log": {
          "response": {
            "code": "402",
            "data": {
              "jit_funding": {
                "decline_reason": "INSUFFICIENT_FUNDS"

As expected, this second request for John was declined because of INSUFFICENT_FUNDS.

Request a payment for $75 by Jane to merchant-02

We also have a spend control rule for Jane. While she can transact with any merchant, her transaction amounts must be under $75. Our first request for $75 is above her spending limit, and we expect it to fail. The request body looks like this:

{
  "card_token":"jit_jane_card_01",
  "amount": 75,
  "mid":"merchant-02"
}

 

The response shows a denied request (AMOUNT_LIMIT_EXCEEDED), as expected:

{
  "transaction": {
    "type": "authorization",
    "state": "DECLINED",
    "user_token": "jit_jane",
    "gpa_order": {
      "state": "DECLINED",
      "response": {
        "code": "1887",
        "memo": "JIT Response Amount Limit Reached"
      },
      "funding": {
        "gateway_log": {
          "message": "Declined by gateway.",
          "response": {
            "code": "402",
            "data": {
              "jit_funding": {
                "decline_reason": "AMOUNT_LIMIT_EXCEEDED"

Request a payment for $74.99 by Jane to merchant-02

We’ll send a similar request for Jane, but this time for an amount just under her limit, at $74.99.

{
  "card_token":"jit_jane_card_01",
  "amount": 75,
  "mid":"merchant-02"
}

 

This request is approved, as expected:

{
  "transaction": {
    "type": "authorization",
    "state": "PENDING",


Check ledger amounts

Based on our above transactions, we should have a final ledger balance of $40 remaining for John and $25.01 remaining for Jane. When we check ledger.json on our local machine, this is what we see:

 

{"jit_john":40,"jit_jane":25.01}

 

Everything checks out and is working as it should be!


Conclusion

A JIT gateway is a powerful way to customize your payment card program by putting you in full control of spending authorization. You can write custom business logic to determine whether to approve or deny funding requests in real time.

The code for our demo can be found at this GitHub repository. Our mini-project is, of course, a simplified example of a JIT gateway. To support your custom payment program with a JIT gateway, your spend control and ledger maintenance logic will be much more complex. However, you now have the building blocks of a working JIT gateway and the fundamental knowledge to begin implementing your own solution.

For more detailed information and specifications for a JIT gateway, you can reference the Marqeta documentation. When you’re ready to start building with Marqeta, you can create a developer account and join us here in the Marqeta developer community!

Developer Newsletter