DEVELOPER

Back to Developer Blog

technicalseries

Subscription Payments in Java Using North's Recurring Billing API

By Vikram Aruchamy and Laura Olson | December 8th, 2024

The subscription model is a popular method for offering products or services on a recurring basis. Whether in streaming or SaaS services, businesses use this model to establish long-term relationships with customers by providing ongoing access to a product or service in exchange for regular payments.

Recurring billing is the backbone of the subscription model, enabling businesses to automatically charge customers at predefined intervals without manual intervention.

This tutorial shows you how to use North's Recurring Billing API to implement subscription billing in your Java application. You'll see how the API allows you to:

  • Set the recurring period that works best for your business, whether weekly, monthly, or something else;
  • Offer your users options for managing their subscriptions with features such as pausing, resuming, and canceling; and
  • Refund payments easily and securely because every transaction is tokenized.

Let's dive in!

Recurring payment form made with North's Recurring Billing API

Build this App

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

Prerequisites

Before you start the tutorial, you need to create an account on North Developer, sign up for the Recurring Billing API account, and generate the Custom Pay API credentials that include the EPI_id and the EPI_Key.

Since you'll use a Maven project to implement the Recurring Billing API in your application, you also need a Java IDE that supports Maven, such as Eclipse or IntelliJ . This tutorial will use Eclipse.

Create a New Maven Java Project

Maven provides a structured and standardized approach to managing Java projects by defining the project's structure, dependencies, and building processes in a project object model (POM) file. By automatically resolving and downloading dependencies, managing project configurations, and executing predefined build phases and goals, Maven simplifies the process of compiling, testing, packaging, and deploying Java applications.

To create a new Maven project, open Eclipse and click on File > Maven Project.

The New Maven project window will be opened.

Check Create a simple project (skip archetype selection) and click Next to see the Create new Maven project page. An archetype is a Maven project templating tool kit. You can select any archetype to create a Maven project based on the template. You can skip the archetype selection for this application because you'll create a simple project and configure it from scratch.

Click Next to see your new Maven project.

Enter the Group Id and Artifact Id for your project. The Group Id represents the project's package structure, and the Artifact Id is the project's name. When packed as a jar or war, the Artifact Id will be used as the project's name.

Enter the starting version for this project, and select war packaging. Because this is a web application, a WAR file is used to deploy a Java EE application into an application server such as Tomcat.

Click Finish to create the project.

Configuring Project Dependencies and Plugins

In this section, you'll learn how to configure the project dependencies necessary to create, build, and run the application.

The dependencies are handled in the pom.xml file. Open the pom.xml file and add the dependencies below in the dependencies section. (If the dependencies section is not available, you can add it using .)

  1. The Servlet dependency will be used to create a Java web application using servlets:
<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
</dependency>
  1. The Google Cloud Firestore dependency will be used to store the subscription details in the document:
<dependency>
	<groupId>com.google.cloud</groupId>
	<artifactId>google-cloud-firestore</artifactId>
	<version>3.13.0</version>
</dependency>
  1. The Gson library converts Java objects into their JSON representation. The Recurring Billing API accepts inputs as JSON. Hence, this library will be useful in converting Java objects into JSON:
<dependency>
	<groupId>com.google.code.gson</groupId>
	<artifactId>gson</artifactId>
	<version>2.10.1</version>
</dependency>

You need to install an application server to run the application. You can use Apache Tomcat as the application server in this application.

Maven is a plugin execution framework, and every task is executed by plugins. To add plugins, create a build section using and add a section inside the build section in the pom.xml file.

To use Apache Tomcat as the application server, add the tomcat7-maven-plugin inside the plugins section of the pom.xml file:

<build>
		<plugins>
			<plugin>
				<groupId>org.apache.tomcat.maven</groupId>
				<artifactId>tomcat7-maven-plugin</artifactId>
				<version>2.2</version>
				<configuration>
					<port>8090</port>
					<path>/</path>
				</configuration>
			</plugin>
		</plugins>
	</build>

