docs.paymentjs.firstdata.com Open in urlscan Pro
34.236.185.19  Public Scan

URL: https://docs.paymentjs.firstdata.com/
Submission: On August 01 via automatic, source certstream-suspicious — Scanned from DE

Form analysis 0 forms found in the DOM

Text Content

You need to enable JavaScript to run this app.
Top

PAYMENTJS

TABLE OF CONTENTS

 * Overview
 * Additional Security Settings Recommended
 * Functional Flow
 * Accessing the Service
 * Registering with the Service
 * Integration Examples
 * How To: Message Signature
 * API Reference: Authorize Session
 * Webhook
 * Client Library
 * Known Issues

OVERVIEW

Fiserv's PaymentJS allows merchants working with various Fiserv APIs and
gateways to tokenize payment credentials for later transactions without
collecting, processing, or otherwise being able to view those payment
credentials in their un-tokenized form, thus lowering their PCI compliance
requirements.

PaymentJS 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 RSA
public key (asymmetric key pair). This iframe then collects the data hidden in
the other iframes and encrypts the card number, expiration date, and cvv (the
other fields are transferred without data layer encryption due to RSA message
limits). This iframe then makes an API call to the PaymentJS 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-PaymentJS code to steal the data hidden in these iframes
as the card number, expiration date, and cvv in particular never escape into the
parent window in an 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 PaymentJS client library utilizes a "clientToken"
to associate the tokenization api call sent from the browser with credentials
passed directly from merchant server to PaymentJS server

ADDITIONAL SECURITY SETTINGS RECOMMENDED

The following recommendations are to limit potential for fraudulent activity on
your hosted payment page.

 * Enable Re-Captcha
 * Authentication to payment page
 * Limit response back to the browsers/customer
 * Enable appropriate Fraud Tools for your business type or payment flow

FUNCTIONAL FLOW

 * 01
   Consumer visits merchant’s payment page; merchant will return payment page
   back to consumer’s web browser including the PaymentJS 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
   PaymentJS server validates message signature + api key, generates an OAUTH
   token (called the clientToken). This has a one-time use with an expiration
   date. PaymentJS server also requests a key-pair generated from a Fiserv
   crypto service; crypto service returns public key and key-pair id
 * 04
   PaymentJS 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
   PaymentJS 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
   PaymentJS server uses the key-pair ID from the session record together with
   the encrypted data from the consumer to request decryption from the Fiserv
   crypto service. Data decrypted and returned to PaymentJS server
 * 10
   PaymentJS 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 PaymentJS
   server.
 * 12
   PaymentJS server forwards response to a merchant-defined web hook
 * 13
   PaymentJS 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 web form should be updated to notify user that the tokenization
   request was successful or failed.

ACCESSING THE SERVICE

Environments
 * uat: Customer Sandbox
 * prod: Production aka Live

Versions
 * 2.0.0

API Service Base 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://lib.paymentjs.firstdata.com/{{env}}/client-{{version}}.js
 * {{env}} = environment id
 * {{version}} = version number

REGISTERING WITH THE SERVICE

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

 * Request PaymentJS Integration

INTEGRATION EXAMPLES

 * NodeJS (simple; 0 dependencies)
 * NodeJS (complex; based on our internal test tool)
 * PHP
 * Java

HOW TO: MESSAGE SIGNATURE

Algorithm
 * 01
   HMAC SHA256 signed with PaymentJS Api Secret
 * 02
   resulting HMAC hash needs to be hex encoded at this point
 * 03
   encode hex-encoded hash in Base64

Message Components
 * PaymentJS Api Key (corresponds to header "Api-Key")
 * Nonce (corresponds to header "Nonce")
 * Timestamp (corresponds to header "Timestamp")
 * 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.

JavascriptPHPJava

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);
}




API REFERENCE: AUTHORIZE SESSION

API REQUEST

Method, URL and Headers

POST {{SERVICE_URL}}/merchant/authorize-session
Api-Key: {{PaymentJS-apikey}}
Content-Type: application/json
Message-Signature: {{MSG_SIGNATURE}}
Nonce: {{random value}}
Timestamp: {{timestamp, milliseconds since epoch}}

Payloads
PayeezyIPGBluepayCard Connect

