Overview

First Data's Payment.js allows merchants working with various First Data APIs and gateways to tokenize payment credentials for later transactions without collecting, processing, or otherwise being able to view those payment credentials in their untokenized form, thus lowering their PCI compliance requirements.

Payment.js accomplishes this by injecting iframes into a parent form where customers can enter their data as though it were a normal form field styled however the merchant sees fit.

In form submission the client library, loaded into the parent window, sends one of the iframes a clientToken (for authentication with the service) and a one-time-use RSA public key (asymmetric key pair). This iframe then collects the data hidden in the other iframes and encrypts this data with the public key. This iframe then makes an API call to the Payment.js service for tokenization.

Assuming the customer is using a browser with modern cross-origin security controls, and these controls are not compromised by a browser defect, it will not be possible for non-Payment.js code to steal the data hidden in these iframes as it never escapes into the parent window in its unencrypted form.

When the tokenization request is sent out, only the already encrypted data will appear in the browser's network log.

The following gateways are supported:
  • IPG
  • Payeezy
  • CardConnect (CardPointe/CardSecure)
  • Bluepay

Note: The merchant's gateway credentials are never sent to the browser (encrypted or otherwise); The Payment.js client library utilizes a "clientToken" to associate the tokenization api call sent from the browser with credentials passed directly from merchant server to Payment.js server

Functional Flow

Diagram 1: Data Flow
  • 01 Consumer visits merchant’s payment page; merchant will return payment page back to consumer’s web browser including the payment.js javascript library (referenced within merchant’s payment page script tag).
  • 02 Merchant initiates an authorize session request; merchant sends in a few headers including a message signature that’s signed with their API secret and a nonce, timestamp etc. Also sends in their gateway credentials.
  • 03 Payment.js server validates message signature + api key, generates an OAUTH token (called the clientToken). This has a one-time use with an expiration date. Payment.js server also requests a key-pair generated from a First Data crypto service; crypto service returns public key and key-pair id
  • 04 Payment.js server creates a session record. Merchant gw credentials, key-pair id, and merchant-defined web hook stored for later use in the flow.
  • 05 Public key + clientToken returned to merchant's server
  • 06 Merchant returns payment form including iframed fields to client browser.
  • 07 Consumer fills out and submits payment form. All payment fields (card number, cardholder name, expiration date, cvv2) encrypted with public key from pre flow
  • 08 Payment.js server validates clientToken, then uses clientToken to do a lookup to grab the merchant’s gateway config. Session record containing merchant's session data deleted.
  • 09 Payment.js server uses the key-pair ID from the sessionrecord together with the encrypted data from the consumer to request decryption from the First Data crypto service. Data decrypted and returned to Payment.js server
  • 10 Payment.js server takes decrypted data, formats that into something that the target gateway accepts, and sends off a tokenization request to the gateway
  • 11 Gateway processes tokenization request and returns response to Payment.js server.
  • 12 Payment.js server forwards response to a merchant-defined web hook
  • 13 Payment.js server also returns a HTTP 200 back to the client indicating that the tokenization request has completed. clientToken is now revoked and cannot be used for any further tokenization requests
  • 14 Merchant webform should be updated to notify user that the tokenization request was successful or failed.

At this point, the payment.js flow has completed. Merchant would save token generated from the payment.js request in a database on their end and/or then do a subsequent auth or sale transactions using the token to charge the consumer.

Accessing the service

Environments
  • uat: Customer Sandbox
  • prod: Production aka Live
Versions
  • 2.0.0
API Service URLs
  • uat: https://cert.api.firstdata.com/paymentjs/v2
  • prod: https://prod.api.firstdata.com/paymentjs/v2

Referencing The Client Library

The "src" attribute for the script tag follows the following format:
  • https://docs.paymentjs.firstdata.com/lib/{{env}}/client-{{version}}.js
  • {{env}} = environment id
  • {{version}} = version number

Registering with the service

In order to integrate with Payment.js, some details need to be registered on our end. The Payment.js apiKey and apiSecret would be provided by your First Data representative once boarded.

Integration Example

Note: Payment.js does not impose platform restrictions (such as NodeJS) or require use of an SDK.

How To: Message Signature

Algorithm
  • 01 HMAC SHA256 signed with Payment.js Api Secret
  • 02 resulting HMAC hash needs to be hex encoded at this point
  • 03 encode hex-encoded hash in Base64
Message Components
  • 01 Payment.js Api Key (corresponds to header "Api-Key")
  • 02 Nonce (corresponds to header "Nonce")
  • 03 Timestamp (corresponds to header "Timestamp")
  • 04 payload (serialized; must match request body)

