Managing payments

Authorize transactions, and process recurring payments. An overview of the Payments API.

A unified transaction lifecycle

The Payments API enables you to manage all payments, through a unified transaction lifecycle.

Transaction lifecycle


Transactions go through a series of statuses when progressing from authorization to final settlement.
StatusDescription
Pending
An authorization request has been received and a payment has been created.
Authorized
The transaction has been authorized by the payment processor and can proceed to capture.
Declined
This transaction was declined by the payment processor, either at a gateway or acquirer/issuer level. We normalize payment processor decline codes for card payments. See the lastPaymentError object in your response payload for more details.
Failed
The chosen payment processor was not able to process the payment, it may be down or otherwise unavailable. See the lastPaymentError object in your response payload for more details.
Settling
Funds have been captured and are due to be settled.
Cancelled
The transaction has been cancelled. This involves us sending a "void" request to the payment processor.
Partially Settled
Funds have been partially captured for this transaction and will soon be settled. Not all payment processors support partial capture.
Settled
Funds have been settled. This marks the end of the transaction lifecycle.

Integrating with the Payments API

Before accepting payments, we'll show you how to authenticate against our API and maintain idempotency.

Environments

You can use our dedicated Sandbox environment to test your payments integration. Visit our Test your integration page for more information.

API Authentication

Authentication is handled via the X-Api-Key header. Create and manage API keys and scopes from the Dashboard in the Developers area.

Idempotency

Idempotency is the concept that a single action should only ever have one effect. Our Payments API supports idempotency by providing an X-Idempotency-Key header. Multiple requests to the same endpoint using the same idempotency key will be processed only once. The value you pick for this header should be unique to the payment being processed.

We should mention that sending this header is optional. You don't have to send it, but we think you can agree that it's powerful feature to use.

This example demonstrates a valid, authenticated, idempotent request.

1
2
3
4
5
6
7
8
9
10
11
12
curl --location --request \
POST 'https://api.sandbox.primer.io/payments' \
--header 'X-Api-Key: <YOUR_API_KEY>' \
--header 'X-Idempotency-Key: mqum0y1m8n' \
--data '{
"amount": 700,
"currencyCode": "EUR",
"orderId": "order-123",
"paymentInstrument": {
"token": "<YOUR_TOKEN>",
},
}'
curl

Response payloads

For all valid requests to the Payments API, Primer will respond with a 200. The response will always be a payment object including the following attributes:

AttributesDescription
idThe unique payment ID. You can use this ID to retrieve the payment details, or perform downstream operations.
dateThe date and time at which the payment was created in UTC format.
statusThe current status. See the payment status table above for more information.
orderIdYour reference for the payment.
currencyCodeThe 3 digit currency code in ISO 4217 format. e.g. use USD for US dollars.
amountThe amount of the payment at a given instance, in minor units. e.g. $7 would show as 700.

If the payment status is
Authorized
, this will equal the amount that has been authorized. If the payment status is
Settling
or
Settled
, this field will equal the total amount requested to be captured.
amountAuthorizedThe amount that has been authorized. If authorization could not occur, for example if the payment was declined, this value will be set to zero.
amountCapturedThe amount captured. If no capture was performed, this value will be set to zero. If one or more partial captures were performed, this value will be a sum of all partial capture amounts.
amountRefundedThe amount refunded. If no refund was performed, this value will be set to zero. If one or more partial refunds were performed, this value will be a sum of all partial refund amounts.
paymentInstrumentThe payment method details used to process this payment, including the token used for authorization.
vaultedPaymentInstrumentThe vaulted payment method details (if any) generated during this payment.
Optional
Only if a token was vaulted during payment creation, otherwise null.
lastPaymentErrorIf the payment has a declined or failed status, check this field for more information. See below for more details on how to handle declined payments.
Optional
Only if the status is
Declined
or
Failed
.
customerThe customer details associated with the payment.
transactionsA list summarizing the transactions that occurred while processing the payment.

Each list item will have the key details for a transaction, including the transaction id, status, and paymentError if any.

