Nexus for Payment Method Providers

Note: This document is intended for payment app developers who would like to integrate with Nexus to process payments.

If you are a merchant looking to accept payments through Nexus, click here.

Overview

KOMOJU Nexus is a payment solution that allows customers to pay with any app they choose. With Nexus, merchants can generate a universal QR code for a payment. This QR code can then be scanned by any supporting payment app or system camera.

Our solution allows payment service providers (hereafter, “providers”) to connect and process payments through any merchant with your payment method enabled. Once a provider is connected, customers can start using a provider’s payment app to make purchases by scanning Nexus QR codes.

Terminology

Term Description
Provider An organization that runs a payment service
Client The provider's mobile app that users use to make payments
Backend The backend payment system for the Provider
Merchant An individual or business that sells products or services online or offline
Customer A person who buys a product or service from a merchant

How It Works

  1. Customer scans Nexus QR code using a Client app or system camera
  2. Client app sends an authorization request through Nexus to Provider
  3. Client app captures the payment using authorization response
  4. Provider sends capture callback to Nexus, completing the payment

How it works

Onboarding

Here’s a top-level overview of the Nexus onboarding process. From first contact to accepting payments:

  1. Register as a Nexus provider
  2. We’ll get back to you with some tools for testing
  3. Develop your integration - see Client Integration and Backend Integration
  4. Inform us when you are ready for release
  5. Start collecting payments

Registration

To make your payment service available on Nexus, you need to register your service (Provider) to Nexus. For registration, reach out to contact@komoju.com.

The following information is required for the provider registration:

  • Provider domain
    • Domain name for communication between Nexus and your backend
    • Example: "examplepay.com"
  • Backend API path
    • Appended to your Provider domain to form an API endpoint
    • Used for Nexus to initiate authorizations and refunds
    • Example: "/nexus/api" would have backend requests sent to "https://examplepay.com/nexus/api"
    • See Backend Integration
  • Mobile landing path
    • Appended to your Provider domain
    • Used to open your app for payment directly from the user’s web browser via deep linking or app-schema redirect (up to you)
    • Example: "/nexus/activate" would have users redirected to "https://examplepay.com/nexus/activate"
    • See Use Cases
  • Provider name
    • A unique name to identify your client. Alphanumeric and "-" can be used.
    • Example: "example-pay"
  • Public key

Once you have registered, you can start Client Integration and Backend Integration.

Client Integration

This section covers the development work required to implement Nexus on your client (typically a mobile app, but it could be anything - including a website).

The payment flow can start as soon as your client discovers a Nexus Link.

In this document, we show a few examples of Nexus Links:

  • https://komoju.com/s/p/dc1y676t3m59hdxx89lfhr6p0
  • https://komoju.com/s/p/xxxxx
  • etc.

The exact format of these URLs is subject to change. See the verify step for how to identify authentic Nexus Links.

Use Cases

In short, there are 2 customer use cases that your Client must support in order to integrate Nexus. For desktop/external payments, you must support a QR code scanner, and for mobile payments, a redirect scheme.

We also refer to these scenarios as Nexus Link discovery.

Use Case 1: Collect payments initiated on non-mobile devices via QR code

To collect payments made from a desktop application or website, Nexus presents a QR code and asks the customer to pay by scanning that QR code using your client app.

Example Nexus QR code:

Case 1

When the customer scans the QR code using your client app, you can collect the payment by talking to Nexus’s HTTP API using the QR code data as the Nexus Link.

Case 2: Collect payments initiated on mobile devices via redirect

To collect payments made from mobile websites or apps, Nexus simply redirects the customer to your mobile landing URL in the following form:

https://{endpoint}{mobile_landing_path}?nexus_link=https://komoju.com/s/p/xxxxxxxx

The endpoint and mobile_landing_path fields are specified by you during registration.

You can decide what to do in this case. Typically, you would launch your client app via deep linking or an app-schema. Alternatively, you may carry out payment on a web interface. Either way, you are expected to talk to Nexus’s HTTP API using the nexus_link query param as the Nexus Link.

Completing a payment through Nexus

As soon as you’ve discovered a Nexus Link, you can use it to collect payment.

The Nexus payment flow is a simple 3-step process: verify, authorize, then capture.

ClientClientNexusNexusProviderProvider1. VerifyGET https://komoju.com/s/p/xxxxxContent-Type "application/vnd.nexus-link+json"2. AuthorizePOST https://komoju.com/s/p/xxxxx{"provider": "provider-name"}Reserve Payment APIsuccesssuccess3. CaptureCapture paymentCapture callback

These 3 steps are designed so that neither the Nexus server nor the provider’s server can be easily spoofed. A fake Nexus server will be unable to authorize payments, and a fake provider server will be unable to send capture callbacks. See Authentication for details.

ClientClientNexusNexusProviderProviderGET https://komoju.com/s/p/xxxxxContent-Type "application/vnd.nexus-link+json"