Get in Touch

Talk to us about adding recurring subscription payments to your Node.js application today.

Using Google Cloud Firestore as a Database

In this tutorial, you'll use Google Cloud Firestore to store user profile information, credit card information, and the details about the user subscription.

You're using Google Cloud Firestore because it stores documents using a key-value format, and the data you'll handle in this application is in JSON format, which is a set of key-value pairs. However, you could also use any database of your choice with the Recurring Billing API, such as MySQL or PostgreSQL.

To create a database in Google Cloud, create a service account with the Cloud Datastore Owner and Firestore Service Agent roles. These roles let the service account create, update, and get documents from the Firestore data store. For detailed instructions on how to use a Firestore database, check out the Firestore documention .

After creating the service account, create a key, download the key file , and place it in the src/main/resources folder of your project. All the necessary details for authenticating the connection to the Firestore data store is available in the JSON file. Remember to update the name of the JSON file to your actual JSON file name.

Lastly, create a class called FireStoreHandler.java in the handlers package. Mention the package name handlers in the Package text box and place the following code into it:

package handlers;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import com.google.api.core.ApiFuture;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.firestore.CollectionReference;
import com.google.cloud.firestore.DocumentReference;
import com.google.cloud.firestore.DocumentSnapshot;
import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.cloud.firestore.QuerySnapshot;
import com.google.cloud.firestore.SetOptions;
import com.google.cloud.firestore.WriteResult;
import com.google.gson.Gson;

public class FireStoreHandler {

	private static Firestore getFireStoreObject() throws IOException {
		ClassLoader classLoader = FireStoreHandler.class.getClassLoader();
		
		InputStream inputStream = classLoader.getResourceAsStream("paymenthubdemo-477c22e5c9de.json");

		FirestoreOptions options = FirestoreOptions.newBuilder()
				.setCredentials(GoogleCredentials.fromStream(inputStream)).build();

		Firestore firestore = options.getService();
		
		return firestore;
	}

	public static void createDocument(String collectionName, String documentId, Object data) throws Exception {
		
		Firestore firestore = getFireStoreObject();
		
		ApiFuture<WriteResult> future = firestore.collection(collectionName).document(documentId).set(data);
		
		System.out.println("Document created: " + future.get().getUpdateTime());
		firestore.close();
	}

	public static void updateDocument(String collectionName, String documentId, Object data) throws Exception {
		
		Firestore firestore = getFireStoreObject();
		
		DocumentReference documentRef = firestore.collection(collectionName).document(documentId);
		
		ApiFuture<WriteResult> future = documentRef.set(data, SetOptions.merge());
		
		System.out.println("Document updated: " + future.get().getUpdateTime());
		firestore.close();
	}

	public static String getDocument(String collectionName, String documentId) throws Exception {
		
		Firestore firestore = getFireStoreObject();
		
		DocumentReference documentRef = firestore.collection(collectionName).document(documentId);
		
		ApiFuture<DocumentSnapshot> future = documentRef.get();
		
		DocumentSnapshot document = future.get();

		if (document.exists()) {
	        Map<String, Object> documentData = document.getData();
	        Gson gson = new Gson();
	        firestore.close();
	        return gson.toJson(documentData);
	    } else {
	        System.out.println("Document not found.");
	        firestore.close();
	        return null;
	    }
	}
	
	public static Map<String, Map<String, Object>> getAllDocuments(String collectionName) throws InterruptedException, ExecutionException,Exception {
	    Firestore firestore = getFireStoreObject();
	    CollectionReference collectionRef = firestore.collection(collectionName);
	    ApiFuture<QuerySnapshot> future = collectionRef.get();
	    QuerySnapshot querySnapshot = future.get();
	    
	    Map<String, Map<String, Object>> jsonDocuments = new HashMap<>();
	    //Gson gson = new GsonBuilder().setPrettyPrinting().create();
	    
	    for (DocumentSnapshot document : querySnapshot.getDocuments()) {
	        if (document.exists()) {
	        	
	            Map<String, Object> documentData = document.getData();
	            
	            jsonDocuments.put(document.getId(),documentData);
	        } else {
	            System.out.println("Document not found.");
	        }
	    }
	    
	    firestore.close();
	    return jsonDocuments;
	}
	
