# 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](https://dashboard.increase.com/) by toggling on sandbox mode:

![Sandbox toggle](/images/sandbox-toggle.png)

## Set-up

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

```curl
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
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
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](/documentation/api/inbound-ach-transfers#sandbox-create-an-inbound-ach-transfer). 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.

```curl
# 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](https://increase.com/documentation/api/card-payments#sandbox-create-a-card-authorization):

```curl
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](https://dashboard.increase.com/cards) in the Dashboard and click on our card we should now see the following:

![Card transactions](/images/card-transactions.png)

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](/images/available-balance-pending.png)

## 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](https://increase.com/documentation/api/card-payments#sandbox-settle-a-card-authorization):

```curl
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](/images/card-transactions-settled.png)

And your balance:

![Available balance](/images/available-balance-settled.png)

## 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](https://increase.com/documentation/api/card-payments) is a useful way to keep track of the state of an authorization:

```curl
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](/images/card-payment.png)

## 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](https://increase.com/documentation/api/real-time-decisions) API together with a webhook [event subscription](https://increase.com/documentation/api/event-subscriptions#create-an-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](https://github.com/Increase/increase-kotlin).

**Set-up event subscription**
To begin, we’ll create an event subscription for `real_time_decision.card_authorization_requested`. We’ll use [localtunnel](https://localtunnel.me/) to expose our local server to the internet which will give us a URL we define:

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

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

```curl
curl -X "POST" \
  --url "${INCREASE_URL}/event_subscriptions" \
  -H "Authorization: Bearer ${INCREASE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d $'{
    "url": "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

2\. Retrieve the real-time decision referenced in the event

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

3\. Action the real-time decision with either an approval or decline

```kotlin
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
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
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](/images/real-time-decisions.png)