Note: Message components must be concatenated in order without delimiters.

Note: Payload must be serialized in the same way as it appears in the request body.

Code Examples

Message Signature: Javascript
function genHmac(msg, secret) {
  const algorithm = CryptoJS.algo.SHA256);
  const hmac = CryptoJS.algo.HMAC.create(algorithm, secret);
  hmac.update(msg);
  const hexEncodedHash = hmac.finalize().toString();
  const base64EncodedHash = base64.encode(hexEncodedHash);
}
Message Signature: PHP
/**
 * @param string $msg Message To Sign
 * @param string $secret Payment.js Api Secret
 * @return string The value for header Message-Signature
 */
function genHmac($msg, $secret)
{
    $algorithm = 'sha256';
    $hexEncodedHash = hash_hmac($algorithm, $msg, $secret);
    $base64EncodedHash = base64_encode($hexEncodedHash);
    return $base64EncodedHash;
}
Message Signature: Java
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;

public class HmacGenerator {
    public String genHmac(String msg, String secret) {
        String algorithm = HmacAlgorithms.HMAC_SHA_256;
        HmacUtils hmacUtils = new HmacUtils(algorithm, secret);
        Hex hexEncoder = new Hex();

        byte[] binaryEncodedHash = hmacUtils.hmac(msg);
        byte[] hexEncodedHash = hexEncoder.encode(binaryEncodedHash);
        String base64EncodedHash = Base64.encodeBase64String(hexEncodedHash);
        return base64EncodedHash;
    }
}

API Reference: Authorize Session

Api Request

Method, URL, and Headers
POST {{SERVICE_URL}}/merchant/authorize-session
Api-Key: {{Payment.js-apikey}}
Content-Type: application/json
Message-Signature: {{MSG_SIGNATURE}}
Nonce: {{random value}}
Timestamp: {{timestamp, milliseconds since epoch}}
Payload for IPG
{
  "gateway": "IPG",
  "apiKey": "",
  "apiSecret": "",
  "zeroDollarAuth": false
}
Payload for Payeezy
{
  "gateway": "PAYEEZY",
  "apiKey": "",
  "apiSecret": "",
  "authToken": "",
  "transarmorToken": "",
  "currency": "USD",
  "zeroDollarAuth": false
}
Payload for Bluepay
{
  "gateway": "BLUEPAY",
  "accountId": "",
  "secretKey": "",
  "zeroDollarAuth": false
}
Payload for CardConnect
{
  "gateway": "CARD_CONNECT",
  "merchantId": "",
  "apiUserAndPass": "",
  "currency": "USD",
  "zeroDollarAuth": false
}

Api Response

Noteworthy Response Headers
Client-Token: {{access/session id used by client library}}
Nonce: {{should match "Nonce" header passed in request}}
Response Success Payload
{
  "publicKeyBase64": "{{base64-encoded public rsa key used by client library}}"
}
Error Cases
  • HTTP 400: request failed structural validation (missing one or more expected fields/headers)
  • HTTP 401: authentication error, may include payload with "error" field

Support for non-USD Currency

Pass an ISO 4217 currency code in field "currency" in the authorize session payload to perform a zero-dollar authorization under a currency other than "USD".

Note: Only applicable to Payeezy and CardConnect

Note: Please Click here for the list of ISO 4217 currencies supported by Payeezy

Note: Please Click here for the list of ISO 4217 currencies supported by CardConnect

Webhook

Description

The gateway response to the tokenization request is parsed for details, normalized, and then HTTPS POSTed to the configured webhook URL.

Note: A webhook url must support HTTPS and include fully qualified domain name as well as route

Note: A webhook url must only contain static elements

Webhook Payloads

Noteworthy Headers
Client-Token: {{access/session id for which webhook call applies}}
Nonce: {{should match "Nonce" header passed in authorize session request}}
Example Success Payload
{
  "authCode": "ABCABC",
  "card": {
    "bin": "424242",
    "brand": "visa",
    "exp": {
      "month": "02",
      "year": "2022"
    },
    "last4": "4242",
    "name": "Blah Blah",
    "token": "123123123123",
    "masked": "XXXXXXXXXXXX4242"
  },
  "gatewayRefId": "123123123123",
  "error": false,
  "zeroDollarAuth": {
    "cvv2": "MATCHED"
  }
}
Error payload
{
  "error": true,
  "reason": "{{ERROR_CODE}}",
  "gatewayRefId": "...",
  "gatewayReason": "...",
  "zeroDollarAuth": {
    "cvv2": "NOT_MATCHED"
  }
}
CVV2 Auth Codes
  • MATCHED
  • NOT_MATCHED
  • NOT_PROCESSED
  • NOT_PRESENT
  • NOT_CERTIFIED
  • NO_RESPONSE
