Guides

Third-party Asset

By integrating with Centrapay as a third-party asset provider, you can take advantage of our connections with terminals, point-of-sale systems, and merchant networks, thereby expanding the reach of your digital asset to a wider audience.

Once you have defined your asset as a Payment Method with Centrapay and implemented the required Uplink APIs, consumers will be able to spend your digital asset using one of our payment flows wherever merchants accept your digital asset.

Defining a Payment Method

We require a way of identifying your asset in order to route payments to the correct asset provider. You must define a payment method namespace, description, and list of supported currencies.

Namespace

The namespace is a unique string to identify your asset. Your namespace must conform to the following properties:

  • Alphanumeric characters.
  • No punctuation except for -.
  • Must not end in reserved keywords test or main.
  • Must not conflict with existing names.

Your payment method namespace may have a connection to your brand or product. Centrapay reserves the right to decline a requested namespace.

Valid examples are centrapay-example or bitcoin.

Liveness

The liveness of a payment method can be either main or test. This can be used to accept test assets through Centrapay.

  • Test and main assets must share the same namespace but end in test or main. For example, centrapay.main and centrapay.test.
  • Integrating a test asset is optional.
  • A set of uplink APIs must be provided for both main and test.
  • Centrapay Merchants need a test flag in order to accept test assets.

Description

Merchants may look at reports or receipts of past transactions. If there was a payment or refund with your asset against their Payment Request, the description of your asset will be displayed.

Your description cannot exceed 15 characters.

An example description is Centrapay NZD, Bitcoin, or Ethereum.

Supported Currencies

Payment Requests have a value determined by the currency a Merchant accepts. You should supply a finite list of three-letter ISO currency codes that is supported by your asset.

Example Definition

FieldTypeDescriptionExamples
NamespaceStringA name used for uniquely identifying the asset as a payment method.centrapay-example
DescriptionStringA short human readable description.Centrapay Money
Supported CurrenciesArrayA list of supported currency codes.NZD, USD

To integrate with Centrapay payments, you must implement the Uplink endpoints. An Uplink is a strategy for performing payment or refund transactions for your Asset. These endpoints will be used in the lifecycle of a payment.

Requirements specific to an endpoint will be stated in the API specification.

Integrations are required to tolerate any unknown fields. Over time as new functionality is added, we reserve the right to add fields to API models. You can expect us to notify you of modelling changes.

Contact Details

Centrapay requires at least one email address to notify you of integration failures or changes. You may choose to provide us with multiple emails for different priorities of communication.

Protocol

Each endpoint must use the HTTPS protocol.

URL

There are no restrictions regarding the provided URLs, so long as they adhere to the specification for the HTTP method, query parameters, request body, response body, and error codes.

Authentication

Endpoints are authenticated against requests using a JSON Web Token (JWT) issued by Centrapay. The JWT will be sent through the Authorization header in the HTTP Request.

Verify the JWT using the public key returned for the JSON Web Key Set (JWKS) endpoint with the matching kid.

Keys used for signing JWTs may be rotated without warning, therefore it is required that signatures are resolved dynamically against the JWKS endpoint. You may choose to cache the result, but respect the directives in the http cache-control headers.

JWKS Request
curl https://auth.centrapay.com/api/.well-known/jwks.json
JWKS Response
{
"keys": [
{
"kty": "EC",
"x": "t-vW2fE0mLLmdzJtYrz7J9q_yEXlgmIjCXdv3VNvYfQ",
"y": "7GgTuTyTYmg95fZQ_D8xELt9Xj7DhvNZg1bqONPnYC4",
"crv": "P-256",
"kid": "20191127-07baec395",
"use": "sig",
"alg": "ES256"
}
]
}

To verify a request start by decoding the JWT.

Decoded JWT Payload
{
"iat": "1684105185",
"exp": "1684105485",
"aud": "https://your.endpoint",
"request_body_sha256": "b9195bf41bf0e38ab0ab44e7ef5b9af5cb0fe2ece8dee5d112d7485bf4ef0007",
}
FieldDescription
iatAn Unix timestamp of the request’s creation.
expAn Unix timestamp that the request is valid until. Set to 5 minutes after iat.
audThe Uplink API URL belonging to the intended recipient of the request.
request_body_sha256A hash of the request payload, created using the SHA256 algorithm.

Use the decoded JWT fields to validate the following:

Exp is provided as a default expiry. Alternatively, use iat + your own expiry window to determine if the JWT has expired.