	public static void deleteDocument(String collectionName, String documentId) throws Exception {
		
		Firestore firestore = getFireStoreObject();
		
		DocumentReference documentRef = firestore.collection(collectionName).document(documentId);
		
		ApiFuture<WriteResult> future = documentRef.delete();
		
		System.out.println("Document deleted: " + future.get().getUpdateTime());
		firestore.close();
	}

}

These methods interact with the datastore to create, update, and retrieve the documents. It uses the credentials available in the JSON file to authenticate the connection.

Creating the Request Signature

The Recurring Billing API requires a signature to authenticate the API requests. The signature is the hash-based message authentication code (HMAC) of the endpoint and payload. HMAC ensures the authenticity and integrity of a request by verifying its source and detecting any alterations made during transmission.

To create an HMAC signature for the request, you will use the createSignature() method available in the Util class.

Create the following Utils.java class in the package utils:

package utils;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class Utils {
	
	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);
	}
	
	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()));
	}
}

It contains the method to generate a request signature based on the endpoint, the JSON payload, and the EPI key, a credential obtained from the North portal.

Creating a Recurring Billing App

This section explains how to create different servlets to handle requests sent by users, process them, and send a response back to the user.

You'll create three servlets:

  • Subscription handler: a servlet to handle the subscription requests placed from the home and profile pages
  • Home page: a servlet to serve the home page for the subscription. It serves the HTML page for subscribing to the application.
  • Profile page: a servlet to serve the profile page of the subscribed users. It serves the HTML page for displaying the profile and the subscription information of the subscribed users. The user can also pause, resume, or cancel the subscription on this page.

Creating the Subscription Handler

Next, you'll create a SubscriptionHandler.java class in the handlers package. This class includes the methods to handle the subscription requests placed from the home page.

To create a new class, select the File > New menu to open the Select a Wizard page. Select the Class option in the wizard and click Next. The New Java Class wizard will be opened. Use handlers as the package name and SubscriptionHandler.java as the class name and click Finish.

To create the subscription handler, place the following code in the class you just created. Remember to update the EPI key and the EPI ID in the appropriate fields:

package handlers;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpHeaders;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;

import utils.Utils;

@WebServlet("/subscriptionhandler")
public class SubscriptionHandler extends HttpServlet {

	private static final String EPI_KEY = "<YOUR_EPI_KEY>";

	private static final String EPI_ID = "<YOUR_EPI_ID>";

	private static final String API_URL = "https://billing.epxuap.com";

	private static final long serialVersionUID = 7901708808237295496L;

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		// Retrieve form data from the HttpServletRequest
		String firstName = request.getParameter("FirstName");
		String lastName = request.getParameter("LastName");
		String phone = request.getParameter("Phone");
		String email = request.getParameter("Email");
		String accountNumber = request.getParameter("AccountNumber");
		String expirationDate = request.getParameter("ExpirationDate");
		String cvv = request.getParameter("CVV");
		String postalCode = request.getParameter("PostalCode");
		String streetAddress = request.getParameter("StreetAddress");
		// String amount = request.getParameter("Amount");
		String frequency = request.getParameter("frequency");

		String billingDate = request.getParameter("BillingDate");
		System.out.println("date:" + billingDate);

		String numberOfPayments = request.getParameter("NumberOfPayments");

		String description = request.getParameter("Description");
		String plan = request.getParameter("plan");

		String subscriptionId = request.getParameter("subId");

		String changeSubscription = request.getParameter("changeSub");

		String responseCode = "";
String subscriptionMessage = "";