Possible card brand values
  • "visa"
  • "mastercard"
  • "american-express"
  • "diners-club"
  • "discover"
  • "elo"
  • "jcb"
  • "maestro"
  • "mir"
  • "unionpay"
  • null: if ambiguous or unrecognized
Error Codes
  • "BAD_REQUEST": the request body is missing or incorrect for endpoint
  • "DECRYPTION_ERROR": failed to decrypt card data
  • "INVALID_GATEWAY_CREDENTIALS": gateway credentials failed
  • "JSON_ERROR": the request body is either not valid JSON or larger than 2kb
  • "KEY_NOT_FOUND": no available key found
  • "MISSING_CVV": zero dollar auth requires cvv in form data
  • "NETWORK": gateway connection error
  • "REJECTED": the request was rejected by the gateway
  • "SESSION_CONSUMED": session completed in another request
  • "SESSION_INSERT": failed to store session data
  • "SESSION_INVALID": failed to match clientToken with valid record; can occur during deployment
  • "UNEXPECTED_RESPONSE": the gateway did not respond with the expected data
  • "UNKNOWN": unknown error

Client Library

Example Usage

Form with placeholders
<form id="form">
  <div>
    <label for="cc-name">Name</label>
    <div id="cc-name" data-cc-name></div>
  </div>

  <div>
    <label for="cc-card">Card</label>
    <div id="cc-card" data-cc-card></div>
  </div>

  <div>
    <label for="cc-exp">Exp</label>
    <div id="cc-exp" data-cc-exp></div>
  </div>

  <!-- required only if using cvv in config-->
  <div>
    <label for="cc-cvv">CVV</label>
    <div id="cc-cvv" data-cc-cvv></div>
  </div>

  <button id="submit">Submit</button>
</form>
Configuration
const config = {
  // optional
  styles: {
    ".emptyClass": {
    },

    ".focusClass": {
    },

    ".invalidClass": {
      color: "#C01324",
    },

    ".validClass": {
      color: "#43B02A",
    },
  },

  // optional
  classes: {
    empty: "emptyClass",
    focus: "focusClass",
    invalid: "invalidClass",
    valid: "validClass",
  },

  fields: {
    card: {
      selector: '[data-cc-card]',

      // optional
      placeholder: 'Full Name',
    },

    // optional but required for successful zero dollar auth
    cvv: {
      selector: '[data-cc-cvv]',

      // optional
      placeholder: 'Full Name',
    },

    exp: {
      selector: '[data-cc-exp]',

      // optional
      placeholder: 'Full Name',
    },

    name: {
      selector: '[data-cc-name]',

      // optional
      placeholder: 'Full Name',
    }
  }
};
Hooks
const hooks = {
  // required
  preFlowHook: (callbackFn) => {
    // values come from authorize-session endpoint
    callbackFn({
      clientToken: "....",
      publicKeyBase64: "...",
    });
  },
};
Instantiating
window.firstdata.createPaymentForm(config, hooks, (paymentForm) => {
  // example: add submit handler to form
  formElement.addEventListener("submit", (e) => {
    e.preventDefault();
    paymentForm.onSubmit(
      // on success
      (clientToken) => {
      },

      // on failure
      (errorObj) => {
      },
    );
  });
});

Form Submission Flow

// onSubmit (roughly the manual equivalent)
const onSubmit = (resolve, reject) => {
  try {
    paymentForm.validate(
      () => {
        paymentForm.authenticate(
          (auth) => {
            paymentForm.submit(auth, resolve, reject);
          },

          reject,
        );
      },

      reject,
    );
  } catch (error) {
    if (reject) {
      reject(error);
    } else {
      throw error;
    }
  }
};

Styling Restrictions

The css properties that can be injected into the field iframes are restricted to the below whitelist.

Style Whitelist
  • -moz-appearance
  • -moz-osx-font-smoothing
  • -moz-tap-highlight-color
  • -moz-transition
  • -webkit-appearance
  • -webkit-font-smoothing
  • -webkit-tap-highlight-color
  • -webkit-transition
  • -webkit-box-shadow
  • -webkit-text-fill-color
  • background-color
  • appearance
  • color
  • direction
  • font
  • font-family
  • font-size
  • font-size-adjust
  • font-stretch
  • font-style
  • font-variant
  • font-variant-alternates
  • font-variant-caps
  • font-variant-east-asian
  • font-variant-ligatures
  • font-variant-numeric
  • font-weight
  • letter-spacing
  • line-height
  • opacity
  • outline
  • text-shadow
  • transition