After discovery, your client must verify the integrity of the newly-discovered Nexus Link. Perform a GET request to the link.

## Request
GET https://komoju.com/s/p/xxxxx HTTP/1.1
Content-Type: application/json

## Response
200 HTTP/1.1
Content-Type: application/vnd.nexus-link+json
Nexus-Signature: aaaaaabbbbbbccccccdddddd...

{
  "id": "dc1y676t3m59hdxx89lfhr6p0",
  "resource": "session",
  "type": "payment",
  "timestamp": 11111111
}

In the response, confirm that the Content-Type header is equal to application/vnd.nexus-link+json.

Additionally, please verify our Nexus-Signature header as described in the Authentication section.

This step ensures that your client doesn’t blindly send authorization POST requests to arbitrary endpoints.

Note: Do not check the hostname of the Nexus Link for verification. The domain used for Nexus Links may change in the future without notice.

Step 2: Authorize the payment through Nexus

ClientClientNexusNexusProviderProviderPOST https://komoju.com/s/p/xxxxx{"provider": "provider-name"}Reserve Payment APIsuccesssuccess

After verifying, your client should send a POST request to the Nexus Link with provider set to the value submitted when registering with Nexus:

## Request
POST https://komoju.com/s/p/dc1y676t3m59hdxx89lfhr6p0 HTTP/1.1
Content-Type: application/json

{
  "provider": "your-provider-name-here"
}

## Response
200 HTTP/1.1

{
  "id": "dc1y676t3m59hdxx89lfhr6p0",
  "type": "payment",
  "payment": {
    "id": "1zk24mmsez27d369pp7wqaoen",
    "status": "authorized",
    "amount": 1000,
    "currency": "JPY",
    "payment_details": {
      "type": "nexus",
      "authorization_response_text": "your backend’s response body here"
    },
    ...
  }
}

When you perform this request, Nexus will synchronously make an authorize request to your backend. The raw response body returned by your backend will be included in the payment.payment_details.authorization_response_text field.

Step 3. Capture the payment through your own backend

ClientClientNexusNexusProviderProviderCapture paymentCapture callback

Before you actually capture, now is a good time to ask for user confirmation on your client app. You can display their remaining balance, the payment amount, etc., and ask them if they’re sure they want to pay.

Using the information contained in authorization_response_text, your client is expected to capture the payment directly with your backend. The way your client communicates with your backend is up to you, and is not governed by this specification. When this capture succeeds, your backend is expected to send a callback to Nexus.

Backend integration

Your backend implementation will need to communicate with Nexus to authorize, refund, and send callbacks when payments are captured on your system.

All requests to and from your backend will be digitally signed using a SHA-2 HMAC signature to ensure secure communication between Nexus and the backend integration. Please refer to the Authentication section for more details.

Integrating with Nexus

Feature 1: Receive authorization requests from Nexus

Nexus will send your backend a reserve payment request to create an authorized payment. To handle this request, you are expected to synchronously create and authorize a payment with your own backend.


POST https://provider/path/to/handler HTTP/1.1
Content-Type: application/json
Nexus-Signature: xxxxxxxxxx

{
  "type": "payment.create",
  "mode": "test",
  "payment": {
    "id": "86rzkcrvcf82s0audo5himdi3",
    "amount": 1000,
    "currency": "JPY",
    "description": "",
    "merchant": {
      "id": "9ccxr6ymcpsyq4fupp7qb6arl",
      "name": "Komoju Mart"
    }
  }
}
Note: No payment is actually captured in this step. Capture should happen between your client app and backend directly after confirming with the user. Nexus will never send capture requests to your backend server.

For a successful authorization, you can place any arbitrary text in your response body. Since your client app is expected to capture with your backend directly (not through Nexus), we recommend including any internal IDs required for capture.


HTTP/1.1 200
Content-Type: application/json

{
  "success": true,
  "my_internal_transaction_id": "abc123xyz"
}

This arbitrary response will be made available to your client app through the payment.payment_details.authorization_response_text field, similarly to the example shown here.

Feature 2: Receive refund requests from Nexus

Nexus will send your backend a refund payment request when the customer or merchant wishes to perform a refund.

POST https://provider/path/to/handler HTTP/1.1
content-type: application/json
komoju-signature: xxxxxxxxxx

{
  "type": "payment.refund",
  "payment_id": "86rzkcrvcf82s0audo5himdi3",
  "mode": "test",
  "amount": 100
}

If the specified payment cannot be refunded, feel free to return a non-200 HTTP response code or a JSON object containing { "success": false }.

HTTP/1.1 200
Content-Type: application/json

{
  "success": true
}

Feature 3: Send capture callbacks to Nexus

Your client is expected to capture with your backend directly. The way you perform the capture is entirely up to you. Once captured, your backend must send a capture callback to Nexus. The capture callback must include a payment_id parameter matching the one provided when you received the Reserve Payment API request.