		if (subscriptionId != null && subscriptionId.equals("0")) {
			JsonObject inputJSON = generateJSONFromInput(firstName, lastName, phone, email, accountNumber,
					expirationDate, cvv, postalCode, streetAddress, frequency, billingDate, numberOfPayments,
					description, plan);
			responseCode = addSubscription(inputJSON);
			if (responseCode.equals("00")) {
subscriptionMessage = "Subscription created succesfully. ";

				redirectToProfilePage(response, email, subscriptionMessage);
			} else {
				printErrorMessage(response, responseCode);
			}

		} else {
			JsonObject jsonObject = new JsonObject();

			BigDecimal decimalValue = new BigDecimal(subscriptionId);
			int subscirptionIdInt = decimalValue.intValue();
			jsonObject.addProperty("SubscriptionID", subscirptionIdInt);

			int httpResponseCode = 0;

			if (changeSubscription != null && changeSubscription.equals("pause")) {

				jsonObject.addProperty("Paused", true);

				httpResponseCode = pauseResumeSubscription(email, jsonObject);
subscriptionMessage = "Successfully paused.";

			} else if (changeSubscription != null && changeSubscription.equals("resume")) {

				jsonObject.addProperty("Paused", false);

				httpResponseCode = pauseResumeSubscription(email, jsonObject);
subscriptionMessage = "Successfully resumed.";

			} else if (changeSubscription != null && changeSubscription.equals("cancel")) {

				httpResponseCode = cancelSubscription(email, jsonObject);
subscriptionMessage = "Successfully cancelled.";

			}

			if (httpResponseCode == 200) {

				redirectToProfilePage(response, email, subscriptionMessage);
			} else {
				printErrorMessage(response, responseCode);
			}
		}
		response.setContentType("text/html");

	}

	private void printErrorMessage(HttpServletResponse response, String responseCode) throws IOException {
		response.getWriter()
				.println("<h1>Error while creating subscription with the following response code!</h1>");
		response.getWriter().println("<h2> " + responseCode + "!</h2>");
	}

	private void redirectToProfilePage(HttpServletResponse response, String email, String subscriptionMessage) throws IOException {
		response.getWriter().println("<h1>Processed successfully!</h1>");

		String redirectURL = "/profile?mailID=" + email + "&subscriptionMessage=" + subscriptionMessage;
		response.sendRedirect(redirectURL);
	}

	private JsonObject generateJSONFromInput(String firstName, String lastName, String phone, String email,
			String accountNumber, String expirationDate, String cvv, String postalCode, String streetAddress,
			String frequency, String billingDate, String numberOfPayments, String description, String plan) {
		JsonObject customerDataJson = new JsonObject();
		customerDataJson.addProperty("FirstName", firstName);
		customerDataJson.addProperty("LastName", lastName);
		customerDataJson.addProperty("Phone", phone);
		customerDataJson.addProperty("Email", email);

		JsonObject creditCardData = new JsonObject();
		creditCardData.addProperty("AccountNumber", accountNumber);
		creditCardData.addProperty("ExpirationDate", expirationDate);
		creditCardData.addProperty("CVV", cvv);

		creditCardData.addProperty("FirstName", firstName);
		creditCardData.addProperty("LastName", lastName);
		creditCardData.addProperty("PostalCode", postalCode);
		creditCardData.addProperty("StreetAddress", streetAddress);
		// System.out.println(creditCardData.toString());

		JsonObject paymentMethod = new JsonObject();

		paymentMethod.add("CreditCardData", creditCardData);

		double amount = 0;

		if (plan.equals("basic")) {
			amount = 2121.21;
		} else if (plan.equals("premium")) {
			amount = 5000.00;
		}

		JsonObject subscriptionData = new JsonObject();

		subscriptionData.addProperty("Amount", amount);

		subscriptionData.addProperty("Frequency", frequency);

		subscriptionData.addProperty("BillingDate", billingDate);

		subscriptionData.addProperty("FailureOption", "Forward");

		subscriptionData.addProperty("NumberOfPayments", Integer.parseInt(numberOfPayments));
		subscriptionData.addProperty("Retries", 5);
		subscriptionData.addProperty("Description", description);

		JsonObject mainJson = new JsonObject();
		mainJson.add("CustomerData", customerDataJson);
		mainJson.add("PaymentMethod", paymentMethod);
		mainJson.add("SubscriptionData", subscriptionData);

		addJSONTOFireStore(email, "CustomerData", customerDataJson);
		addJSONTOFireStore(email, "CreditCardData", paymentMethod.getAsJsonObject("CreditCardData"));
		addJSONTOFireStore(email, "SubscriptionData", subscriptionData);

		return mainJson;
	}

	private void addJSONTOFireStore(String collectionName, String documentID, JsonObject jsonString) {
		TypeToken<Map<String, Object>> typeToken = new TypeToken<Map<String, Object>>() {
		};
		Map<String, Object> data = new Gson().fromJson(jsonString, typeToken.getType());
		try {
			FireStoreHandler.createDocument(collectionName, documentID, data);

		} catch (Exception e) {

			e.printStackTrace();
		}
	}

	private String addSubscription(JsonObject jsonPayload) {

		String endPoint = "/subscription";

		String requestURI = API_URL + endPoint;

		HttpResponse<String> response = executeHttpRequest(jsonPayload, endPoint, requestURI);

		String emailID = jsonPayload.getAsJsonObject("CustomerData").get("Email").getAsString();

		Gson gson = new Gson();

		JsonObject responseString = gson.fromJson(response.body(), JsonObject.class);

		JsonElement subscriptionResult = responseString.get("VerifyResult");
		String responseCode = subscriptionResult.getAsJsonObject().get("Code").getAsString();

		System.out.println(responseCode);

		if (responseCode.equals("00")) {

			addJSONTOFireStore(emailID, "SubscriptionData", responseString);

		}
		return responseCode;
	}

	private int pauseResumeSubscription(String emailId, JsonObject jsonPayload) {

		String endPoint = "/subscription/pause";

		String requestURI = API_URL + endPoint;

		HttpResponse<String> response = executeHttpRequest(jsonPayload, endPoint, requestURI);

		Gson gson = new Gson();

		JsonObject responseString = gson.fromJson(response.body(), JsonObject.class);

		addJSONTOFireStore(emailId, "SubscriptionResponse", responseString);

		return response.statusCode();

	}

	private int cancelSubscription(String emailId, JsonObject jsonPayload) {

		String endPoint = "/subscription/cancel";

		String requestURI = API_URL + endPoint;

		HttpResponse<String> response = executeHttpRequest(jsonPayload, endPoint, requestURI);

		Gson gson = new Gson();

		JsonObject responseString = gson.fromJson(response.body(), JsonObject.class);

		addJSONTOFireStore(emailId, "SubscriptionResponse", responseString);

		return response.statusCode();
	}

	private HttpResponse<String> executeHttpRequest(JsonObject jsonPayload, String endPoint, String requesURI) {

		Gson gson = new Gson();

		String jsonString = gson.toJson(jsonPayload);

		String signature = null;

		HttpClient httpClient = HttpClient.newHttpClient();

		URI uri = URI.create(requesURI);

		HttpResponse<String> response;

		try {

			signature = Utils.createSignature(endPoint, jsonString, EPI_KEY);

			HttpRequest request = HttpRequest.newBuilder(uri).header("Content-Type", "application/json")
					.header("EPI-Signature", signature).header("EPI-Id", EPI_ID)
					.POST(HttpRequest.BodyPublishers.ofString(jsonString)).build();

			response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

			int statusCode = response.statusCode();

			HttpHeaders headers = response.headers();

			String responseBody = response.body();

			System.out.println("Response Code: " + statusCode);

			System.out.println("Response Headers: " + headers);

			System.out.println("Response Body: " + responseBody);

			return response;

		} catch (NoSuchAlgorithmException | InvalidKeyException | IOException | InterruptedException e) {

			e.printStackTrace();
		}
		return null;

	}
}

