Pay Integration Guide

Power your checkout with crypto and stablecoins - end to end integration guide

Introduction

The Pay product allows payment service providers or merchants to offer stablecoins or crypto as a payment option at checkout.

Definitions

TermDefinition
PlatformThe company that's in contract with Zero Hash and directly interacts with Zero Hash's API's or SDK's
MerchantAn optional participant in the flow, a non-natural person (typically a business) that is a customer of the Platform. This is the core front-end that the Shopper interacts with. The Merchant may also be the Platform
ShopperThe natural person that pays for the good or service

High Level Flow

  1. Submit Merchant
  2. Query Merchant
  3. Fund Refund Account
  4. Submit Shopper
  5. Query Shopper
  6. Initiate Payment
  7. Webhooks Sent
  8. Email Receipts Sent
  9. Query Payments
  10. Settlement

1. Submit Merchant

Merchant Information

ℹ️

This section is only applicable if the Platform is not also acting as the Merchant. Typically Payment Service Providers (PSP's) who acquire merchants will need to submit Merchants to Zero Hash.

Begin by submitting the Merchant via POST /participants/entity/new

{
    "platform_code": "PLAT01",
    "entity_name": "Merchant XYZ",
    "legal_name": "Merchant XYZ Inc.",
    "contact_number": "15553765432",
    "website": "www.merchant.com",
    "date_established": "2018-01-15",
    "entity_type": "llc",
    "address_one": "1 Main St.",
    "address_two": "Suite 1000",
    "city": "Chicago",
    "postal_code": "12345",
    "jurisdiction_code": "US-IL",
    "tax_id": "883987654",
    "id_issuing_authority": "United States",
    "risk_rating": "low",
    "risk_vendor": "passbase",
    "sanction_screening": "pass",
    "sanction_screening_timestamp":1677252628000,
    "metadata":{},
    "signed_timestamp":1677252629000,
    "submitter_email": "[email protected]",
    "submitter_first_name": "Josh" 
    "submitter_last_name": "Doe"  
    "submitter_title": "Senior Legal Council"
    "control_persons":[
      {
        "name": "Joe Doe",
        "email": "[email protected]",
        "address_one": "1 South St.",
        "address_two": "Suite 2000",
        "city": "Chicago",
        "postal_code": "12345",
        "jurisdiction_code": "US-IL",
        "date_of_birth": "1980-01-30",  
        "citizenship_code": "US", 
        "tax_id": "123456789",
        "id_number_type": "us_passport",
        "id_number": "332211200",
        "kyc": "pass",
        "kyc_timestamp": 1630623005000,
        "sanction_screening":"pass",
        "sanction_screening_timestamp":1677252628000,
        "control_person": 1
      }
    ],
    "beneficial_owners":[
      {
        "name": "Jane Doe Jr",
        "beneficial_owner":1,
        "email": "[email protected]",
        "address_one": "1 North St.",
        "address_two": "Suite 3000",
        "city": "Chicago",
        "postal_code": "12345",
        "jurisdiction_code": "US-IL",
        "date_of_birth": "1980-01-30",
        "citizenship_code": "US", 
        "tax_id": "012345578",
        "id_number_type": "us_drivers_license",
        "id_number": "P11122243333",
        "kyc": "pass",
        "kyc_timestamp": 1630623005000,
        "sanction_screening": "pass",
        "sanction_screening_timestamp":1677252628000
      }
    ]
}

You’ll receive a participant_code in the response - this is the Merchant participant code that uniquely identifies the entity indefinitely. We’ll use MERCH1 as the participant_code throughout the examples.

See response for expected shape.

You must submit at least 1 beneficial_owners and 1 control_persons. If these persons do not have an SSN, you must submit a document via POST /participants/entity/documents.

Merchant Documents

Your Merchant will not become approved unless you also supply the proper documents via POST /participants/entity/documents endpoint. Depending on the entity_type that was used in the original POST /participants/entity/new call, the document requirements vary. See details here.

Next, submit the Merchants documents via POST /participants/entity/documents:

{
    "document": "...", // base 64 encoded file that you wish to upload (10mb limit)
    "mime": "image/png",
    "document_type": "articles_of_incorporation",
    "file_name": "test.png",
    "participant_code": "MERCH1"
}

State Logic

After a successful POST /participants/entity/new submission, the initial status of the entity will be submitted. In order to transition this to approved, you must then submit all required documents via POST /participants/entity/documents.

2. Query Merchant

You can query already-submitted Merchants via the GET /participants endpoint. If you'd like to query a specific Merchant, use theparticipant_code parameter.

3. Fund Refund Account

In order to fulfill a refund, you'll need to pre-fund the Refund account. Here are the account details:

  • Participant_code: 00SCXM
  • Account_group: PLAT01 (replace with your participant_code)
  • Account_label: pay_refund
  • Account_type: available
  • Asset: USD

4. Submit Shopper

Next, you'll need to onboard the end users (ie, "Shoppers"). Depending on each Shopper's transaction volume and residence, we have differing level of KYC requirements. See table below:

If a Shopper’s total payment volume exceeds $999 within a 24-hour period, you must meet Tier 2 requirements before any further payments can be processed.

To submit a shopper, send a request to the POST /participants/customers/new endpoint.

Shopper - Data Flow

See the below flow diagram to understand the data flow, sequencing, and possible outcomes of submitting a Shopper

Notes:

  • Step 3 - Zero Hash will automatically and immediately screen the shopper
  • Step 5a - Zero Hash's Compliance team will manually review the flagged individual. This can take up to 24 hours. One strategy to move the Shopper to approved yourself is to update the Zero Hash Shopper record with another data point, allowing our sanctions system to comfortably deem them as not sanctioned (ie, the date of birth). Endpoint: PATCH /participants/customers/{participant_code}
  • See participant webhook information here

Tier 1 Submission Example

{
    "first_name": "John",
    "last_name": "Smith",
    "address_one": "1 Main St.",
    "address_two": "Suite 1000",
    "zip": "10014",
    "city": "New York City",
    "jurisdiction_code": "US-NY",
    "phone_number": "123456789",
    "partial": "true",
}

📘

For the below examples, we'll use the sample Shopper participant code of SHOPP1

5. Query Shopper

To retrieve general information about the shopper, query the GET /participants endpoint. You can also use the participant_code filter to retrieve an individual Shopper.

6. Query Shopper Limits

To check a Shopper’s accumulated volume over the past 24 hours, query the GET /participant/{participant_code}/limits endpoint. Example response:

{
    "participant_code": "SHOPP1",
    "limits": [{
            "type": [
                "payment"
            ],
            "period": 24,
            "balance":"100"
            "limit": "999",
            "asset": "USD"
      }
    ]
}

Interpretation of the above: over the last 24 hours, the Shopper has done $100 of payment activity.

7. Initiate Payment

Now, let's move onto money movement. Begin by requesting an access token specifying:

FieldDescriptionExample ValueRequired?
participant_codeThe 6-digit alpha numeric code associated with the Customer that is looking to withdraw (the response of the original POST /participants/customers/new call)SHOPP1Y
permissionsThe array of permissions that will be granted to the returned JWT token["pay"]Y
amountThe US Dollar amount of the good or service being paid for100Y
reference_idPlatform reference id for a specific transaction0bd7f7f0-cf26-495f-b2df-e8afe8481ba3N

Example POST /client_auth_token request:

{
"participant_code": "SHOPP1",
  "permissions": ["crypto-pay"],
  "payment_details" : {
      "purchase_amount": "100",
      "denominated_currency" : "USD"
   },
  "reference_id": "0bd7f7f0-cf26-495f-b2df-e8afe8481ba3"
}

After a successful POST /client_auth_token call, the next step is to start the Pay SDK flow, see the example below for doing it in a React application. Keep in mind that if you're using a Native Mobile App (Swift, Flutter, etc) instead of using zh-web-sdk you should follow the WebView approach, described here.

import React from 'react';
import ZeroHashSDK, { AppIdentifier } from 'zh-web-sdk';

const App = () => {
  const sdk = new ZeroHashSDK({
    zeroHashAppsURL: "https://web-sdk.cert.zerohash.com",
    PAYJWT: "<JWT_TOKEN_HERE>" 
  });

    sdk.openModal({
    appIdentifier: AppIdentifier.PAY, 
  })
  return <></>;
}

export default App;

Payment Options

Zero Hash offers 2 types of payment options:

Payment OptionDescription
AuthWithin the SDK, the Shopper will be prompted to choose a non-custodial wallet or exchange account to sign into and "pull" funds from in order to complete the payment.

Zero Hash will validate the amount of crypto or stablecoins being sent plus will ensure assets are sent on supported chains. Zero risk of Shopper error.
Classic TransferThe SDK will present the Shopper with a QR code and an address. Outside of the SDK, the Shopper will navigate to their non-custodial wallet or exchange account to coordinate the on-chain transfer.

For this option, the Shopper must send at least the amount of crypto or stablecoins in order for the payment to be made successfully.

🚧

You have the option to configure your Platform to offer 1 or both of the above payment options. Contact your Zero Hash rep.

Platform Accounts

The Platform will have the following accounts at Zero Hash:

AccountDescriptionTechnical Details
Settlement AccountThe fiat account that contains the balance of successful payments- participant_code = [your platform code] - account_group = [your platform code] - account_label = general - account_type = available - asset = USD (or other fiat currency being used)
Error AccountThe fiat account that contains the balance of excess deposits or deposits that were made with an incorrect asset or network. For example for the Classic Transfer, if the Shopper sends too much, Zero Hash will transfer the expected amount for the payment to the Settlement Account, and the excess to the Excess Account- participant_code = [your platform code] - account_group = [your platform code] - account_label = pay_error - account_type= available -asset` = USD (or other fiat currency being used)
Float AccountThe fiat account, funded by the Platform, that is used to refund Shoppers. The balances here will be converted to a crypto or stablecoin asset and sent on-chain to the Shopper. See section, "11. Refund a Payment" below for details- participant_code = 00SCXM - account_group = [your platform code] - account_label = general - account_type = available - asset = USD (or other fiat currency being used)

External Account and Payment Data Flow

Once the SDK is invoked, there are 2 important data flows to pay attention to:

  1. External Account status updates (applicable to the Auth payment option only)
  2. Payment statuses

External Account Linking Statuses

Subscribe to the Payments - External Account status updates webhooks


Payment Statuses

Subscribe to the Payments - Payment status updates webhooks

Depending on the payment option selected, the webhooks you should expect to receive will be different

Auth:

Classic Transfer:

Classic Transfer Scenarios

  1. settled - The Shopper sent the correct amount on the expected asset and network. Zero Hash will
    1. send a settled webhook
    2. auto convert to USD
    3. auto transfer the to the Settlement Account
    4. send an email to the shopper
  2. settled_over_payment - The Shopper sent too much. Zero Hash will:
    1. Decide if the market has moved against the Shopper or not
      1. If not moved against, Zero Hash will
        1. send a settled_over_payment webhook
        2. auto convert the entire deposit to USD
        3. auto transfer the amount to the Settlement Account
        4. auto transfer the excess to the Error Account
        5. send an email to the shopper
  3. under_payment - The Shopper sent too little. Zero Hash will:
    1. send a under_payment webhook
    2. auto convert the entire deposit to USD
    3. auto transfer the amount to the Under Payment Account
    4. Note: The Shopper can still complete the payment by sending another deposit. Each time they deposit, we'll check to see if we have the expected amount. Once we have at least the expected amount, we'll consider the payment either settled or settled_over_payment, depending on whether the Shopper sent to much on the subsequent deposit(s) or not.
  4. wrong_asset_network - the Shopper sent an asset or network other than the one they selected on the Select Asset and Network screen. Zero Hash will:
    1. send a wrong_asset_network webhook
    2. auto convert the entire deposit to USD
    3. auto transfer the USD the Error Account
    4. send an email to the Shopper
  5. settled_past_expiry - The Shopper sent funds after the expiration, but the equivalent fiat amount was equal to or greater than the amount. Zero Hash will:
    1. send a settled_past_expiry webhook
    2. auto convert the entire deposit to USD
    3. auto transfer the amount to the Settlement Account
    4. auto transfer the excess to the Error Account (unless in the rare case that the equivalent fiat amount is exactly equal to the amount - no funds will be transferred to the Error Account)
    5. send an email to the shopper
  6. under_payment_past_expiry - The Shopper sent funds after the expiration, but the equivalent fiat amount was less than the amount. Zero Hash will:
    1. send a under_payment_past_expiry webhook
    2. auto convert the entire deposit to USD
    3. auto transfer the USD to the Under Payment Account
    4. Note: The Shopper can still complete the payment by sending another deposit. Each time they deposit, we'll check to see if we have the expected amount. Once we have at least the expected amount, we'll consider the payment either settled or settled_over_payment, depending on whether the Shopper sent to much on the subsequent deposit(s) or not.

8. Email Receipts Sent

Zero Hash requires that Merchants receive an email receipt for every payment and refund. This will be handled automatically on your behalf.

9. Query Payments

You can view all payments via GET /payments

{
  "message": [
    {
      "payment_id": "0f68333e-2114-469d-b505-c850d776e061",
      "obo_participant": {
         "participant_code": "MERCH1",
         "account_group": "PLAT01",
         "account_label": "general"
      },      
      "payment_details": {
         	"withdrawal_id": "",
	        "trade_id": "",
	        "on_chain_transaction_id ": "",
          "network_fee_notional": "",
          "network_fee_quantity": "",
      }
      "asset": "USDC",
      "network": "SOL",
      "payment_type":"pay",
      "external_account_id": "2cc93b20-ee43-4877-8cdc-863e61829015"
      "participant_code": "SHOPP1",
      "amount": "125.50",
      "status": "settled",
      "failure_reason": "",
      "created_at": "2024-08-19T23:15:30.000Z",
      "updated_at": "2024-08-19T23:15:35.000Z"
    }
  ]
}

Platforms can also query an individual payments via the GET /payments/id endpoint. Example:

{
  "message":
    {
      "payment_id": "0f68333e-2114-469d-b505-c850d776e061",
      "obo_participant": {
         "participant_code": "MERCH1",
         "account_group": "PLAT01",
         "account_label": "general"
      },      
      "payment_details": {
         	"withdrawal_id": "",
	        "trade_id": "",
	        "on_chain_transaction_id ": "",
          "network_fee_notional": "",
          "network_fee_quantity": "",
      }
      "asset": "USDC",
      "network": "SOL",
      "payment_type":"payout",
      "external_account_id": "2cc93b20-ee43-4877-8cdc-863e61829015"
      "participant_code": "SHOPP1",
      "amount": "125.50",
      "status": "settled",
      "failure_reason": "",
      "created_at": "2024-08-19T23:15:30.000Z",
      "updated_at": "2024-08-19T23:15:35.000Z"
    }
}

10. Settlement

Zero Hash will, one time per day, send you a fiat settlement wire where the amount represents the sum of all converted payments from the prior trading session. Here is the settlement schedule:

SessionStartEndExpected Settlement Time
MondayMonday 9:00a ESTTuesday 8:59:59a ESTTuesday EOD
TuesdayTuesday 9:00a ESTWednesday 8:59:59a ESTWednesday EOD
WednesdayWednesday 9:00a ESTThursday 8:59:59a ESTThursday EOD
ThursdayThursday 9:00a ESTFriday 8:59:59a ESTFriday EOD
FridayFriday 9:00a ESTMonday 8:59:59a ESTMonday EOD

During US holidays, Platforms should expect their settlements to arrive by EOD on the next business day. For example, for the August 30th 2024 session, the settlement will arrive by Tuesday EOD (because Monday was Labor Day)

11. Refund a Payment

In order to initiate a refund, you should use the following endpoints:

  • POST /payments/external_accounts - links a crypto account where the Shopper will receive their refund
  • POST /payments - initiates a conversion from USD (using Refund account) to the crypto asset. This API call will also automatically and immediately send the funds on-chain.