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
Submission: On August 01 via automatic, source certstream-suspicious — Scanned from DE
Form analysis
0 forms found in the DOMText 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.