The subscription handler receives the Post request from the home and profile pages and handles them as follows:

  • If the subscription ID is null or 0, it adds a new subscription using the /subscription endpoint.
  • If the subscription ID is not null and has a proper numeric value, it checks the change requested to the subscription, whether pause, resume, or cancel. Based on this request parameter, it calls the appropriate API endpoint.

All the API endpoints, request specifications, and response models are available in North's documentation for subscriptions.

/subscription, /subscription/pause, and /subscription/cancel are invoked using the HTTP post requests as follows:

  • A request URL is created using the API URL https://billing.epxuap.com and the appropriate endpoint.
  • A request signature is created using the createSignature() method and the EPI_KEY, as explained above.
  • An HttpRequest is built using the JSON payload, EPI-ID, and the EPI-Signature. The request is sent, and the response is received.
  • The response is added to the Firestore database for displaying the information on the profile page.

For example, while creating a new subscription, you'll invoke the /subscription endpoint. If the subscription is successful, it returns a 200 response code in the HttpResponse. In the HttpResponse body is a JSON object called VerifyResult, and inside this JSON object is a nested JSON with a key code. Its response code is 00, denoting that the subscription was successfully created.

Add this response to Firestore and redirect the user to the profile page.

Creating the Home Page

Now you'll create a HomeServlet.java class in the demo package. This class includes the methods to create a home page and display the available plans as well as a button to subscribe to a plan. When the button is clicked, it'll send the appropriate request to the subscription handler to create a subscription.