For example, a refund is a separate transaction and so will appear in this transactions list if a refund was performed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
{
"id": "slklmjh4", // The payment ID
"date": "2021-01-26T14:24:15.540899",
"status": "DECLINED",
"orderId": "YOUR_CUSTOMER_ORDER_ID",
"currencyCode": "GBP",
"amount": 800,
"amountAuthorized": 1000,
"amountCaptured": 800,
"amountRefunded": 300,
"paymentInstrument": {
"token": "hXIHB_WfR2-M6PM13JDmQnwxNjAxMTE3NTA4",
"analyticsId": "0gUl6gf1VgmsJQSM4wXShzNX",
"tokenType": "SINGLE_USE",
"paymentInstrumentType": "PAYMENT_CARD",
"paymentInstrumentData": {
"last4Digits": "1111",
"expirationMonth": "03",
"expirationYear": "2030",
"cardholderName": "MR FOO BAR",
"network": "Visa",
},
"threeDSecureAuthentication": null
},
"vaultedPaymentInstrument": null,
"lastPaymentError": {
"date": "2021-01-26T14:27:15.540899",
"type": "ISSUER_DECLINE",
"declineCode": "DO_NOT_HONOR"
"declineType": "SOFT",
"processorMessage": "Transaction cannot be honored."
},
"customer": {
"id": "customer-123",
},
"transactions": [
Show all 49 lines ↓
json

Managing declined and failed payments

If your payment status is

Declined
, or
Failed
, commonly due to payment processors being down, the lastPaymentError will give details of the reason.

In the example above, the payment has a status of

Declined
. To see why, we can see on the lastPaymentError that the transaction could not be honored. Primer normalizes decline codes for card payments across all payment processors.

1
2
3
4
5
6
7
8
9
{
"lastPaymentError": {
"date": "2021-01-26T14:27:15.540899",
"type": "ISSUER_DECLINE",
"declineCode": "DO_NOT_HONOR",
"declineType": "SOFT",
"processorMessage": "Transaction cannot be honored."
}
}
json

In this case it was a soft decline, and so authorization can be retried with the same or another payment processor.

Managing non-200 status codes

If you receive a 4XX, then there has been an API validation error. In this case, the response payload will contain an error object with the following attributes:

AttributesDescription
errorIdThe error code returned from Primer indicating the reason for rejection.
descriptionA human readable description of the error.
diagnosticsIdA unique identifier of the request. If you're having trouble getting this endpoint to work, please quote this identifier when contacting your account manager. This will allow us to provide you with the help you need.
validationErrorsA list of validation errors (if applicable). These should help you to diagnose exactly what was malformed in the request.

For more on errors, see our API Reference.

An example error response:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"error": {
"errorId": "RequestValidationError",
"description": "We were unable to validate your request, please check your payload against https://primer.io/docs/api",
"diagnosticsId": "11738938659721344025",
"validationErrors": [
{
"model": "PaymentCreationAPIRequest",
"errors": [
{
"path": "$.amount",
"description": "Missing required field"
}
]
}
]
}
}
json

Accepting payments

Now that you can create authenticated, idempotent requests, let's start processing payments. The Payments API is available at /payments.

Creating a payment

Upon creation, a payment will automatically be sent for authorization, using the authorization Workflow in your Dashboard. On successful authorization, the payment status will be set to

Authorized
. Below are the payment creation request fields.

ParametersDescription
amountThe amount you would like to charge the customer, in minor units. e.g. for $7, use 700.

Some currencies, such as Japanese Yen, do not have minor units. In this case you should use the value as it is, without any formatting. For example for ¥100, use 100.
currencyCodeThe 3 digit currency code in ISO 4217 format. e.g. use USD for US dollars.
orderIdYour reference for the payment.
paymentInstrumentThe payment token and vaulting option to use for this payment. See the paymentInstrument parameters table below for more detail.
customerThe customer details associated with the payment.

This attribute is only required if paymentInstrument.vault is set to ON_SUCCESS. For more information on available customer details, see our Payments API reference.
Optional
optionsVarious optional settings you can have for your payment. See recurring payments for the recurring options available.
Optional

Below are the paymentInstrument parameters:

ParametersDescription
tokenA one-time use token generated from our checkout libraries, or a recurring token retrieved from the Vault API. e.g. hXIHB_WfR2-M6PM13JDmQnwxNjAxMTE3NTA4. One-time use tokens expire after 15 minutes.
vaultVault options to store payment methods (if supported). Can be one of the following options:

ON_SUCCESS on successful authorization, the token will be vaulted.

NO_VAULT do not vault this token.
Optional
If no vault value is specified, this field defaults to NO_VAULT.

An example payment creation request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
curl --location --request \
POST 'https://api.sandbox.primer.io/payments' \
--header 'X-Api-Key: <YOUR_API_KEY>' \
--header 'X-Idempotency-Key: mqum0y1m8n' \
--data '{
"amount": 700,
"currencyCode": "EUR",
"orderId": "order-123",
"paymentInstrument": {
"token": "<YOUR_TOKEN>", # one-time use, or recurring token
"vault": "ON_SUCCESS",
},
"customer": {
"id": "customer-123",
},
}'
curl

Recurring payments

