DEVELOPER

Back to Developer Blog

technicalseries

Building a Java Payment App with Custom Pay API

By Kumar Harsh and Laura Olson | November 19th, 2024

Building a customized payment solution that enables you to process transactions from either a backend server, Point of Sale (POS) system, or website can be complex, but North's Custom Pay API helps simplify the process. The Custom Pay API can be integrated into almost all server-side environments and offers an easy-to-use API for handling transactions and related tasks.

In this tutorial, you'll learn what the Custom Pay API is and how to use it to set up payment processing in a Java backend. You'll create a Java Spring Boot REST API to process sales and then use a web form to send payment requests to the Java API to process payments.

Custom Payment Form

Build this App

Clone this code repository to quickly create your own app today!

What is the Custom Pay API?

The Custom Pay API is a full-featured solution for accepting card-present and card-not-present payments from the same system. It also allows tokenizing transactions—that is, linking custom tokens to transactions—for reporting, chargebacks, recurring sales, and more to help mitigate the risks and costs involved in storing card data and ACH account numbers.

The Custom Pay API is a versatile payment processing solution that can be integrated into almost all environments, including websites, backend servers, and POS backends. You can implement all major payment processing functionalities with it, such as:

  • Sale: Authorize and capture payments from users
  • Adjust tips: Adjust an authorization to add a tip
  • Refund: Issue refunds against payments
  • Reverse: Reverse the authorization of funds on a credit card
  • Void: Stop a sale before it settles
  • Address verification: Verify the customer's address to prevent fraud
  • Batch transactions: Submit multiple transactions for settlement at once

To learn how to use all these features, check out the Custom Pay API documentation. This tutorial gives you an easy way to get started, as you'll only learn how to use the /sale endpoint to authorize and capture payments from your front-end clients. You can easily modify and reuse the steps to interact with other endpoints as well.

Creating the Java Payment App

Set Up a Java Project

To get started, create a new Java Maven project using the IDE of your choice. Once the project is ready, add the following dependencies to the pom.xml file:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.googlecode.json-simple</groupId>
        <artifactId>json-simple</artifactId>
        <version>1.1.1</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.2.2</version>
    </dependency>
</dependencies>

This adds the following dependencies to your app: Spring Boot Starter Web to create a REST API using Spring Boot, json-simple to handle JSON objects in Java, and OkHttp to send HTTP requests.

You could replace json-simple and OkHttp with any other equivalent libraries. This tutorial uses these two for simplicity in demonstrating the integration of the Custom Pay API.

To complete the initial setup of the Java app, add the following plugin to your pom.xml file right below the dependencies array:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Registering on North Developer and Getting Credentials

Before setting up the integration, you also need to register on North Developer and request test credentials to integrate and test the Custom Pay API in your project during its development.

After registering for free on North Developer, use the contact form to request test credentials.

The test credentials consist of two keys:

  • Epi_id
  • Epi_key

You'll learn how to use these in the next sections.

Get in Touch

Talk to us about adding payments to your Java application.

Using the Custom Pay API to Enable Payment Processing

Next, you'll create a handler method that exposes a /pay endpoint from your Java app that accepts the user's card details and transaction amount to process the transaction.

Start by creating a file named CustomPayController.java in the same directory as your Application class and saving it with the following code:

package dev.draft.payments_hub_custom_pay_tutorial;

import okhttp3.*;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.json.simple.JSONObject;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

@RestController
public class CustomPayController {

    private static final OkHttpClient httpClient = new OkHttpClient();

    @CrossOrigin
    @PostMapping(path = "/pay")
    public static String pay(@org.springframework.web.bind.annotation.RequestBody JSONObject inputBody) throws NoSuchAlgorithmException, InvalidKeyException {

        // 1. Extract user input
        
        // 2. Define other payment-related parameters

        // 3. Define request headers and body content

        // 4. Create signature
        
        // 5. Prepare body to send with request

        // 6. Build the request with correct headers and body content

        // 7. Send the request and handle the response
        
        return "Hello world!";
    }
}

This code creates a base REST controller class that allows you to define endpoints. It then defines a /pay endpoint that handles POST requests with a body. There are also six comments in the body of the pay() handler function that explain what you need to do to send requests to the Custom Pay API.

First, extract the payment-related data sent by the user by adding the following code under comment 1:

// 1. Extract user input
String cardNumber = inputBody.get("cardNumber").toString();
String CVV = inputBody.get("CVV").toString();
String cardExpiryDate = inputBody.get("cardExpiryDate").toString();
String amount = inputBody.get("amount").toString();

Next, define other payment-related values by pasting the following code below comment 2:

// 2. Define other payment-related parameters
String cardEntryMethod = "X";                               // To denote that the card was keyed in manually
String industryType = "E";                                  // To denote an e-commerce transaction
boolean capture = true;                                     // To authorize and capture payment in the same go

String epiId = "XXXX-XXXXXX-X-X";                           // From your North account
String epiKey = "8XXXXXXXXXXXXXXXX05XXXXXXXXXXX31";         // From your North account
String baseUrl = "https://epi.epxuap.com";                  // From North docs
String endpoint = "/sale";                                  // From North docs

Make sure to replace the values of epiId and epiKey here with the test credentials you received from the North Integration team.

Next, define the headers and body content of the Custom Pay request by pasting the following code under comment 3:

// 3. Define request headers and body content
String contentType = "application/json";
Double transactionId = Math.random();
String orderNumber = String.valueOf(Math.random());
Double batchId = Math.random();

JSONObject bodyJSON = new JSONObject();

bodyJSON.put("account", cardNumber);
bodyJSON.put("cvv2", CVV);
bodyJSON.put("expirationDate", cardExpiryDate);
bodyJSON.put("amount", Float.valueOf(amount));
bodyJSON.put("transaction", transactionId);
bodyJSON.put("orderNumber", orderNumber);
bodyJSON.put("capture", capture);
bodyJSON.put("industryType", industryType);
bodyJSON.put("cardEntryMethod", cardEntryMethod);
bodyJSON.put("batchID", batchId);

Next, create a signature for the request. The Custom Pay API documentation explains how to do so in detail and also provides a shell script for doing it.

Paste the following functions into your CustomPayController.java class:

public static String createSignature(String endpoint, String payload, String epiKey) throws NoSuchAlgorithmException, InvalidKeyException {
    String algorithm = "HmacSHA256";
    SecretKeySpec secretKeySpec = new SecretKeySpec(epiKey.getBytes(), algorithm);
    Mac mac = Mac.getInstance(algorithm);
    mac.init(secretKeySpec);
    String data = endpoint + payload;
    return bytesToHex(mac.doFinal(data.getBytes()));
}

private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
    char[] hexChars = new char[bytes.length * 2];
    for (int j = 0; j < bytes.length; j++) {
        int v = bytes[j] & 0xFF;
        hexChars[j * 2] = HEX_ARRAY[v >>> 4];
        hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
    }
    return new String(hexChars);
}

Note: You're adding these functions to the controller class here for brevity. Normally, you would store such functions in a utility class for better separation of concerns.

You can now call the createSignature() function to create a signature for your request. To do that, paste the following code snippet under comment 4:

// 4. Create signature
String signature = createSignature(endpoint, bodyJSON.toJSONString(), epiKey);

Next, prepare and build the request by pasting the following code snippets below comments 5 and 6:

// 5. Prepare body to send with request
RequestBody body = RequestBody.create(
        bodyJSON.toJSONString(),
        MediaType.parse("application/json; charset=utf-8")
);

// 6. Build the request with correct headers and body content
Request request = new Request.Builder()
        .url(baseUrl + endpoint)
        .addHeader("Content-Type", contentType)
        .addHeader("epi-Id", epiId)
        .addHeader("epi-signature", signature)
        .post(body)
        .build();

Finally, send the request to the Custom Pay API. To do so, paste the following code snippet below comment 7:

// 7. Send the request and handle the response
try (Response response = httpClient.newCall(request).execute()) {

    // Handling failure
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    // Handling success
    JSONParser jsonParser = new JSONParser();
    JSONObject responseBodyJSON = (JSONObject) jsonParser.parse(response.body().string());

    // Sending the payment status back to the caller
    return ((JSONObject) responseBodyJSON.get("data")).get("text").toString();
} catch (Exception e) {

    // Handling errors
    e.printStackTrace();
    return e.getMessage();
}

Make sure to remove the return "Hello world!"; line from the end of the pay() function. This is how the file should look when done:

package dev.draft.payments_hub_custom_pay_tutorial;

import okhttp3.*;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

@RestController
public class CustomPayController {

    private static final OkHttpClient httpClient = new OkHttpClient();