Create another new class using the same steps as before, but use demo as the package name and HomeServlet.java as the class name. To create the home page, place the following code in this new class:

package demo;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/home")
public class HomeServlet extends HttpServlet {
    private static final long serialVersionUID = -1541805457506999385L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        
        out.println("<h1>North Subscription Demo</h1>");

        out.println("<form method=\"POST\" action=\"/subscriptionhandler?subId=0\">");
        out.println("First Name: <input type=\"text\" name=\"FirstName\" value=\"Joe\"><br><br>");
        out.println("Last Name: <input type=\"text\" name=\"LastName\" value=\"Doe\"><br><br>");
        out.println("Phone: <input type=\"text\" name=\"Phone\" value=\"1234567890\"><br><br>");
        out.println("Email: <input type=\"text\" name=\"Email\" value=\"jdoe@domanin.com\"><br><br>");
        out.println("Address: <input type=\"text\" name=\"Address\" value=\"1234 Ruby Road\"><br><br>");
        out.println("Postal Code: <input type=\"text\" name=\"PostalCode\" value=\"12346\"><br><br>");
        
        out.println("<h2>Credit Card Information</h2>");
        
        out.println("Account Number: <input type=\"text\" name=\"AccountNumber\" value =\"400000000000000002\"><br><br>");
        out.println("Expiration Date: <input type=\"text\" name=\"ExpirationDate\"  placeholder=\"YYMM\" value=\"2211\" required><br><br>");
        out.println("CVV: <input type=\"text\" name=\"CVV\" value=\"123\"><br><br>");

        
        out.println("<h2>Subscription Details</h2>");
        
        out.println("Select Plan:");
        out.println("<label><input type=\"radio\" name=\"plan\" value=\"basic\" checked> Basic ($2121.21) </label>");
        out.println("<label><input type=\"radio\" name=\"plan\" value=\"premium\"> Premium ($5000.00)</label><br><br>");

        out.println("Frequency:");
        out.println("<label><input type=\"radio\" name=\"frequency\" value=\"Weekly\"> Weekly</label>");
        out.println("<label><input type=\"radio\" name=\"frequency\" value=\"Biweekly\"> Biweekly</label>");
        out.println("<label><input type=\"radio\" name=\"frequency\" value=\"Monthly\" checked> Monthly</label><br><br>");