Primer enables you to flag recurring or subscriptions payments. This is handled by the options.recurring.transactionType field. If you successfully vault a single-use token on payment creation, then there's no need to set a value for this field and it will be flagged as FIRST_PAYMENT. Otherwise, see the table below for all possible values.

ParametersDescription
transactionTypeCan be one of the following options:

FIRST_PAYMENT a customer-initiated payment which is the first in a series of recurring payments or subscription, or a card on file scenario.

ECOMMERCE a customer-initiated payment using stored payment details where the cardholder is present.

SUBSCRIPTION a merchant-initiated payment as part of a series of payments on a fixed schedule and a set amount.

UNSCHEDULED A merchant-initiated payment using stored payment details with no fixed schedule or amount.
previousNetworkTransactionIdThe Network ID of the previous transaction in the recurring sequence.
Optional

An example payment creation request which includes a recurring flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
curl --location --request \
POST 'https://api.sandbox.primer.io/payments' \
--header 'X-Api-Key: <YOUR_API_KEY>' \
--header 'X-Idempotency-Key: mqum0y1m8n' \
--data '{
"amount": 700,
"currencyCode": "EUR",
"orderId": "order-123",
"paymentInstrument": {
"token": "<YOUR_TOKEN>", # one-time use, or recurring token
"vault": "ON_SUCCESS",
},
"customer": {
"id": "customer-123",
},
"options": {
"recurring": {
"transactionType": "FIRST_PAYMENT",
"previousNetworkTransactionId": "AJHGHJ",
},
},
}'
curl

Capturing a payment

If you have successfully authorized a transaction, you can now fully capture, or partially capture funds from the authorized payment, depending on whether your selected payment processor supports it. The transaction will be updated to

Settled
or
Settling
, depending on the payment method type. The payload sent in this capture request is completely optional. If you don't send a payload with the capture request, the full amount that was authorized will be sent for capture. Below are the available payload attributes, which give you more granular control when capturing funds, if you require it.

ParametersDescription
amountThe amount you would like to capture, in minor units. The authorized currency is assumed. e.g. for $7, use 700.
Optional
if no amount is specified it defaults to the full amount.
finalIndicates whether the capture request is the final capture request.
Optional
Defaults to true.
1
2
3
4
5
6
7
8
curl --location --request \
POST 'https://api.sandbox.primer.io/payments/<YOUR-PAYMENT-ID>/capture' \
--header 'X-Api-Key: <YOUR_API_KEY>' \
--header 'X-Idempotency-Key: mqum0y1m8n' \
--data '{
"amount": 700,
"final": false,
}'
curl

Managing Payments

Cancelling a payment

Provided the payment has not reached 

Settled
status, Primer will send a "void" request to the payment processor, thereby cancelling the payment and releasing the hold on customer funds. Upon success, the payment will transition to 
Cancelled
.

Example cancellation request:

1
2
3
4
curl --location --request \
POST 'https://api.sandbox.primer.io/payments/<YOUR-PAYMENT-ID>/cancel' \
--header 'X-Api-Key: <YOUR_API_KEY>' \
--header 'X-Idempotency-Key: mqum0y1m8n' \
curl

Refunding payments

Refunds

By default, this request will refund the full amount. Optionally, pass in a lesser amount for a partial refund.

ParametersDescription
amountThe amount you would like to refund the customer, in minor units. e.g. for $7, use 700.
Optional
Defaults to captured amount.
orderIdYour reference for the transaction.
Optional
by default this will be set to the original orderId given on payment creation.

If you'd like to use a different value, for example suffixing the orderId with a reference like order-123-refund just set this as the orderId in the refund request.
1
2
3
4
5
6
7
8
curl --location --request \
POST 'https://api.sandbox.primer.io/payments/<YOUR-PAYMENT-ID>/refund' \
--header 'X-Api-Key: <YOUR_API_KEY>' \
--header 'X-Idempotency-Key: mqum0y1m8n' \
--data '{
"amount": 200, # supply this parameter for a partial refund, otherwise defaults to full amount
"orderId": "order-123",
}'
curl

Retrieving a payment

When getting your payments from our API, we'll return a payment object or list of summarized payment objects.

To retrieve a list of payments in your Primer account:

1
2
3
curl --location --request \
GET 'https://api.sandbox.primer.io/payments' \
--header 'X-Api-Key: <YOUR_API_KEY>' \
curl

To get a specific payment by it's payment ID:

1
2
3
curl --location --request \
GET 'https://api.sandbox.primer.io/payments/<YOUR-PAYMENT-ID>' \
--header 'X-Api-Key: <YOUR_API_KEY>' \
curl