{
  "gateway": "PAYEEZY",
  "apiKey": "",
  "apiSecret": "",
  "authToken": "",
  "transarmorToken": "",
  "currency": "USD",
  "zeroDollarAuth": false
}

 * Note: Payeezy utilizes the apiKey & apiSecret within the APIs tab of your
   developer.payeezy.com account.

 * Note: "currency" is optional and defaults to "USD"





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

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",
    "address1": "Apartment 123",
    "address2": "123 Main Street",
    "city": "New York",
    "region": "NY",
    "country": "US",
    "postalCode": "11375",
    "company": "My Company"
  },
  "gatewayRefId": "123123123123",
  "error": false,
  "zeroDollarAuth": {
    "cvv2": "MATCHED",
    "avs": "Y",
  },
}

 * Note: "authCode" defaults to ""

 * Note: "gatewayRefId" defaults to ""

 * Note: "zeroDollarAuth" set to false if zeroDollarAuth not requested in
   authorize session

 * Note: "avs" may not be set if address data not present or not returned by
   gateway

 * Note: "avs" may have different value types for different gateways; IPG
   returns an object

 * Note: see gateway documentation on what values are expected in the "avs"
   field

Error payload

{
  "error": true,
  "reason": "{{ERROR_CODE}}",
  "gatewayRefId": "...",
  "gatewayReason": "...",
  "zeroDollarAuth": {
    "cvv2": "NOT_MATCHED",
    "avs": "N",
  }
}

 * Note: "gatewayRefId" defaults to ""

 * Note: "gatewayReason" only added if available and may be a string or json
   object

 * Note: "zeroDollarAuth" set to false if zeroDollarAuth not requested in
   authorize session

 * Note: gateway rejections are treated as errors with reason "REJECTED"

 * Note: "avs" may not be set if address data not present or not returned by
   gateway

 * Note: "avs" may have different value types for different gateways; IPG
   returns an object

 * Note: see gateway documentation on what values are expected in the "avs"
   field

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">
  <!-- masked iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-card">Card</label>
    <div id="cc-card" data-cc-card></div>
  </div>

  <!-- iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-name">Name</label>
    <div id="cc-name" data-cc-name></div>
  </div>

  <!-- iframe value will be sent to webhook parsed into month and year-->
  <div>
    <label for="first-data-payment-field-exp">Exp</label>
    <div id="cc-exp" data-cc-exp></div>
  </div>

  <!-- required only if using cvv in config -->
  <!-- iframe value will not be sent to webhook -->
  <div>
    <label for="first-data-payment-field-cvv">CVV</label>
    <div id="cc-cvv" data-cc-cvv></div>
  </div>

  <!-- completely optional -->
  <!-- iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-address1">Address1</label>
    <div id="cc-address1" data-cc-address1></div>
  </div>

  <!-- completely optional -->
  <!-- iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-address2">Address2</label>
    <div id="cc-address2" data-cc-address2></div>
  </div>

  <!-- completely optional -->
  <!-- iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-city">City</label>
    <div id="cc-city" data-cc-city></div>
  </div>

  <!-- completely optional -->
  <!-- iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-region">State or Province</label>
    <div id="cc-region" data-cc-region></div>
  </div>

  <!-- completely optional -->
  <!-- iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-country">Country</label>
    <div id="cc-country" data-cc-country></div>
  </div>

  <!-- completely optional -->
  <!-- iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-postalCode">Postal Code</label>
    <div id="cc-postalCode" data-cc-postalCode></div>
  </div>

  <!-- completely optional -->
  <!-- iframe value will be sent to webhook -->
  <div>
    <label for="first-data-payment-field-company">Company</label>
    <div id="cc-company" data-cc-company></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: 'Credit Card Number',

      // optional, defaults to all brands being allowed.
      // see section titled "Restrict Card Brands" below for more information
      allowedBrands: ["visa", "discover", "mastercard", "american-express"],
    },

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

      // optional
      placeholder: 'CVV',
    },

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

      // optional
      placeholder: 'Expiration Date',
    },

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

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

    // optional
    address1: {
      selector: '[data-cc-address1]',

      // optional
      placeholder: 'Address 1',
    }

    // optional
    address2: {
      selector: '[data-cc-address2]',

      // optional
      placeholder: 'Address 2',
    },

    // optional
    city: {
      selector: '[data-cc-city]',

      // optional
      placeholder: 'City',
    },

    // optional
    company: {
      selector: '[data-cc-company]',

      // optional
      placeholder: 'Company',
    },

    // optional
    country: {
      selector: '[data-cc-country]',

      // optional
      placeholder: 'Country',
    },

    // optional
    postalCode: {
      selector: '[data-cc-postalCode]',

      // optional
      placeholder: 'Postal Code',
    },

    // optional
    region: {
      selector: '[data-cc-region]',

      // optional
      placeholder: 'State or Province',
    },
  }
};

Hooks