        out.println("Billing Date: <input type=\"date\" name=\"BillingDate\"><br><br>");
        out.println("Number of Payments: <input type=\"text\" name=\"NumberOfPayments\" value =\"5\"><br><br>");
        out.println("Description: <input type=\"text\" name=\"Description\" value=\"Subscription for Streaming service\"><br><br>");

        out.println("<input type=\"submit\" value=\"Submit\">");
        out.println("</form>");

        out.println("</body></html>");

    }
}

It creates the HTML form for getting the profile, payment, and subscription information from users. It also defines that a Post request should be sent to the /subscriptionhandler with the query parameter subId=0 when the form is submitted. Here, subId=0 denotes that a new subscription must be created with the available details.

This form allows the user to enter their profile information such as name and address as well as payment information such as credit card details. They can also select the subscription plan and payment intervals (weekly, biweekly, or monthly).

Creating the Profile Page

Now you'll create a ProfileServlet.java class in the demo package. This class includes the methods to display the subscription details such as the username, the subscribed plan, and a button to pause or cancel the subscription. When the cancel or pause button is clicked, it'll send the appropriate request to the subscription handler to modify the subscription.

Create a new class using the same steps as before but use demo as the package name and ProfileServlet.java as the class name. Place the following code in this class to create the profile page:

package demo;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import handlers.FireStoreHandler;

@WebServlet("/profile")
public class ProfileServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.println("<html><body>");
		out.println("<h1>North Profile Page</h1>");

		String emailId = request.getParameter("mailID");
		String subscriptionMessage = request.getParameter("subscriptionMessage");

		Map<String, Map<String, Object>> documents = getUserDocumentsFromFireStore(emailId);
		
		
		
		String firstName = documents.get("CustomerData").get("FirstName").toString();
		
		out.println("<h2>Welcome " + firstName + "! </h2><br><br>");