Assert that the audience is correct by checking that aud is equal to the base URL of your Uplink API.

The request_body_sha256 property should be used to verify that the request payload has not been tampered with. This should be done by hashing the received request payload using the SHA256 algorithm and checking for equality with request_body_sha256 as shown below. If the request does not have a payload then the request_body_sha256 field will not be present in the decoded JWT.

Example
const crypto = require("crypto");
const decodedJwt = {
request_body_sha256: "d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
};
const payload = {
vader: "I am your father",
luke: "That's not true, that's impossible",
};
const jsonStringPayload = JSON.stringify(payload);
const hashedPayload = crypto.createHash("sha256").update(jsonStringPayload).digest("hex");
if (decodedJwt["request_body_sha256"] != hashedPayload) {
throw new Error("Payload hashes do not match");
}

If any of the above assertions are not met the request must be rejected.

Settlement

It is the responsibility of the Asset Integrator to settle funds with a merchant.

Transaction Idempotency

The transactionId is used for idempotency for HTTP POST requests. Centrapay does not guarantee endpoints will be called only once per transaction. It is expected that you will enforce transaction idempotency. In the event that the idempotency is violated, Centrapay expects a 200 OK response as described in the endpoint specification.

Errors

A 2XX response should be returned from all endpoints for both successful and failed transaction attempts unless an unexpected error has occurred in your system.

For failed transaction attempts, the response body should contain a failure reason. These are defined by each endpoint specification.

Centrapay will retry a transaction attempt immediately when an HTTP response status code ≥ 500 is thrown. The Get Transaction Endpoint will be used to resolve transaction attempts if an unexpected error is thrown after retrying.

Transaction Attempt Model

Integrations are required to tolerate any unknown fields. This is so we can maintain forwards compatibility with endpoints as we add to the API specification without versioning.

NameTypeNecessityDescription
currencyStringrequiredThe three letter ISO currency code for the payment.
amountStringrequiredThe value required to pay in the smallest denomination for the supported currency (e.g. cents).
authorizationStringrequiredA reference to the asset of the party paying the Payment Request.
merchantNameStringrequiredThe name of the merchant who created the Payment Request.
merchantIdStringrequiredYour identifier for the merchant receiving payment.
merchantCategoryCodeStringoptionalCategory code for the merchant who created the Payment Request.
transactionIdStringrequiredA unique ID for the transaction in Centrapay’s system. Also used for idempotency
statusStringrequiredThe status of the asset transaction. See possible status values.
typeStringrequiredThe type of transaction. Possible values are payment or refund.
failureReasonStringoptionalRequired if the status is failed. See possible failure reasons under each API below.
refundableBooleanoptionalRequired if type is payment and status is successful. A flag indicating whether a payment is refundable.
refundBeforeTimestampoptionalThe latest time at which a refund can be initiated.
idempotencyKeyStringdeprecatedA unique value that can be used to recognize subsequent retries of the same request.

Statuses

NameDescription
pendingThe transaction is processing.
successfulThe transaction has been successfully processed.
failedThe transaction has been unable to be successfully processed. A failure reason is expected to be provided when status is failed.

Pay Endpoint

This endpoint is used to initiate payment. If payment is completed asynchronously, the status will be pending and the get transaction endpoint will be used to poll until the status is successful or failed.

Loading Diagram...

Request
curl -X POST https://your.endpoint/pay \
-H "Authorization: ${jwt}" \
-H "Content-Type: application/json" \
-d '{
"currency": "NZD",
"amount": "1000",
"authorization": "WRhAxxWpTKb5U7pXyxQjjY",
"merchantName": "Centrapay Cafe",
"merchantId": "MhocUmpxxmgdHjr7DgKoKw",
"merchantCategoryCode": "2481",
"idempotencyKey": "UttDGTHjr7DgKoKwWpTKb"
"transactionId": "UttDGTHjr7DgKoKwWpTKb"
}'
Response
{
"currency": "NZD",
"amount": "1000",
"authorization": "WRhAxxWpTKb5U7pXyxQjjY",
"merchantName": "Centrapay Cafe",
"merchantId": "MhocUmpxxmgdHjr7DgKoKw",
"merchantCategoryCode": "2481",
"transactionId": "UttDGTHjr7DgKoKwWpTKb",
"type": "payment",
"status": "successful",
"refundable": true,
"refundBefore": "2023-06-09T00:52:22.468Z"
}

