visit
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.
For the remainder of this article, the following items in the image above will be referenced:
${APPLICATION_TOKEN}
and APPLICATION_TOKEN_GOES_HERE
${ADMIN_ACCESS_TOKEN}
and ADMIN_ACCESS_TOKEN_GOES_HERE
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
}' \
//sandbox-api.marqeta.com/v3/users
{
"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" : "400810",
"routing_number" : "293748000",
"allow_immediate_credit" : false
}
}
curl -X POST "//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}’
{
"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.
curl -X POST "//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
}
}
}
}'
{
"token": "99db1d05-9199-446c-9ff1-047df5ccf154",
"name": "Funding Source Bank Card",
"active": true,
...
curl -X POST "//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
}'
{
"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": "390829687",
"pin_is_set": false,
"state": "ACTIVE",
"state_reason": "New card activated",
"fulfillment_status": "ISSUED",
"instrument_type": "VIRTUAL_PAN",
"expedite": false,
"metadata": {}
}
<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>
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:
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
@Data
@Configuration("marqetaConfigurationProperties")
@ConfigurationProperties("marqeta")
public class MarqetaConfigurationProperties {
private String applicationToken;
private String adminAccessToken;
private String hostname;
private boolean secure;
private String baseUri;
}
@RequiredArgsConstructor
@Service
public class UserService {
private final MarqetaConfigurationProperties marqetaConfigurationProperties;
...
}
To keep things “dry” (don’t repeat yourself), I created a 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;
}
...
}
CloseableHttpResponse closeableHttpResponse = MarqetaUtils.marqetaGet(marqetaConfigurationProperties, "/users", null)
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<>();
}
}
[
{
"token": string,
"createdTime": number,
"lastModifiedTime": number,
"metadata": {},
"active": boolean,
"firstName": string,
"lastName": string,
"usersParentAccount": boolean,
"corporateCardHolder": boolean,
"accountHolderGroupToken": string,
"status": string
}
]
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'
{
"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"
}
curl --location -X GET 'localhost:9999/users'
[
{
"token": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"createdTime": 00,
"lastModifiedTime": 00,
"metadata": {},
"active": true,
"firstName": "Randy",
"lastName": "Kern",
"usersParentAccount": false,
"corporateCardHolder": false,
"accountHolderGroupToken": "DEFAULT_AHG",
"status": "ACTIVE"
}
]
curl --location --request GET 'localhost:9999/cards/user/1017b62c-6b61-4fcd-b663-5c81feab6524'
[
{
"token": "9d32f3b7-2fb6-43ec-b4a8-99fc81312301",
"createdTime": 00,
"lastModifiedTime": 00,
"metadata": {},
"userToken": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"cardProductToken": "99db1d05-9199-446c-9ff1-047df5ccf154",
"lastFour": "4445",
"pan": "111111______4445",
"expiration": "0825",
"expirationTime": 00,
"barcode": "390829687",
"pinSet": false,
"state": "ACTIVE",
"stateReason": "New card activated",
"fulfillmentStatus": "ISSUED",
"instrumentType": "VIRTUAL_PAN",
"expedite": false
}
]
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": "//mywebook.url.goes.here.com",
"username": "some_username",
"password": "some_password"
}
}
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"
}
}'
curl --location --request GET 'localhost:9999/transactions/user/1017b62c-6b61-4fcd-b663-5c81feab6524'
[
{
"token": "ca66618d-43c7-4c74-aa30-5b2fedfc676f",
"createdTime": 00,
"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": 00,
"lastModifiedTime": 00,
"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": 00,
"lastModifiedTime": 00,
"type": "program",
"active": true,
"name": "funding_source_bank",
"defaultAccount": false
}
},
"fundingSourceToken": "**********ea62",
"userToken": "1017b62c-6b61-4fcd-b663-5c81feab6524",
"currencyCode": "USD"
},
"duration": 127,
"userTransactionTime": 00,
"settlementDate": 00,
"requestAmount": 5.00,
"amount": 5.00,
"issuerReceivedTime": 84,
"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
}
}
]
“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