const hooks = {
  // required
  preFlowHook: (callbackFn) => {
    // values come from authorize-session endpoint
    callbackFn({
      clientToken: "....",
      publicKeyBase64: "...",
    });
  },

  // optional, alternate method of providing address field data
  // see target gateway documentation on how this data should be formatted
  // such as what country codes are accepted.
  submitFormHook: (callbackFn) => {
    callbackFn({
      address1: "...",  // optional
      address2: "...",  // optional
      city: "...",      // optional
      company: "...",   // optional
      country: "...",   // optional
      postalCode: "...",// optional
      region: "...",    // optional
    });
  },
};

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

MISCELLANEOUS FUNCTIONALITY

Resetting Form Fields

paymentForm.reset(() => console.log("reset form fields"));

Destroying Form Fields

paymentForm.destroyFields(() => console.log("destroyed form fields"));

Registering Card Brand Listener

//
// data will contain the following details:
//  - brand: string
//  - brandNiceType: string
//  - code: string or object (may not be defined)
//  - field: string
//  - potentiallyValid: boolean
//  - selector: string
//  - valid: boolean
//
paymentForm.on("cardType", (data) => {
  console.log("card brand is: " + data.brandNiceType);
});

Registering Field Focus Listener

//
// data will contain the following details:
//  - field: string
//  - selector: string
//
paymentForm.on("focus", (data) => {
  console.log("field name is: " + data.field);
});

Registering Field Blur Listener

//
// data will contain the following details:
//  - field: string
//  - selector: string
//
paymentForm.on("blur", (data) => {
  console.log("field name is: " + data.field);
});

Registering Field Change Listener

//
// data will contain the following details:
//  - empty: boolean
//  - field: string
//  - length: number
//  - potentiallyValid: boolean
//  - selector: string
//  - touched: boolean
//  - valid: boolean
//
// if the field is the card number field, it will also have the following:
//  - brand: string
//  - brandNiceType: string
//
paymentForm.on("change", (data) => {
  console.log("field name is: " + data.field);
});

Getting Form State

//
// data will contain the following details for each field:
//  - empty: boolean
//  - field: string
//  - length: number
//  - potentiallyValid: boolean
//  - touched: boolean
//  - valid: boolean
//
// the card field object will also contain the following:
//  - brand: string
//  - brandNiceType: string
//
// the cvv field object will also contain the following:
//  - maxLength: number
//
paymentForm.getState((data) => {
});

Set Field Focus

//
// valid field names include:
//  - address1
//  - address2
//  - card
//  - city
//  - company
//  - country
//  - cvv
//  - exp
//  - name
//  - postalCode
//  - region
//
paymentForm.setFieldFocus(fieldName, () => console.log("set field focus for " + fieldName));

RESTRICT CARD BRANDS

You may pass an array of brand identifiers as a string array in the form
configuration to restrict the allowed card brands. If brands allowed are
restricted in this way, the card field will fail the validation check if the
entered number does not match one of the allowed brands. The configuration field
is config.fields.card.allowedBrands as shown above under sub heading "Example
Usage".

Brand Identifiers
 * visa
 * mastercard
 * american-express
 * diners-club
 * discover
 * elo
 * jcb
 * maestro
 * mir
 * unionpay

KNOWN ISSUES

SET FIELD FOCUS

The setFieldFocus client library functionality does not currently work in
Safari.

CLICKING LABEL TO FOCUS FIELD

While giving label elements particular "for" properties (as seen in the example
html) is supposed to enable focusing the field by clicking the label, this does
not work in Safari.

NVDA CAN PREVENT FIELD ENTRY

When tabbing through all the iframe fields and then tabbing back through them
again NVDA can get stuck in "browse" mode which by default NVDA configuration
means field entry is blocked by NVDA since it intercepts all the key presses. To
resolve the issue the NVDA user can press a key or key combination that triggers
"focus" mode such as "space" or "enter". See NVDA documentation on other key
combinations that can be used or configured.

AUTOFILL LIMITATIONS

Previously we supported auto-filling multiple payment fields simultaneously by
using browser autofill on the card number field. We had to remove that
functionality as it caused an issue on mobile devices where some hidden fields
became focusable. Autofill now only works on a field by field basis with the
iframe fields.

VALIDATIONS NOT FIRING ON FOCUS CHANGE IN IOS 12 AND BELOW

In iOS 12 and below the "blur" event does not fire when focus changes from one
iframe to another iframe or window. We target the "blur" event to check for
incomplete field input such as when a card number of insufficient length is
entered. Validations will still occur as expected when form submission occurs.
This bug was fixed in iOS 13.