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.

Payment Flow

    sequenceDiagram
	participant P as Patron
	participant T as Terminal
	participant C as Centrapay
	participant A as Asset Provider
	T->>C: Create Payment Request
	P->>T: Scan QR Code
	P->>+C: Get Payment Request
	C-->>-P: Return Payment Request
	note over P: Display Payment Details
	par While not successful
		loop
			T->>C: Poll for Payment Confirmation
		end
		P->>+C: Pay Payment Request
		C->>+A: Call Pay with Transaction Attempt
		A-->>-C: Success/Failure
		C-->>-P: Success/Failure
		note over P: Display Payment Result
	end
	note over T: Display Payment Result
  

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 required.
  • 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
Namespace StringA name used for uniquely identifying the asset as a payment method.centrapay-example
Description StringA short human readable description.Centrapay Money
Supported Currencies ArrayA 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, changes, and API spec enhancements. You may choose to provide us with multiple emails for different priorities of communication.

Settlement

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

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.

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.

Requests will time out after 60 seconds if no response is received.

Centrapay will retry a transaction attempt once, immediately, when an HTTP response status code ≥ 500 is thrown, when the connection is aborted, or when no response is received. If that retry also fails, the transaction attempt is left unresolved and will later be checked with the Get Transaction Endpoint — see Unresolved Transactions .

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).
authorizationStringrequiredThis field is treated as opaque by Centrapay and is passed through to the third-party provider without interpretation. Its contents may include a user authorization token, asset or account identifiers, or any other information required by the provider to identify the user and approve the payment.
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.
merchantLocation Location optionalLocation of 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.
refundBefore Timestamp optionalThe latest time at which a refund can be initiated.
paymentRequestIdStringoptionalA unique identifier for the original Payment request.
paymentTransactionIdStringrequiredRefund requests only. The transactionId of the original payment being refunded.
idempotencyKeyStringdeprecatedSent on Pay and Refund requests with the same value as transactionId. This field is deprecated use transactionId for idempotency.

merchantCategoryCode and merchantLocation are only present on Pay requests — they are not part of the response, and are not returned by the Get Transaction Endpoint.

Statuses

NameDescription
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 and Refund must resolve synchronously with a successful or failed status.

Pay Endpoint

This endpoint is used to initiate payment. It must resolve synchronously with a status of successful or failed.

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",
"merchantLocation": {
"lat": "-36.8483579",
"lng": "174.7725834",
"city": "Auckland",
"postCode": "1010",
"country": "NZ",
"street": "17 South Street"
},
"paymentRequestId": "LTsofbYSldsp35psd",
"idempotencyKey": "UttDGTHjr7DgKoKwWpTKb",
"transactionId": "UttDGTHjr7DgKoKwWpTKb"
}'
Response
{
"currency": "NZD",
"amount": "1000",
"authorization": "WRhAxxWpTKb5U7pXyxQjjY",
"merchantName": "Centrapay Cafe",
"merchantId": "MhocUmpxxmgdHjr7DgKoKw",
"merchantCategoryCode": "2481",
"merchantLocation": {
"lat": "-36.8483579",
"lng": "174.7725834",
"city": "Auckland",
"postCode": "1010",
"country": "NZ",
"street": "17 South Street"
},
"paymentRequestId": "LTsofbYSldsp35psd",
"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",
"idempotencyKey": "dDHF8743fVzdsg84f6",
"transactionId": "dDHF8743fVzdsg84f6"
}'
Response
{
"currency": "NZD",
"amount": "1000",
"authorization": "WRhAxxWpTKb5U7pXyxQjjY",
"merchantName": "Centrapay Cafe",
"merchantId": "MhocUmpxxmgdHjr7DgKoKw",
"transactionId": "HFCD73hsbJHBDd9gs3t",
"type": "refund",
"status": "successful"
}

Get Transaction Endpoint

This endpoint is used to resolve a Transaction Attempt when its outcome is unknown to Centrapay — for example, to resolve a Pay call that could not be confirmed (see Unresolved Transactions ).

Centrapay may call this endpoint 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 -X GET 'https://your.endpoint/get?transactionId=UttDGTHjr7DgKoKwWpTKb' \
-H 'authorization: ${jwt}'
Response
{
"currency": "NZD",
"amount": "1000",
"authorization": "WRhAxxWpTKb5U7pXyxQjjY",
"merchantName": "Centrapay Cafe",
"merchantId": "MhocUmpxxmgdHjr7DgKoKw",
"transactionId": "UttDGTHjr7DgKoKwWpTKb",
"type": "payment",
"status": "successful",
"refundable": true,
"refundBefore": "2023-06-09T00:52:22.468Z"
}

Unresolved Transactions

When Centrapay can't confirm the outcome of a Pay call — for example, if your endpoint times out or returns an unexpected error. When this happens we check the outcome with the Get Transaction Endpoint at a later stage to determine the result.

  • If the Get Transaction Endpoint reports the transaction as successful, We finalize the Payment Request as successful if the Payment Request isn't in a final state.
  • If the Payment Request has already reached a final state (cancelled, expired or paid) by the time this check happens, since the Payment Request is no longer payable, Centrapay will call your Refund endpoint to reverse it.
  • Otherwise, the Payment Request is marked as failed on Centrapay's side. Your integration is not notified of this separately — you should still expect the Get Transaction Endpoint to be called for a transactionId that never completes.
    sequenceDiagram
	participant C as Centrapay
	participant A as Asset Provider
	C->>A: Call Pay with Transaction Attempt
	A--xC: Timeout / 5xx / no response
	C->>A: Retry Pay with Transaction Attempt
	A--xC: Timeout / 5xx / no response
	note over C, A: Payment Request reaches final state
	C->>+A: Get Transaction
	A-->>-C: status
	alt status is successful
		note over C: Payment is marked as successful
		C->>+A: Refund
		A-->>-C: Success/Failure
	else status is failed or unknown
		note over C: Payment is marked failed
	end
  

Certification

Once you have defined your Payment Method , implemented the Uplink endpoints for both main and test liveness, and provided your Contact Details , you must complete an integration certification against the Integration Checklist , including the Third-Party Asset Provider Extensions applicable to your asset, before merchants can accept your asset as a payment method.

When you're ready or need assistance/have questions integrating, please contact the Centrapay Engineering team at integrations@centrapay.com .