Programmatic card processing

Increase directly connects to the Visa Direct Exchange network, ensuring highly available processing of card transactions with no layers in-between Increase and Visa. You can start creating virtual and physical Increase cards as soon as you sign up. Each card is backed by the balance of its underlying account. By default, all valid card authorizations are approved as long as the underlying balance of the card’s account is sufficient. For more complicated use cases, where you would like granular control over which authorizations are approved and which are declined, we’ll send you a webhook and let you decide.

Throughout this guide we’ll walk through programmatically creating a card and testing it out, first without and then with real-time webhooks. As you go through the commands, you can follow the activity visually in your Increase dashboard by toggling on sandbox mode:

Sandbox toggle

Set-up

To follow the curl commands, make sure to set the following environment variables:

export INCREASE_URL="https://sandbox.increase.com" export INCREASE_API_KEY="<sandbox key from https://dashboard.increase.com/developers/api_keys>"

All cards belong to an Account so we’ll also need to create one of those:

curl -X "POST" \ --url "${INCREASE_URL}/accounts" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H "Content-Type: application/json" \ -d $'{ "name": "Cards" }' => {"id": "sandbox_account_...", ...} export INCREASE_ACCOUNT = "sandbox_account_..."

You can also do this in the dashboard at https://dashboard.increase.com/accounts.

Create a card

To create a card, we’ll provide the account the card belongs to, a description of the card, and a billing address.

curl -X "POST" \ --url "${INCREASE_URL}/cards" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H 'Content-Type: application/json' \ -d $'{ "account_id": "account_...", "description": "Card for Ian Crease", "billing_address": { "line1": "33 Liberty Street", "city": "San Francisco", "state": "CA", "postal_code": "94132" } }' => { "id": "sandbox_card_oubs0hwk5rn6knuecxg2", ... }

This gets us the Increase ID of the card, card_oubs0hwk5rn6knuecxg2. When we’re ready to show the card number to a customer, you’ll retrieve it with the /cards/card_.../details endpoint. For now, we’ll move on to simulating an authorization on the card.

Simulating an authorization

To simulate an authorization we’ll first need to top up our account with funds. We’ll do that with the ACH simulation endpoint. To simulate moving funds into an account we’ll first need an account number where the funds can land. Increase lets you create as many account numbers as you want for each of your accounts—a common strategy is for example to create one account number for each of your vendors.

# First create an account number where the ACH funds will land: curl -X "POST" \ --url "${INCREASE_URL}/account_numbers" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H "Content-Type: application/json" \ -d $'{ "account_id": "ACCOUNT_ID_GOES", "name": "Card payments" }' => {"id": "sandbox_account_number_...", ...} # Then move some funds into the account: curl -X "POST" \ --url "${INCREASE_URL}/simulations/inbound_ach_transfers" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H "Content-Type: application/json" \ -d $'{ "account_number_id": "account_number_v18nkfqm6afpsrvy82b2", "amount": 100000 }'

At this point we have funded our sandbox account and can simulate usage of the card at a store with the card authorization simulation endpoint:

curl -X "POST" \ --url "${INCREASE_URL}/simulations/card_authorizations" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H "Content-Type: application/json" \ -d $'{ "amount": 1000, "card_id": "card_oubs0hwk5rn6knuecxg2" }' => { "pending_transaction": { // ... "id": "sandbox_pending_transaction_bxenyrfmlvpfcdhne1go" } }

If we navigate to the cards page in the dashboard and click on our card we should now see the following:

Card transactions

This is what you’ll see immediately after someone uses one of your cards. At this point the authorization is pending and should only be reflected in your available balance:

Available balance

Simulating a card settlement

Once the transaction clears, usually later that day, your current balance will go down as well. Since we’re in our sandbox environment we can speed up the process by simulating a card settlement:

curl -X "POST" \ --url "${INCREASE_URL}/simulations/card_settlements" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H "Content-Type: application/json" \ -d $'{ "card_id": "sandbox_card_imnigzlhgsgykpzzkcwc", "pending_transaction_id": "sandbox_pending_transaction_bxenyrfmlvpfcdhne1go" }'

Navigating back to your card detail page, the transaction should now show as a Card Settlement instead:

Card transactions settled

And your balance:

Available balance

Card payment flows

The majority of your card payments will likely follow these two steps: an authorization followed by a settlement. You will also sometimes see authorizations get reversed, e.g., in the event of a refund before a settlement, and even incremented when the original authorization amount needs to be increased. The [/card_payments](https://increase.com/documentation/api#card-payments) API is a useful way to keep track of the state of an authorization:

curl --url "${INCREASE_URL}/card_payments?account_id=sandbox_account_uhatomeo6ndzqhljge3z" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" { "data": [ { "id": "sandbox_card_payment_qctt5lw3zese8eujb4kr", "created_at": "2024-01-11T21:28:35Z", "account_id": "sandbox_account_uhatomeo6ndzqhljge3z", "card_id": "sandbox_card_imnigzlhgsgykpzzkcwc", "elements": [ { "category": "card_authorization", "card_authorization": { "id": "sandbox_card_authorization_imbwqnss4l1jpefp01uj", "card_payment_id": "sandbox_card_payment_qctt5lw3zese8eujb4kr", "merchant_acceptor_id": "5200291094", "merchant_descriptor": "MBLREHXEWT", "merchant_category_code": "5734", "merchant_city": "SANFRANCISCO", "merchant_country": "US", "digital_wallet_token_id": null, "physical_card_id": null, "verification": { "cardholder_address": { "provided_postal_code": null, "provided_line1": null, "actual_postal_code": null, "actual_line1": null, "result": "not_checked" }, "card_verification_code": { "result": "not_checked" } }, "network_identifiers": { "transaction_id": "333325222528937", "trace_number": "454885", "retrieval_reference_number": "320337291607" }, "amount": 1000, "class_name": "card_authorization", "currency": "USD", "direction": "settlement", "processing_category": "purchase", "expires_at": "2024-01-19T12:00:00Z", "real_time_decision_id": null, "pending_transaction_id": "sandbox_pending_transaction_bxenyrfmlvpfcdhne1go", "type": "card_authorization" }, "created_at": "2024-01-11T21:28:35Z" }, { "category": "card_settlement", "card_settlement": { "id": "sandbox_card_settlement_vqmulmnzjpapzufybqqa", "card_payment_id": "sandbox_card_payment_qctt5lw3zese8eujb4kr", "card_authorization": "sandbox_card_authorization_imbwqnss4l1jpefp01uj", "amount": 1000, "currency": "USD", "presentment_amount": 1000, "presentment_currency": "USD", "class_name": "card_settlement", "merchant_acceptor_id": "NBURKHVRKWCMLMQ", "merchant_city": "SANFRANCISCO", "merchant_state": null, "merchant_country": "US", "merchant_name": "BASKZBJO", "merchant_category_code": "5734", "transaction_id": "sandbox_transaction_sgi3pokksyuxjsrg51ug", "pending_transaction_id": "sandbox_pending_transaction_bxenyrfmlvpfcdhne1go", "interchange": null, "purchase_details": null, "network_identifiers": { "transaction_id": "353029890989561", "acquirer_reference_number": "08030532720715529117083", "acquirer_business_id": "93651339" }, "type": "card_settlement" }, "created_at": "2024-01-11T21:33:39Z" } ], "state": { "authorized_amount": 1000, "incremented_amount": 0, "reversed_amount": 0, "fuel_confirmed_amount": 0, "settled_amount": 1000 }, "type": "card_payment" } ] }

Or in the dashboard:

Card payment

Real-time decisions

If you’re managing cards programmatically, you might also want to control the outcome of each authorization—e.g., to only allow payments at specific stores, or below certain limits. To do so you’ll use Increase’s Real-Time Decisions API together with a webhook event subscription for real_time_decision.card_authorization_requested.

Let’s look at an example. We’ll build a quick application where we’ll only approve authorizations with a merchant category code of 5812 (restaurants) with a merchant city of San Francisco. We’ll use our Kotlin SDK.

Set-up event subscription To begin, we’ll create an event subscription for real_time_decision.card_authorization_requested. We’ll use localtunnel to expose our local server to the internet which will give us a URL we define:

lt --subdomain your-increase-webhook-testing-url --port 4567

Then we can create the event subscription pointing to this URL:

curl -X "POST" \ --url "${INCREASE_URL}/event_subscriptions" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H "Content-Type: application/json" \ -d $'{ "url": "https://https://your-increase-webhook-testing-url.loca.lt" "secret": "your webhook secret", "category": "real_time_decision.card_authorization_requested" }'

This will make sure we only get events of the type real_time_decision.card_authorization_requested. For a fully fledged application there are other webhooks that can be useful as well, such as card_payment.updated, but we’ll start here.

Webhook implementation We’ll aim to do the following:

  1. Verify the incoming webhook signature with our webhook secret

https://increase.com/documentation/webhooks#securing-your-webhook-endpoint-recommended

  1. Retrieve the real-time decision referenced in the event

https://increase.com/documentation/api#retrieve-a-real-time-decision

  1. Action the real-time decision with either an approval or decline
import com.google.common.collect.ImmutableListMultimap import io.javalin.Javalin import com.increase.api.client.okhttp.IncreaseOkHttpClient import com.increase.api.models.Event import com.increase.api.models.RealTimeDecisionActionParams import com.increase.api.models.RealTimeDecisionRetrieveParams import io.javalin.http.Context const val MERCHANT_CATEGORY_CODE = "5812" const val MERCHANT_CITY = "San Francisco" fun main() { val client = IncreaseOkHttpClient.builder() .apiKey(API_KEY) .webhookSecret(WEBHOOK_SECRET) .build() Javalin.create() .post("/webhook", fun(ctx: Context) { val headers = ImmutableListMultimap.copyOf(ctx.headerMap().entries) val payload = client.webhooks().unwrap(ctx.body(), headers, null) val event = payload.convert<Event>()!! if (event.category() != Event.Category.REAL_TIME_DECISION_CARD_AUTHORIZATION_REQUESTED) { println("Ignoring other event types ${event.category()}") ctx.status(200) return } val realTimeDecision = client.realTimeDecisions().retrieve( RealTimeDecisionRetrieveParams.builder().realTimeDecisionId(event.associatedObjectId()).build() ) val cardAuthorization = realTimeDecision.cardAuthorization()!! val isApproved = cardAuthorization.merchantCategoryCode() == MERCHANT_CATEGORY_CODE && cardAuthorization.merchantCity() == MERCHANT_CITY val decision = if (isApproved) { RealTimeDecisionActionParams.CardAuthorization.Decision.APPROVE } else { RealTimeDecisionActionParams.CardAuthorization.Decision.DECLINE } client.realTimeDecisions().action( RealTimeDecisionActionParams.builder().realTimeDecisionId(realTimeDecision.id()).cardAuthorization( RealTimeDecisionActionParams.CardAuthorization.builder().decision( decision ).build() ).build() ) ctx.status(200) }) .start(4567) }

Testing our webhook To test our webhooks, we’ll want to simulate more authorizations—we’ll start with one we’ll be declining:

curl -X "POST" \ --url "${INCREASE_URL}/simulations/card_authorizations" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H "Content-Type: application/json" \ -d $'{ "amount": 1250, "card_id": "sandbox_card_imnigzlhgsgykpzzkcwc", "merchant_category_code": "5942", "merchant_descriptor": "Books 📚", "merchant_city": "Seattle" }' => { "declined_transaction": { "account_id": "sandbox_account_uhatomeo6ndzqhljge3z", "amount": -1250, "currency": "USD", "created_at": "2024-01-12T04:23:48Z", "date": "2024-01-12", "description": "Books 📚", "id": "sandbox_declined_transaction_fimya9x1xdintmgyz5tu", "path": "/declined_transactions/sandbox_declined_transaction_fimya9x1xdintmgyz5tu", "route_id": "sandbox_card_imnigzlhgsgykpzzkcwc", "route_type": "card", "source": { // ... "id": "sandbox_card_decline_pmtriejsd1cn9vv853ls", "amount": 1250, "class_name": "card_decline", "reason": "webhook_declined", "merchant_acceptor_id": "0315399943", "merchant_descriptor": "Books 📚", "merchant_category_code": "5942", "merchant_city": "Seattle", "merchant_country": "US", "real_time_decision_id": "sandbox_real_time_decision_syxbufwmreypjo78ekag" }, "type": "declined_transaction" }, "type": "inbound_card_authorization_simulation_result" }

And then one we’ll want our application to approve:

curl -X "POST" \ --url "${INCREASE_URL}/simulations/card_authorizations" \ -H "Authorization: Bearer ${INCREASE_API_KEY}" \ -H "Content-Type: application/json" \ -d $'{ "amount": 1250, "card_id": "sandbox_card_imnigzlhgsgykpzzkcwc", "merchant_category_code": "5812", "merchant_descriptor": "Z&Y 🍜", "merchant_city": "San Francisco" }' => { "pending_transaction": { "account_id": "sandbox_account_uhatomeo6ndzqhljge3z", "amount": -1250, "currency": "USD", "status": "pending", "type": "pending_transaction" // .... }, "type": "inbound_card_authorization_simulation_result" }

Real time decisions