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

The Payments API enables you to manage all payments, through a unified transaction lifecycle.
Transactions go through a series of statuses when progressing from authorization to final settlement.
Status | Description |
---|---|
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. |
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.
Environment | API | Dashboard |
---|---|---|
Sandbox | api.sandbox.primer.io | sandbox-dashboard.primer.io |
Live | api.primer.io | dashboard.primer.io |
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.
123456789101112
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>",},}'
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:
Attributes | Description |
---|---|
id | The unique payment ID. You can use this ID to retrieve the payment details, or perform downstream operations. |
date | The date and time at which the payment was created in UTC format. |
status | The current status. See the payment status table above for more information. |
orderId | Your reference for the payment. |
currencyCode | The 3 digit currency code in ISO 4217 format. e.g. use USD for US dollars. |
amount | The 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. |
amountAuthorized | The amount that has been authorized. If authorization could not occur, for example if the payment was declined, this value will be set to zero. |
amountCaptured | The 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. |
amountRefunded | The 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. |
paymentInstrument | The payment method details used to process this payment, including the token used for authorization. |
vaultedPaymentInstrument | The vaulted payment method details (if any) generated during this payment. Optional Only if a token was vaulted during payment creation, otherwise null . |
lastPaymentError | If 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 . |
customer | The customer details associated with the payment. |
transactions | A 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. |
123456789101112131415161718192021222324252627282930313233343536
{"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": [
Managing declined and failed payments
If your payment status is
lastPaymentError
will give details of the reason.In the example above, the payment has a status
of
lastPaymentError
that the transaction could not be honored. Primer normalizes decline codes for card payments across all payment processors.123456789
{"lastPaymentError": {"date": "2021-01-26T14:27:15.540899","type": "ISSUER_DECLINE","declineCode": "DO_NOT_HONOR","declineType": "SOFT","processorMessage": "Transaction cannot be honored."}}
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:
Attributes | Description |
---|---|
errorId | The error code returned from Primer indicating the reason for rejection. |
description | A human readable description of the error. |
diagnosticsId | A 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. |
validationErrors | A 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:
123456789101112131415161718
{"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"}]}]}}
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
Parameters | Description |
---|---|
amount | The 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 . |
currencyCode | The 3 digit currency code in ISO 4217 format. e.g. use USD for US dollars. |
orderId | Your reference for the payment. |
paymentInstrument | The payment token and vaulting option to use for this payment. See the paymentInstrument parameters table below for more detail. |
customer | The 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 |
options | Various optional settings you can have for your payment. See recurring payments for the recurring options available. Optional |
Below are the paymentInstrument
parameters:
Parameters | Description |
---|---|
token | A 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. |
vault | Vault 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:
12345678910111213141516
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",},}'
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.
Parameters | Description |
---|---|
transactionType | Can 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. |
previousNetworkTransactionId | The Network ID of the previous transaction in the recurring sequence. Optional |
An example payment creation request which includes a recurring flag:
12345678910111213141516171819202122
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",},},}'
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
Parameters | Description |
---|---|
amount | The 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. |
final | Indicates whether the capture request is the final capture request. Optional Defaults to true . |
12345678
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,}'
Cancelling a payment
Provided the payment has not reached
Example cancellation request:
1234
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' \
Refunding payments
By default, this request will refund the full amount. Optionally, pass in a lesser amount for a partial refund.
Parameters | Description |
---|---|
amount | The amount you would like to refund the customer, in minor units. e.g. for $7, use 700 . Optional Defaults to captured amount. |
orderId | Your 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. |
12345678
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",}'
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:
123
curl --location --request \GET 'https://api.sandbox.primer.io/payments' \--header 'X-Api-Key: <YOUR_API_KEY>' \
To get a specific payment by it's payment ID:
123
curl --location --request \GET 'https://api.sandbox.primer.io/payments/<YOUR-PAYMENT-ID>' \--header 'X-Api-Key: <YOUR_API_KEY>' \