Failure Reasons

NameDescription
INSUFFICIENT_ASSET_VALUEThe patron does not have the sufficient asset amount to complete the transaction.
ASSET_REDEMPTION_DENIEDThe asset redemption has been unsuccessful due to the provided payment parameters e.g. currency not supported or unknown authorization.

Refund Endpoint

This endpoint is used to refund a Payment Request with status paid. Refunds must be synchronous i.e. the status must be successful or failed.

It is expected that partial refunds are supported.

Request
curl -X POST https://your.endpoint/refund \
-H "Authorization: ${jwt}" \
-H "Content-Type: application/json" \
-d '{
"currency": "NZD",
"amount": "1000",
"paymentTransactionId": "HFCD73hsbJHBDd9gs3t",
"transactionId": "dDHF8743fVzdsg84f6"
"idempotencyKey": "dDHF8743fVzdsg84f6"
}'
Response
{
"currency": "NZD",
"amount": "1000",
"authorization": "WRhAxxWpTKb5U7pXyxQjjY",
"merchantName": "Centrapay Cafe",
"merchantId": "MhocUmpxxmgdHjr7DgKoKw",
"merchantCategoryCode": "2481",
"transactionId": "HFCD73hsbJHBDd9gs3t",
"type": "refund",
"status": "successful"
}

Failure Reasons

NameDescription
PARTIAL_REFUNDS_NOT_ALLOWEDThe Amount provided is less than the value of the payment and partial refunds are not allowed.

Cancel Endpoint

After initiating a transaction with the pay endpoint, the status may be pending. During this time something may have happened to prevent the payment request from being paid (e.g. payment request timeout or merchant network issues).

The Cancel endpoint can be used to stop processing a transaction that has status pending. If the Cancel endpoint is called for a transaction that already has successful or failed, it is expected that an error will be thrown.

Once the Cancel endpoint is called, the transaction status should be failed. This is a synchronous call and cannot return status pending. The reason for cancellation will be passed through as a failureReason.

Example Request

Request
curl -X POST https://your.endpoint/cancel \
-H "Authorization: ${jwt}" \
-H "Content-Type: application/json" \
-d '{
"transactionId": "UttDGTHjr7DgKoKwWpTKb",
"failureReason": "PAYMENT_REQUEST_EXPIRED"
}'
Response
{
"currency": "NZD",
"amount": "1000",
"authorization": "WRhAxxWpTKb5U7pXyxQjjY",
"merchantName": "Centrapay Cafe",
"merchantId": "MhocUmpxxmgdHjr7DgKoKw",
"merchantCategoryCode": "2481",
"transactionId": "UttDGTHjr7DgKoKwWpTKb",
"type": "payment",
"status": "failed",
"failureReason": "PAYMENT_REQUEST_EXPIRED"
}

Failure Reasons

NameDescription
CANCELLED_BY_MERCHANTThe merchant has initiated a cancel of the payment request.
PAYMENT_REQUEST_EXPIREDThe payment request has expired before being paid.

Get Transaction Endpoint

This endpoint is used resolve a Transaction Attempt when the transaction is pending or has an unknown state.

Polling will continue until either the transaction attempt status is successful or failed, or the Centrapay Payment Request is no longer payable (e.g. it has expired).

You should return a 2XX response with an empty body {} if the transaction does not exist in your system.

Request
curl https://your.endpoint/get?transactionId=UttDGTHjr7DgKoKwWpTKb
-H "Authorization: ${jwt}"
Response
{
"currency": "NZD",
"amount": "1000",
"authorization": "WRhAxxWpTKb5U7pXyxQjjY",
"merchantName": "Centrapay Cafe",
"merchantId": "MhocUmpxxmgdHjr7DgKoKw",
"merchantCategoryCode": "2481",
"transactionId": "UttDGTHjr7DgKoKwWpTKb",
"type": "payment",
"status": "successful",
"refundable": "true",
"refundBefore": "2023-06-09T00:52:22.468Z"
}

Implementation Checklist

To enable merchants to accept your asset as a payment method, you must complete an integration certification. When you’re ready or need assistance/have questions integrating, please contact the Centrapay Engineering team at integrations@centrapay.com.

  • Payment Method
    • Namespace
    • Description
    • Supported Currencies
  • Uplink urls for main liveness payment method
  • (Optional) Uplink urls for test liveness payment method
  • An email address to contact for API spec enhancements