POST https://komoju.com/callbacks/providers/provider-name HTTP/1.1
Content-Type: application/json
Nexus-Provider-Signature: xxxxxxxx

{
  "type": "payment.captured",
  "payment_id": "xxxxxxxx" // payment.id provided by Reserve Payment API
}

Error Handling

Most exceptional cases such as the user having insufficient balance are probably already handled by your client. This section will cover what to do in cases where communication between your backend and Nexus is in conflict.

Authorization errors

Under normal operating conditions, the authorization request should never fail. If something exceptional happens, returning a response in this format will greatly assist in debugging.

HTTP/1.1 400
Content-Type: application/json

{
  "success": false,
  "error": { "message": "<your error message here>" }
}

Refund errors

Refunds have many reasons to potentially fail. Even so, we currently have no special behavior for different types of refund failures. Just like for authorization, a response in the following format is preferred in case an investigation is required.

HTTP/1.1 400
Content-Type: application/json

{
  "success": false,
  "error": { "message": "<reason for why the refund failed>" }
}

Callback errors

If Nexus returns a non-200 response code when trying to perform a capture callback, we expect your backend to try again periodically. The exact period is up to you. We are notified of callback errors and will usually resolve them quickly. If there is a payment whose capture callback fails for an excessive amount of time (24+ hours), please contact us and we will work to resolve it.

Example Integration

We have an example Nexus integration (backend + mobile app in JS) here:

github.com/komoju/nexus-example

It’s built using React Native for the client and ExpressJS for the backend. It should be fairly straightforward to build and try it out yourself. If you have any trouble, please file an issue!

Authentication

Nexus uses an asymmetric 2-way authentication scheme for most API calls.

  • Asymmetric: public/private key signing - RSA and ECDSA supported
  • 2-way: both Nexus and the provider (you) have a public/private key pair

In short, you must sign outgoing requests, and verify incoming requests.

During signup, the provider (you) provides Nexus (us) with a public key. You should never share your private key with anybody. You only need to store your private key on your backend, not on your client. Client requests to Nexus do not need to be signed.

The Nexus public key is always available here.

Signing

When your backend makes requests to Nexus, you are expected to sign them.

Create a SHA-256 signature using your private key and the entire body of the request you intend to send

  1. Encode the signature using Base64
  2. Attach the encoded signature to the Nexus-Provider-Signature request header

Request signing in Javascript


const fs = require("fs");
const crypto = require("crypto");

function createSignature(body) {
  const privateKey = fs.readFileSync('./private-key.pem', { encoding: "utf-8" });
  const sign = crypto.createSign("sha256");
  sign.update(body);
  sign.end();
  const signature = sign.sign(privateKey);

  let base64EncodedSignature = Buffer.from(signature).toString("base64");

  // must be added to the request as the Nexus-Provider-Signature header
  return base64EncodedSignature;
};

Request signing in Ruby


require 'openssl'
require 'json'
require 'base64'

def create_signature(body)
  key_string = File.read('private.pem')
  key = OpenSSL::PKey::EC.new(key_string)
  digest = OpenSSL::Digest::SHA256.new

  signature = key.sign(digest, body.to_json)

  # must be added to the request as the Nexus-Provider-Signature header
  Base64.encode64(signature)
end

Verifying

When Nexus makes requests to your backend, they come with a special signature that you can use to ensure that all backend requests really do come from Nexus. Additionally, some Nexus responses are also signed and can be verified.

  1. Receive a HTTP request or response from Nexus
  2. Get the nexus-signature header from the request or response
  3. Base64 decode the header value to receive the signature
  4. Compare the signature against the body of the request and the Nexus public key

Verification in Javascript

const fs = require("fs");
const crypto = require("crypto");

function verify(body, headers) {
  let base64Signature = headers["nexus-signature"];
  let publicKey = fs.readFileSync("./nexus-pub.pem", { encoding: "utf-8" });

  let verifier = crypto.createVerify("sha256");
  verifier.update(body);

  let signature = Buffer.from(base64Signature, "base64");

  return verifier.verify(publicKey, signature);
}

Verification in Ruby

require 'openssl'
require 'base64'

def verify(body, headers)
  pem_string = File.read('./nexus-pub.pem')
  komoju_key = OpenSSL::PKey::EC.new(pem_string)

  return if komoju_key.nil?

  b64_signature = headers['HTTP_NEXUS_SIGNATURE']

  return 'Nexus-Signature header not found' if b64_signature.nil?

  signature = Base64.decode64(b64_signature)
  unless komoju_key.verify('sha256', signature, body)
    return 'Invalid Nexus-Signature header (verification failed)'
  end
rescue OpenSSL::PKey::PKeyError
  'Invalid Nexus-Signature header (verification failed)'
end