Leveraging Marqeta to Build a Payment Service in Spring Boot
After paying an Uber, John Vester decided to dig into the Marqeta API used for payment processing. Hours later, he created a fully functional Payment Service.
Join the DZone community and get the full member experience.
Join For FreeFor the last two years, I experienced the Uber service while traveling from the Las Vegas airport to my sister-in-law’s beautiful estate in the outlying suburbs. In every case, the Uber rides were purchased for me—a nice gesture from my brother-in-law. As a result, I was able to focus on the opportunity to meet someone new while we crossed the rocky and arid desert Nevada vegetation, containing more than a fair share of fancy hotels and casinos.
In July, my flight back to the midwest was met with a few delays. Instead of arriving home shortly after midnight, the Southwest plane landed at 4 am local time. I wasn’t about to ask my wife (and toddler son) to make the 30-minute drive (one-way) to pick me up. Instead, I decided to use Uber for the very first time, scheduling my own ride.
I was impressed. Not only did I get to meet another wonderful soul, but I experienced an amazing use case of paying for transportation using only my mobile phone. In fact, I set up my account prior to departure as soon as I knew I would need transportation when a majority of the city was fast asleep.
After resting for several hours and settling back in at my home office, I decided to learn more about the payment processing service behind Uber. This led me to Marqeta, a company I had never heard of before. Looking over their payment platform and API, I wanted to see how easily I could create a payment service in Spring Boot while leveraging the Marqeta platform.
Since I have a passion for writing, I thought I would document the results of my quest.
About Marqeta
CEO Jason Gardner founded Marqeta “to democratize innovation by enabling any company—a brand new startup or the most established enterprise—to build on the shoulders of our modern card issuing platform without worrying about the complexity of payments infrastructure.”
This approach immediately got my attention, as I have been ultra-focused on leveraging frameworks, products, and services in order to maintain my focus on providing solutions which extend the value of my client’s (or employer’s) intellectual property.
The success of Marqeta can easily be measured by the “who’s who” list of technology-focused customers who rely on Marqeta on a 24x7x365 basis. As noted earlier, the Uber brand leverages Marqeta. Additional customers include DoorDash, Square, Instacart, Affirm, and Brex (which undergirds Airbnb, Classpass, and Flexport), all of whom have become household names in the last five years.
As of March 31, 2021, Marqeta had processed over 320 million cards (payment sources) and 1.6 billion transactions. The success of Marqeta has been recognized by Forbes (Fintech 50 award), Fast Company (50 most innovative award) and CNBC (Disruptor Top 50 award).
Marqeta seems like the “real deal” to me.
No Java SDK, No Problem
Marqeta offers a very impressive Core API Explorer that allows developers to gain an understanding of the payment platform. In fact, my prior experience has taught me that when I start seeing possibilities and get excited from simply browsing through an API, then I am on the receiving end of a quality product.
While software development kits (SDKs) currently exist for Python and Ruby, one does not exist for Java. However, after reviewing the Core API, I felt like building a payment service in Spring Boot, which acts as a middleware product and makes RESTful calls to the Marqeta platform, would be an interesting quest.
Once completed, this Java-based middleware payment service can be leveraged by custom applications running in my domain which require the ability to perform financial transactions.
Before going down this path, perhaps I should provide some high-level details about the Marqeta platform.
Getting Started with Marqeta
To keep things simple, there are four main concepts at the heart of the Marqeta design:
User - the customer who plans to perform transactions (you)
Funding Source - the financial institution where your funds reside (your bank)
Card Product - card services offered by your funding source (such as Visa, Mastercard, Discover, and so on)
Card - a card product instance tied to your funding source (your credit card)
I created the following illustration to demonstrate these relationships:
For the purposes of this article, I decided to set up the user, funding source, card product, and card using direct calls to the Marqeta Core API via basic cURL commands.
However, before I could get started, I needed to create a new account with Marqeta, which would grant access to the Marqeta sandbox.
Establishing Access to the Marqeta Sandbox
Getting started with Marqeta at zero cost is as simple as creating an account using the link below:
Once your account has been created, navigating to the Development Dashboard will provide details similar to what is displayed below:
For the remainder of this article, the following items in the image above will be referenced:
Application token =
${APPLICATION_TOKEN}
andAPPLICATION_TOKEN_GOES_HERE
Admin access token =
${ADMIN_ACCESS_TOKEN}
andADMIN_ACCESS_TOKEN_GOES_HERE
Creating a New User
The following cURL command can be used to create a new user by the name of Randy Kern:
curl -i \
-X POST \
-H 'Content-Type: application/json' \
--user APPLICATION_TOKEN_GOES_HERE:ADMIN_ACCESS_TOKEN_GOES_HERE \
-d '{
"first_name": "Randy",
"last_name": "Kern",
"active": true
}' \
https://sandbox-api.marqeta.com/v3/users
A successful POST yields a 2xx HTTP response and unique token for the Randy Kern user:
{
"token" : "1017b62c-6b61-4fcd-b663-5c81feab6524",
"active" : true,
"first_name" : "Randy",
"last_name" : "Kern",
"uses_parent_account" : false,
"corporate_card_holder" : false,
"created_time" : "2021-08-14T13:01:13Z",
"last_modified_time" : "2021-08-14T13:01:14Z",
"metadata" : { },
"account_holder_group_token" : "DEFAULT_AHG",
"status" : "ACTIVE",
"deposit_account" : {
"token" : "6716c09f-c0dd-430f-ada5-d39f6c5059bb",
"account_number" : "40018215000000810",
"routing_number" : "293748000",
"allow_immediate_credit" : false
}
}
Take note of the token value as it will be required for use later.
Creating a Just-In-Time Funding Source
Next, a funding source is required. For this example, we’ll use a just-in-time (JIT) funding source. Think of the JIT model like what happens when you use a debit card, rather than a gift card which is preloaded with a certain amount.
The following cURL command establishes a JIT funding source called "funding_source_bank" in my Marqeta sandbox:
curl -X POST "https://sandbox-api.marqeta.com/v3/fundingsources/program" \
--user APPLICATION_TOKEN_GOES_HERE:ADMIN_ACCESS_TOKEN_GOES_HERE \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-d ‘{"name":"funding_source_bank","active":true}’
The resulting payload should be similar to what is noted below:
{
"name": "funding_source_bank",
"active": true,
"token": "069e4f6c-a731-48e2-82b7-0df9f44dea62",
"created_time": "2021-08-15T02:42:06Z",
"last_modified_time": "2021-08-15T02:42:06Z",
"account": "12.003.001.000000"
}
Take note of the token value from this newly-created funding source, as this will be our funding_source_token
in the next cURL command.
Establishing a Card Product
A card product links to the JIT funding source and a card which belongs to a given user. The following cURL command creates a new card product which uses the "funding_source_bank" created above:
curl -X POST "https://sandbox-api.marqeta.com/v3/cardproducts" \
--user APPLICATION_TOKEN_GOES_HERE:ADMIN_ACCESS_TOKEN_GOES_HERE \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"start_date":"2020-05-01",
"name":"Funding Source Bank Card",
"config":{
"fulfillment":{
"payment_instrument":"VIRTUAL_PAN"
},
"poi":{
"ecommerce":true,
"atm":true
},
"card_life_cycle":{
"activate_upon_issue":true
},
"jit_funding":{
"program_funding_source":{
"funding_source_token":"JIT_TOKEN_GOES_HERE",
"refunds_destination":"PROGRAM_FUNDING_SOURCE",
"enabled":true
}
}
}
}'
This request yields a card product token, which will be used in creating a card for Randy Kern. A portion of the resulting payload is displayed below:
{
"token": "99db1d05-9199-446c-9ff1-047df5ccf154",
"name": "Funding Source Bank Card",
"active": true,
...
Creating a New Card
A card in Marqeta is a payment device that enables a user to conduct transactions at merchants. To keep it simple, just think of a card as a debit or credit card. For our example, we’ll create a card from the "Funding Source Bank Card" card product established above.
To create a card for Randy Kern, execute the following cURL command:
curl -X POST "https://sandbox-api.marqeta.com/v3/cards" \
--user APPLICATION_TOKEN_GOES_HERE:ADMIN_ACCESS_TOKEN_GOES_HERE \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"user_token":"RANDY_KERN_USER_TOKEN_GOES_HERE",
"card_product_token":"FUNDING_SOURCE_BANK_CARD_PRODUCT_TOKEN_GOES_HERE
}'
A payload similar to what is shown below should appear:
{
"created_time": "2021-08-15T02:47:52Z",
"last_modified_time": "2021-08-15T02:47:52Z",
"token": "9d32f3b7-2fb6-43ec-b4a8-99fc81312301",
"user_token": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"card_product_token": "99db1d05-9199-446c-9ff1-047df5ccf154",
"last_four": "4445",
"pan": "111111______4445",
"expiration": "0825",
"expiration_time": "2025-08-31T23:59:59Z",
"barcode": "11195778081390829687",
"pin_is_set": false,
"state": "ACTIVE",
"state_reason": "New card activated",
"fulfillment_status": "ISSUED",
"instrument_type": "VIRTUAL_PAN",
"expedite": false,
"metadata": {}
}
At this point, we are ready to configure the Spring Boot service.
Getting Started with Spring Boot
With a base set of data created in my Marqeta sandbox, the next step is to create a simple payment service using Spring Boot. The Spring Boot service uses the following URIs:
GET users configured in the Marqeta platform
GET cards associated with a given user token
GET transactions associated with a given user token
POST a new transaction for a card associated with a user using a funding source
To get started, I added the following dependencies into the Spring Boot project that I created in my IntelliJ IDEA development environment:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
Next, I created a series of data transformation objects (DTOs) to adhere to the data contract used by—and expected by—the Marqeta platform. Below is a screenshot of the DTO classes that I introduced:
Rather than cover all of these items here, you can always review the resulting source code for the Spring Boot project in more detail here:
Marqeta Configuration
The following properties are externalized into an application.yml configuration file in Spring Boot:
marqeta:
application-token: ${APPLICATION_TOKEN}
admin-access-token: ${ADMIN_ACCESS_TOKEN}
hostname: sandbox-api.marqeta.com
secure: true
base-uri: /v3
The application-token and admin-access-token values would be set outside the program (such as an environment variable) to avoid checking secret information into the repository.
In a realistic example, the hostname property would also be externalized, but I decided to keep this example as simple as possible.
The Marqeta configuration properties are available in Spring Boot as a result of the MarqetaConfigurationProperties class:
@Data
@Configuration("marqetaConfigurationProperties")
@ConfigurationProperties("marqeta")
public class MarqetaConfigurationProperties {
private String applicationToken;
private String adminAccessToken;
private String hostname;
private boolean secure;
private String baseUri;
}
As a result of this change, the configuration values are available to any component or service via dependency injection. Below is an example from the user service:
@RequiredArgsConstructor
@Service
public class UserService {
private final MarqetaConfigurationProperties marqetaConfigurationProperties;
...
}
Communicating with the Marqeta Core API
In order to keep things “dry” (don’t repeat yourself), I created a MarqetaUtils utility class to house helper methods for GET and POST communication with the Marqeta Core API. For simplicity, I list the marqetaGet()
method below:
public final class MarqetaUtils {
private MarqetaUtils() { }
public static CloseableHttpResponse marqetaGet(MarqetaConfigurationProperties marqetaConfigurationProperties, String contextUrl, List<NameValuePair> nameValuePairs) throws Exception {
CloseableHttpClient closeableHttpClient = getCloseableHttpClient(marqetaConfigurationProperties);
URIBuilder uriBuilder = createUriBuilder(marqetaConfigurationProperties, contextUrl, nameValuePairs);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
httpGet.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet);
HttpUtils.checkResponse(closeableHttpResponse);
return closeableHttpResponse;
}
...
}
As a result, the User API in Spring Boot simply needs to provide the following information to make a GET request to the Marqeta Core API:
CloseableHttpResponse closeableHttpResponse = MarqetaUtils.marqetaGet(marqetaConfigurationProperties, "/users", null)
Creating a GET URI in Spring Boot
As noted above, GET-based URIs will exist for users, cards, and transactions. Below is an illustration to note the flow for using the User API:
To retrieve a list of users from the Spring Boot service, we would use the following cURL:
curl --location -X GET 'localhost:9999/users'
This request results in a call to the getUsers()
method in the UserController
class:
@RequiredArgsConstructor
@Slf4j
@CrossOrigin
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@RestController
public class UserController {
private final UserService userService;
@GetMapping(value = "/users")
public ResponseEntity<List<User>> getUsers() {
try {
return new ResponseEntity<>(userService.getAllUsers(), HttpStatus.OK);
} catch (Exception e) {
log.error(e.getMessage(), e);
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
}
The userService.getAllUsers()
method interacts with the UserService
class to return a list of User
objects:
public List<User> getAllUsers() throws Exception {
try (CloseableHttpResponse closeableHttpResponse = MarqetaUtils.marqetaGet(marqetaConfigurationProperties, "/users", null)) {
HttpEntity httpEntity = closeableHttpResponse.getEntity();
if (httpEntity != null) {
MarqetaUserResponse marqetaUserResponse = objectMapper.readValue(EntityUtils.toString(httpEntity), MarqetaUserResponse.class);
if (marqetaUserResponse != null) {
return marqetaUserResponse.getUsers();
}
}
return new ArrayList<>();
}
}
Using the information provided above, the following JSON response payload is returned to the client making the request:
[
{
"token": string,
"createdTime": number,
"lastModifiedTime": number,
"metadata": {},
"active": boolean,
"firstName": string,
"lastName": string,
"usersParentAccount": boolean,
"corporateCardHolder": boolean,
"accountHolderGroupToken": string,
"status": string
}
]
Following the same approach, I created the remaining GET URIs, along with a POST request. Each request to the Marqeta Core API uses the static MarqetaUtils class in order to reduce the amount of duplicate code in the Spring Boot service.
To review all of the services created for this article, simply open the following URL:
Putting it All Together
With the Marqeta sandbox created and the Spring Boot service ready for use, I started the Spring Boot service:
In order to validate the configuration settings are correct, I also created a /ping URI in Spring Boot to interact with the Marqeta Core API without requesting any data. I executed the following cURL against the Spring Boot service:
curl --location -X GET 'localhost:9999/ping'
The /ping URI responded with the following payload and a 200 HTTP response:
{
"success": true,
"version": "rel-21.7.1",
"revision": "7b6bf2842d024b0d26f5e29f5cc50617b0d49872",
"timestamp": "Fri Jul 16 22:37:50 UTC 2021",
"env": "sandbox",
"id": "i-0e0a4a9bc40f8d05d:us-east-1a:10.128.19.176"
}
The 200 HTTP response and true value for the “success” property indicates the request was successful.
Getting a List of Users in Marqeta
As noted earlier, the following cURL command will return a list of users:
curl --location -X GET 'localhost:9999/users'
I received the following response payload from the Spring Boot service:
[
{
"token": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"createdTime": 1628946073000,
"lastModifiedTime": 1628946074000,
"metadata": {},
"active": true,
"firstName": "Randy",
"lastName": "Kern",
"usersParentAccount": false,
"corporateCardHolder": false,
"accountHolderGroupToken": "DEFAULT_AHG",
"status": "ACTIVE"
}
]
Getting a List of Cards for a Given User in Marqeta
The following cURL command will provide a list of cards associated with the Randy Kern user token:
curl --location --request GET 'localhost:9999/cards/user/1017b62c-6b61-4fcd-b663-5c81feab6524'
The response payload provides a summary of cards for the Randy Kern user:
[
{
"token": "9d32f3b7-2fb6-43ec-b4a8-99fc81312301",
"createdTime": 1628995672000,
"lastModifiedTime": 1628995672000,
"metadata": {},
"userToken": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"cardProductToken": "99db1d05-9199-446c-9ff1-047df5ccf154",
"lastFour": "4445",
"pan": "111111______4445",
"expiration": "0825",
"expirationTime": 1756684799000,
"barcode": "11195778081390829687",
"pinSet": false,
"state": "ACTIVE",
"stateReason": "New card activated",
"fulfillmentStatus": "ISSUED",
"instrumentType": "VIRTUAL_PAN",
"expedite": false
}
]
Posting a Transaction to Marqeta
The Spring Boot service accepts a simple POST request to create a new transaction on the Marqeta platform, using a MarqetaTransactionRequest body payload.
The example below is a MarqetaTransactionRequest payload for a USD $7.50 transaction to The Friendly Tavern:
{
"amount": "7.50",
"mid": "11111",
"card_token": "9d32f3b7-2fb6-43ec-b4a8-99fc81312301",
"card_acceptor": {
"name": "The Friendly Tavern",
"address": "290 S. Main St",
"city": "Zionsville",
"state": "IN",
"zip": "46077",
"country": "USA"
},
"webhook": {
"endpoint": "https://mywebook.url.goes.here.com",
"username": "some_username",
"password": "some_password"
}
}
The payload above can be included in the following cURL statement to leverage the Authorization API in the Spring Boot service:
curl --location --request POST 'localhost:9999/authorization' \
--user APPLICATION_TOKEN_GOES_HERE:ADMIN_ACCESS_TOKEN_GOES_HERE \
--H 'accept: application/json' \
--H 'Content-Type: application/json' \
--d '{
"amount": "7.50",
"mid": "11111",
"card_token": "CARD_TOKEN_GOES_HERE",
"card_acceptor": {
"name": "The Friendly Tavern",
"address": "290 S. Main St",
"city": "Zionsville",
"state": "IN",
"zip": "46077",
"country": "USA"
},
"webhook": {
"endpoint": "SOME_WEBHOOK_URL_GOES_HERE",
"username": "USERNAME_GOES_HERE",
"password": "PASSWORD_GOES_HERE"
}
}'
Upon a successful POST submission, the full details of this transaction are displayed. Additionally, the webhook object provides the opportunity to make a call to the specified endpoint once the request has been processed. More information about using Marqeta webhooks can be found here.
Getting a List of Transactions in Marqeta
Having created a single transaction made by the Randy Kern user, we can access the “transactions by user” URI via the following cURL command:
curl --location --request GET 'localhost:9999/transactions/user/1017b62c-6b61-4fcd-b663-5c81feab6524'
The result is the following data for a single transaction:
[
{
"token": "ca66618d-43c7-4c74-aa30-5b2fedfc676f",
"createdTime": 1629083511000,
"type": "authorization",
"state": "PENDING",
"identifier": "19",
"userToken": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"actingUserToken": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"cardToken": "9d32f3b7-2fb6-43ec-b4a8-99fc81312301",
"gpa": {
"ledgerBalance": 7.50,
"availableBalance": 0.00,
"creditBalance": 0.00,
"pendingCredits": 0.00,
"impactedAmount": -7.50,
"currencyCode": "USD",
"balances": {
"USD": {
"ledgerBalance": 7.50,
"availableBalance": 0.00,
"creditBalance": 0.00,
"pendingCredits": 0.00,
"impactedAmount": -7.50,
"currencyCode": "USD"
}
}
},
"gpaOrder": {
"token": "be3dc1b6-fa22-4ef9-8157-8fc10c86d632",
"createdTime": 1629083511000,
"lastModifiedTime": 1629083511000,
"amount": 7.50,
"transactionToken": "23976c31-efc2-4a44-8834-b5390ab131ab",
"state": "PENDING",
"response": {
"code": "0000",
"memo": "Approved or completed successfully"
},
"funding": {
"amount": 7.50,
"source": {
"token": "**********ea62",
"createdTime": 1628995326000,
"lastModifiedTime": 1628995326000,
"type": "program",
"active": true,
"name": "funding_source_bank",
"defaultAccount": false
}
},
"fundingSourceToken": "**********ea62",
"userToken": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"currencyCode": "USD"
},
"duration": 127,
"userTransactionTime": 1629083511000,
"settlementDate": 1629072000000,
"requestAmount": 5.00,
"amount": 5.00,
"issuerReceivedTime": 1629083511084,
"issuerPaymentNode": "00b8d031e0a4759766b5b5266f5229d8",
"networkReferenceId": "765766405219",
"currencyCode": "USD",
"approvalCode": "223355",
"response": {
"code": "0000",
"memo": "Approved or completed successfully"
},
"network": "DISCOVER",
"acquirer": {
"systemTraceAuditNumber": "940779"
},
"acquirerFeeAmount": 0,
"user": {
"metadata": {}
},
"card": {
"lastFour": "4445",
"metadata": {}
},
"cardAcceptor": {
"mid": "11111",
"mcc": "6411",
"name": "The Friendly Tavern",
"streetAddress": "290 S. Main St",
"city": "Zionsville",
"state": "IN",
"zip": "46077",
"countryCode": "USA"
},
"pos": {
"pinPresent": false,
"partialApprovalCapable": true,
"purchaseAmountOnly": false,
"recurring": false,
"installment": false
}
}
]
While the resulting payload is certainly not lean, this is a financial transaction—with the expected safe guards and checks and balances in place.
Conclusion
Starting in 2021, I have been trying to live the following mission statement, which I feel can apply to any IT professional:
“Focus your time on delivering features/functionality which extends the value of your intellectual property. Leverage frameworks, products, and services for everything else.”
- J. Vester
Marqeta certainly fits into my mission statement, taking the confusion and challenges out of implementing a payment platform using Java and Spring Boot.
In 2020, DZone asked me to write a RefCard focused on payment processing using Java. I turned down the offer, because I felt like it would be far easier to leverage a payment processing service. If I had been aware of Marqeta at the time, I would certainly have made a reference to Marqeta in my response.
If your project goals could benefit from a fully-functional payment processing platform trusted by Uber, DoorDash, Square, Instacart, Affirm, and Brex, then I would highly recommend adding Marqeta to your short list of providers to evaluate.
I hope to dive deeper into more aspects of what Marqeta has to offer, continuing to build upon the Spring Boot service I have already created. My goal will be to publish future articles along the way.
If you are interested in the full source code used in this article, simply open the following repository over on GitLab:
https://gitlab.com/johnjvester/marqeta-example
Have a really great day!
Opinions expressed by DZone contributors are their own.
Comments