    @CrossOrigin
    @PostMapping(path = "/pay")
    public static String pay(@org.springframework.web.bind.annotation.RequestBody JSONObject inputBody) throws NoSuchAlgorithmException, InvalidKeyException {

        // 1. Extract user input
        String cardNumber = inputBody.get("cardNumber").toString();
        String CVV = inputBody.get("CVV").toString();
        String cardExpiryDate = inputBody.get("cardExpiryDate").toString();
        String amount = inputBody.get("amount").toString();

        // 2. Define other payment-related parameters
        String cardEntryMethod = "X";                               // To denote that the card was keyed in manually
        String industryType = "E";                                  // To denote an e-commerce transaction
        boolean capture = true;                                     // To authorize and capture payment in the same go

        String epiId = "XXXX-XXXXXX-X-X";                           // From your North account
        String epiKey = "8XXXXXXXXXXXXXXXX05XXXXXXXXXXX31";         // From your North account
        String baseUrl = "https://epi.epxuap.com";                  // From North docs
        String endpoint = "/sale";                                  // From North docs

        // 3. Define request headers and body content
        String contentType = "application/json";
        Double transactionId = Math.random();
        String orderNumber = String.valueOf(Math.random());
        Double batchId = Math.random();

        JSONObject bodyJSON = new JSONObject();

        bodyJSON.put("account", cardNumber);
        bodyJSON.put("cvv2", CVV);
        bodyJSON.put("expirationDate", cardExpiryDate);
        bodyJSON.put("amount", Float.valueOf(amount));
        bodyJSON.put("transaction", transactionId);
        bodyJSON.put("orderNumber", orderNumber);
        bodyJSON.put("capture", capture);
        bodyJSON.put("industryType", industryType);
        bodyJSON.put("cardEntryMethod", cardEntryMethod);
        bodyJSON.put("batchID", batchId);


        // 4. Create signature
        String signature = createSignature(endpoint, bodyJSON.toJSONString(), epiKey);

        // 5. Prepare body to send with request
        RequestBody body = RequestBody.create(
                bodyJSON.toJSONString(),
                MediaType.parse("application/json; charset=utf-8")
        );

        // 6. Build the request with correct headers and body content
        Request request = new Request.Builder()
                .url(baseUrl + endpoint)
                .addHeader("Content-Type", contentType)
                .addHeader("epi-Id", epiId)
                .addHeader("epi-signature", signature)
                .post(body)
                .build();

        // 7. Send the request and handle the response
        try (Response response = httpClient.newCall(request).execute()) {

            // Handling failure
            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

            // Handling success
            JSONParser jsonParser = new JSONParser();
            JSONObject responseBodyJSON = (JSONObject) jsonParser.parse(response.body().string());

            // Sending the payment status back to the caller
            return ((JSONObject) responseBodyJSON.get("data")).get("text").toString();
        } catch (Exception e) {

            // Handling errors
            e.printStackTrace();
            return e.getMessage();
        }

    }

    public static String createSignature(String endpoint, String payload, String epiKey) throws NoSuchAlgorithmException, InvalidKeyException {
        String algorithm = "HmacSHA256";
        SecretKeySpec secretKeySpec = new SecretKeySpec(epiKey.getBytes(), algorithm);
        Mac mac = Mac.getInstance(algorithm);
        mac.init(secretKeySpec);
        String data = endpoint + payload;
        return bytesToHex(mac.doFinal(data.getBytes()));
    }

    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
    public static String bytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }
}

Congratulations! This completes the integration of the Custom Pay API into your Java REST API. You can find the source code for the completed Java app in this GitHub repo.

Testing the System

Finally, test this endpoint using a React front-end to initiate payments.

To set up a sample React application with a form that allows you to send requests to your Java REST API, clone the following GitHub repo:

git clone https://github.com/PaymentsHubDevelopers/PaymentsHub-React-Custom-Pay-API.git

Once you have cloned the repo, open a new terminal inside it and run the following commands:

npm install
npm start

This will open a new web page in your browser with a form that contains default values for testing. Click Pay to test the Java code.

Note: The React code assumes your server is running at http://localhost:8080. If your server's URL is different, make sure to update the code in the App.js file in the React code.

If everything runs successfully, you should see a success message below the button.

This completes the integration of the Custom Pay API into a Java backend.

Conclusion

In this article, you learned how to use North's Custom Pay API to build a payments backend. You learned how to sign and send a request to the /sale endpoint of the Custom Pay API and handle its response.

The Custom Pay API offers many more features for payment processing, including refunds, reversals, and more. Check out the North documentation and other services to explore more possibilities with payment processing.

How To Get Started

North’s Sales Engineering team provides support to developers and business decision-makers to help select the best possible payment system. Contact us to learn more about how to connect your system to the North ecosystem.


Start your free Developer account and try it now.


©2025 North is a registered DBA of NorthAB, LLC. All rights reserved. North is a registered ISO of BMO Harris Bank N.A., Chicago, IL, Citizens Bank N.A., Providence, RI, The Bancorp Bank, Philadelphia, PA, FFB Bank, Fresno, CA, Wells Fargo Bank, N.A., Concord, CA, and PNC Bank, N.A.