if (subscriptionMessage != null && subscriptionMessage !="") {
			out.println("<h3>Subscription Status : " + subscriptionMessage + "! </h3><br>");
		}

		out.println("<form method=\"POST\" action=\"/your-action-url\">");

		out.println("First Name: <input type=\"text\" name=\"FirstName\" value = "+ firstName + "><br><br>");
		out.println("Last Name: <input type=\"text\" name=\"LastName\" value = " + documents.get("CustomerData").get("LastName") + "><br><br>");
		out.println("Phone: <input type=\"text\" name=\"Phone\" value = " + documents.get("CustomerData").get("Phone") + "><br><br>");
		out.println("Email: <input type=\"text\" name=\"Email\" value = " + documents.get("CustomerData").get("Email") + "><br><br>");

		out.println("<h2>Credit Card Information</h2>");
		out.println("Account Number: <input type=\"text\" name=\"AccountNumber\"  value = " + documents.get("CreditCardData").get("AccountNumber") + "><br><br>");
		out.println("Expiration Date: <input type=\"text\" name=\"ExpirationDate\"  value = " + documents.get("CreditCardData").get("ExpirationDate") + "><br><br>");
		out.println("CVV: <input type=\"text\" name=\"CVV\" value = " + documents.get("CreditCardData").get("CVV") + "><br><br>");

		out.println("<h2>Subscription Details</h2>");
		out.println("Amount: <input type=\"text\" name=\"Amount\"  value = " + documents.get("SubscriptionData").get("Amount") + "><br><br>");
		out.println("Frequency: <input type=\"text\" name=\"Frequency\"  value = " + documents.get("SubscriptionData").get("Frequency") + "><br><br>");
		out.println("Billing Date: <input type=\"text\" name=\"BillingDate\"  value = " + documents.get("SubscriptionData").get("BillingDate") + "><br><br>");
		out.println("Failure Option: <input type=\"text\" name=\"FailureOption\"  value = " + documents.get("SubscriptionData").get("FailureOption") + "><br><br>");
		out.println("Number of Payments: <input type=\"text\" name=\"NumberOfPayments\"  value = " + documents.get("SubscriptionData").get("NumberOfPayments") + "><br><br>");
		out.println("Retries: <input type=\"text\" name=\"Retries\"  value = " + documents.get("SubscriptionData").get("Retries") + "><br><br>");
		out.println("Description: <input type=\"text\" name=\"Description\"  value = " + documents.get("SubscriptionData").get("Description").toString() + "><br><br>");

		String subscriptionId = documents.get("SubscriptionData").get("id").toString();
		
		out.println("<input type=\"hidden\" name=\"subscriptionid\" value=\"" + subscriptionId + "\">");
		
		
		out.println("<input type=\"button\" value=\"pause\" onclick=\"submitForm(this.value);\">");
		out.println("<input type=\"button\" value=\"resume\" onclick=\"submitForm(this.value);\">");
		out.println("<input type=\"button\" value=\"cancel\" onclick=\"submitForm(this.value);\">");
		out.println("</form>");
		out.println("<script>");
		out.println("function submitForm(buttonValue) {");
		out.println("    var form = document.createElement('form');");
		out.println("    form.setAttribute('method', 'post');");
		out.println("    form.setAttribute('action', '/subscriptionhandler');");

		out.println("    var hiddenField = document.createElement('input');");
		out.println("    hiddenField.setAttribute('type', 'hidden');");
		out.println("    hiddenField.setAttribute('name', 'Email');");
		out.println("    hiddenField.setAttribute('value', '" + emailId + "');");
		out.println("    form.appendChild(hiddenField);");

		out.println("    var changeSubField = document.createElement('input');");
		out.println("    changeSubField.setAttribute('type', 'hidden');");
		out.println("    changeSubField.setAttribute('name', 'changeSub');");
		out.println("    changeSubField.setAttribute('value', buttonValue);"); 
		out.println("    form.appendChild(changeSubField);");
		
		out.println("    var subscriptionIdField = document.createElement('input');");
		out.println("    subscriptionIdField.setAttribute('type', 'hidden');");
		out.println("    subscriptionIdField.setAttribute('name', 'subId');");
		out.println("    subscriptionIdField.setAttribute('value', '" + subscriptionId + "');"); 
		out.println("    form.appendChild(subscriptionIdField);");

		out.println("    document.body.appendChild(form);");
		out.println("    form.submit();");
		out.println("}");
		out.println("</script>");

		
		out.println("</body></html>");

	}

	private Map<String, Map<String, Object>> getUserDocumentsFromFireStore(String emailId) {
		Map<String, Map<String, Object>> documents = null;
		try {
			documents = FireStoreHandler.getAllDocuments(emailId);
		} catch (Exception e) {
			
			e.printStackTrace();
		}
		return documents;
	}
}

The profile servlet accepts a query parameter mailID. Based on this email ID, it will fetch the necessary documents from Firestore and display details such as name, address, credit card data, and other subscription details on the profile page.

It also creates buttons to pause, resume, and cancel subscriptions. When these buttons are clicked, a Post request is sent to the subscription handler with the parameters emailid, SubscriptionID, and the request type (pause, resume, or cancel). Based on these parameters, the subscription handler will process the request.

Running the Application

Now that you've created all the servlet classes needed to create and manage subscriptions, let's run the application.

To run your Maven application, right-click on your Maven project and select Run As > Maven Build, which will open the Maven build Run configuration:

Enter a name for your configuration and enter tomcat7:run in the Goals.

Click Run to deploy your project on the Tomcat server.

You can reach your application on your browser using the URL http://localhost:8090/home, where http://localhost:8090/ is the local host address, 8090 is the Tomcat port number, and /home is the servlet path you have defined in the Home servlet class annotation.

When you enter the URL in the browser, you should see the home page.

Enter all the necessary details (most details are prefilled with test data) and click Submit.

The subscription will be created, and you'll be redirected to the profile page.

You can modify your subscription from here using the pause, resume, or cancel buttons.

How To Get Started

In this article, you learned how to let users sign up for and manage a subscription service in your Java application using North's Recurring Billing API. The code for this tutorial is available in [this GitHub repo {


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.