Payment Processing in Meta Pay

The documentation in this section describes how to process payments with Meta Pay.

After a user successfully completes checkout using Meta Pay JS SDK a container will be returned to the integrating merchant. A container in Meta Pay represents a payment instrument such as a credit card or other payment method. Merchants are not responsible for processing the container and should reach out to their payment partner for details on how to pass FBPaymentContainer for processing.

The information below is intended for payment partners.

Container Structure

For details of the Meta Pay container structure, see FBPaymentContainer. The following is an example of a Meta Pay container:

{
  mode: "TEST",
  amount:
  { 
    currency: "USD",
    value: 757
  }
  container_id: 'cGF5bWVudF9jb250YWl...',
  container_data: 'eyJ4NWMiO ...'
}

Container Data

Container data contains the payment instrument that can be used to process the user's payment. The contents of this field will be encrypted using the public key of the payment partner for whom it is intended. See container encryption section below for more information about encryption. After decryption container data is a JSON that is guaranteed to contain a container_type field. This field will be used to identify the type of container that was returned. The other fields in container data may differ depending on the container type.

{
  container_type: 'basic-card-v1',
  ...
}

Container types

Meta Pay uses many factors to determine which container type to return for a given payment. The returned type is guaranteed to come from the list of supported container types specified in the Meta Pay JS SDK FBPaymentConfiguration. See FBPayContainerType for a list of all supported container types.

See the following for details about processing each container type:

Container Context

Container context is an opaque ID that uniquely identifies the context that the container is intended for. Create a container context for each payment transaction when you initiate a payment request.

Verify the container context before you process each payment to prevent misuse of containers. For example, if the container context value is a customer ID, verify that the payment request for the same customer ID.

Container Encryption

To ensure security in transit, Meta encrypts container data by using the partner's public key before sending it to the merchant. You exchange keys with Meta as part of the onboarding process.

The following are the encryption steps:

  1. The container data is serialized into a JSON string.
  2. The JSON string is wrapped into a JWE (RFC7516) object by using the ECDH-ES algorithm (against the partner's public key, identified by the RFC7638 thumbprint in the kid field) and the A256GCM (aes256-gcm) encryption algorithm, using compact serialization.
  3. The resulting compact serialized JWE is wrapped into a JWS (RFC7515), signed with ES256 (ecdsa-sha256) using a certificate which chains up (RFC5280) to Meta's trust root, using compact serialization.

Example

The following is an example of an encrypted and signed container data payload:

eyJ4NWMiOlsiTUlJQ1JqQ0NBUzZnQXdJQkFnSUJBVEFOQmdrcWhraUc5dzBCQVFzRkFEQWZNUjB3R3dZRFZRUUREQlJHUWxCaGVTQjBaWE4wSUcxdlpHVWdjbTl2ZERBZUZ3MHlNREEzTURjeE9EQTFOVE5hRncweU5EQXpNRFV4T0RBMU5UTmFNQ2N4SlRBakJnTlZCQU1NSEVaQ1VHRjVJSFJsYzNRZ2JXOWtaU0JwYm5SbGNtMWxaR2xoZEdVd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTMVpxM0swclh6aWFacDEwRmE3MDAvZWNEb3RQRHd3N3V1ajhFZnZFSDdIdHRrQmtNQU1ycUJJMjFFUERxTmxlK2xpaFNHSWw1ZkZxNGZRYWIrTGdpb28xQXdUakFkQmdOVkhRNEVGZ1FVdnBaNmtNSldTZmdhcXlIaEY3aVBTelRESzV3d0h3WURWUjBqQkJnd0ZvQVVLbHl0R2VwYWNwZ011MFdmcjNhbE53ZWJ0dzB3REFZRFZSMFRCQVV3QXdFQi96QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFCWDNmYTBycXFlNmNFeUoxeXVGS2RPZFcvSnpFU09JSWo3WDkzRkFiNEZUUWsrQlcwQVVCdnJwei9idk9WKzhBVjFBUEllRDNFWld0czlaMGNNdjdZdXF0SktUaFd2R0JPc21QQnRIZURlNjZBeFlVOUkzcWhJdUNFcmU3Y2ZWeDVuSm9DcVVyY1B2aGJFbDd5K1BDaGFZdTBSV0VyVnV5OVhPT2NjN1JDZjdLcWFwTmdBQzhSNmdmOVNsUms4OEozRVFPaEowenRBM0ZuSC9ZRzFlM0RVa1JLa2pvMkZHZ3EyakRZTkdOc3BWenRpQXl2QVVqMnk3UUxJMytDclZGL3VlR0J0LzJnSG12ZThMS2s2OXlEKzN3ZGVEQi9YVG1xQVlxaWljQVlSTjAxMURObEgyMHg1aExGYXJhTkZEZ0ZvZlhlRDdHZWtwZk95ZGlaSHhjc0E9PSJdLCJhbGciOiJFUzI1NiJ9.eyJjb250YWluZXJfaWQiOiJjR0Y1YldWdWRGOWpiMjUwWVdsdVpBWEk2TVRJek5EVTJOemhmWDAxRlVrTklRVTVVWDFSRlUxUmZSVEpGWDE5UVUxQmZWRVZUVkY4eCIsIm1vZGUiOiJURVNUIiwiYW1vdW50Ijp7InZhbHVlIjozMjIzLCJjdXJyZW5jeSI6IlVTRCJ9LCJjb250YWluZXJfZGF0YSI6ImV5SmxibU1pT2lKQk1qVTJSME5OSWl3aVlXeG5Jam9pUlVORVNDMUZVeUlzSW10cFpDSTZJa0l4VWw5aFkwVmhhVVZaY3pWTE1IcHRZWGRNVTJoWlVtdGpkSEJKY0ZWVldGRTFkRE5EYVZBMlREQWlMQ0psY0dzaU9uc2lhM1I1SWpvaVJVTWlMQ0pqY25ZaU9pSlFMVEkxTmlJc0luZ2lPaUpHY0RNeE9YVTFOMjFRVVVkbWMyWmFTbFJQVnpoeVoyTklWVzlVZEd3NExUaE5jbFpzWVVnelYyZzRJaXdpZVNJNkltUlJhbmRVYmpWMGJHdEZZMjFPWjFkSFdEVmpSbEZzWkdob2VsaGpNM2xFZURsUGIwSnVlRGcwVm1zaWZYMC4uUGlEaThTbVhPVjNaRXNVUy5oQ0pLcnYwOUFGZEpRRE9lX0hNeEpCZXNuSHV2Ny00MXFGaE8ySTU3a1prVXhsSVJxV0Fia2ZOT2d0SlIxNEMyZ01qNTBERFMyYnFoV3dmV3hTa1RlS3c4bUhIcWtmVzRLSUQ1QlhfcjFSSkstWlBwcnF6OTlBY3dySTJYbFJ5aG1VaHd3Yno3cktDbWVxUUZnUVVuLXlHdGl4cGRiTGl5UmM3czVBSUtPeG1PNkwwTXhXdUVzMjFKZ2xBZGtXSzdWanJ1M3FYYVVFdjFRUzdlWV9OWWxYUnJiUDhNM2JWQ2ZlM2lFZTJwa05IcEtTckRxU1VJY3JnMjM3Z2VPOTJPbjJKRHhUUGkybkhublNZcjI4aVZFQkhBa0ZDRjAxX2VnTVlmeVhKMVFLZ0NBVmVrM2lTdjBNU3ZVeHJRWnVoWm5jQ0s5MEgtdF9kWVdrZl9SLXctSjVWOWc0SVRSMm1NYnZCQWZkcUl5aXlJb1FUak1nWTdGQjdaOGVFZTBSeDZIWkVwNnN0aWlSRGZEQVk0MG1seFdEeWJ3LUswZUtnMGtRODB6eUpKQzJoakpXZTAwSUF4WlI4Rk1QOU4yOG5lc29pR25QbUtqNFRFQkhEcUtYb1NlTWV6cUVjSHpuTFZhdVhpODBzdlotS0Fjc1oxNVQwSmpBcXp6ZnE0V09idGpQM3FnOVNkdXN3TWIyU2FBbWZzVncxOXZiTWtVcUtZRXc5bGlOUi00Q2w5UVR3Y2tnSDFuMlRUTEdNejA0XzNuWVAtYktZLkE2dmdxdGZhVUMwSU9pNXZUSy1LVHcifQ.Jj4v0XNdwdsZ42E_zA2qEdZix2DWyoBZpZT2ehUoLvGRV5NGDFUg7OU_9y91fCGQNjH5MKFiym1qoTC_Rcz1hw

The following is an example of the container data payload Base64 and JSON decoded for clarity, presented in JavaScript syntax:

base64url(json({
 x5c: [
   'MIICRjCCAS6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQDDBRGQlBheSB0ZXN0IG1vZGUgcm9vdDAeFw0yMDA3MDcxODA1NTNaFw0yNDAzMDUxODA1NTNaMCcxJTAjBgNVBAMMHEZCUGF5IHRlc3QgbW9kZSBpbnRlcm1lZGlhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS1Zq3K0rXziaZp10Fa700/ecDotPDww7uuj8EfvEH7HttkBkMAMrqBI21EPDqNle+lihSGIl5fFq4fQab+Lgioo1AwTjAdBgNVHQ4EFgQUvpZ6kMJWSfgaqyHhF7iPSzTDK5wwHwYDVR0jBBgwFoAUKlytGepacpgMu0Wfr3alNwebtw0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABX3fa0rqqe6cEyJ1yuFKdOdW/JzESOIIj7X93FAb4FTQk+BW0AUBvrpz/bvOV+8AV1APIeD3EZWts9Z0cMv7YuqtJKThWvGBOsmPBtHeDe66AxYU9I3qhIuCEre7cfVx5nJoCqUrcPvhbEl7y+PChaYu0RWErVuy9XOOcc7RCf7KqapNgAC8R6gf9SlRk88J3EQOhJ0ztA3FnH/YG1e3DUkRKkjo2FGgq2jDYNGNspVztiAyvAUj2y7QLI3+CrVF/ueGBt/2gHmve8LKk69yD+3wdeDB/XTmqAYqiicAYRN011DNlH20x5hLFaraNFDgFofXeD7GekpfOydiZHxcsA==',
 ],
 alg: 'ES256',
})) + // Protected Header
'.' + base64url(json({
 container_id: 'cGF5bWVudF9jb250YWluZAXI6MTIzNDU2NzhfX01FUkNIQU5UX1RFU1RfRTJFX19QU1BfVEVTVF8x',
 mode: 'TEST',
 amount: {
   value: 3223,
   currency: 'USD',
 },
 container_data: base64url(json({
     enc: 'A256GCM',
     alg: 'ECDH-ES',
     kid: 'B1R_acEaiEYs5K0zmawLShYRkctpIpUUXQ5t3CiP6L0',
     epk: {
       kty: 'EC',
       crv: 'P-256',
       x: 'Fp319u57mPQGfsfZJTOW8rgcHUoTtl8-8MrVlaH3Wh8',
       y: 'dQjwTn5tlkEcmNgWGX5cFQldhhzXc3yDx9OoBnx84Vk',
     },
   })) + // Protected Header
   '.' + '' + // Encrypted Key is empty when using ECDH-ES
   '.' + 'PiDi8SmXOV3ZEsUS' + // Initialization Vector
   '.' + 'hCJKrv09AFdJQDOe_HMxJBesnHuv7-41qFhO2I57kZkUxlIRqWAbkfNOgtJR14C2gMj50DDS2bqhWwfWxSkTeKw8mHHqkfW4KID5BX_r1RJK-ZPprqz99AcwrI2XlRyhmUhwwbz7rKCmeqQFgQUn-yGtixpdbLiyRc7s5AIKOxmO6L0MxWuEs21JglAdkWK7Vjru3qXaUEv1QS7eY_NYlXRrbP8M3bVCfe3iEe2pkNHpKSrDqSUIcrg237geO92On2JDxTPi2nHnnSYr28iVEBHAkFCF01_egMYfyXJ1QKgCAVek3iSv0MSvUxrQZuhZncCK90H-t_dYWkf_R-w-J5V9g4ITR2mMbvBAfdqIyiyIoQTjMgY7FB7Z8eEe0Rx6HZEp6stiiRDfDAY40mlxWDybw-K0eKg0kQ80zyJJC2hjJWe00IAxZR8FMP9N28nesoiGnPmKj4TEBHDqKXoSeMezqEcHznLVauXi80svZ-KAcsZ15T0JjAqzzfq4WObtjP3qg9SduswMb2SaAmfsVw19vbMkUqKYEw9liNR-4Cl9QTwckgH1n2TTLGMz04_3nYP-bKY' + // Ciphertext
   '.' + 'A6vgqtfaUC0IOi5vTK-KTw' // Authentication Tag
})) + // Payload
'.' + 'Jj4v0XNdwdsZ42E_zA2qEdZix2DWyoBZpZT2ehUoLvGRV5NGDFUg7OU_9y91fCGQNjH5MKFiym1qoTC_Rcz1hw' // Signature

Container Decryption

The payload in the above example can be decrypted with the following private key which corresponds to kid B1R_acEaiEYs5K0zmawLShYRkctpIpUUXQ5t3CiP6L0:

-----BEGIN EC PRIVATE KEY-----
MGICAQEEC++break+things+oAoGCCqGSM49AwEHoUQDQgAEt9vlzj8MootxUFNb
fBL2eRU9YPMJNlgjLS2byDWzIHSr/g5At16XE+9oPndHYVLoYvlMKrXfTyyAnIRC
+u9C4w==
-----END EC PRIVATE KEY-----

Below is sample code demonstrating how to verify and decrypt an encrypted Meta Pay container (in JavaScript/TypeScript/Node and leveraging node-jose). partnerEncryptionKeyPem is your EC keypair. fbpayTestRootCertPem is the Meta Pay Test Root Certificate used to sign the public key in JWS x5c header of the container. Please note that LIVE containers use a separate root certificate.

import {JWK, JWE, JWS} from 'node-jose';
import partnerEncryptionKeyPem from './test_keys/encryption.key';
import fbpayTestRootCertPem from './test_keys/test_mode_root.crt';

import {X509Certificate} from 'crypto';


export async function decryptTestContainer(container_jws: string) {
  const partnerEncryptionKey = await JWK.asKey(partnerEncryptionKeyPem, 'pem');
  const unverifiedPayload = JSON.parse(base64decode(container_jws.split('.')[1]));
  const jweHeader = JSON.parse(base64decode(unverifiedPayload.container_data.split('.')[0]));

  if (jweHeader.kid === partnerEncryptionKey.kid) {
    const verifiedPayload = await verifyTestContainer(container_jws);
    const payload = JSON.parse(verifiedPayload);
    const decryptResult = await JWE.createDecrypt(partnerEncryptionKey, {
      algorithms: ['ECDH-ES', 'A256GCM'],
    }).decrypt(payload.container_data);

    return {
      ...payload,
      container_data: JSON.parse(decryptResult.plaintext.toString()),
    };
  }

  throw new Error(`Decryption not available for key id ${jweHeader.kid}`);
}

export async function verifyTestContainer(container_jws: string) {
    // verify container signature using x5c leaf key
    const verifySignatureResult = await JWS.createVerify(undefined, {
      algorithms: ['ES256'],
    }).verify(container_jws, {allowEmbeddedKey: true});
  
  
    if(typeof X509Certificate != 'undefined') {
      const rootX509 = new X509Certificate(fbpayTestRootCertPem);
      const x5c = (verifySignatureResult.header as any).x5c;

      if(x5c.length !== 1) {
        throw new Error("This util only supports single certificate in x5c header. Found " + x5c.length);
      }
      // x5c to PEM
      const embeddedX509 = new X509Certificate(Buffer.from(x5c[0], 'base64'));

      // verify x5c chains to root cert
      let isChainVerified = embeddedX509.verify(rootX509.publicKey);

      if(!isChainVerified) {
        console.error("Certificate chain is invalid. x5c = \n", x5c, "\nroot = \n", rootX509.toString);
        throw new Error("Certificate chain is invalid");
      }
      // TODO: CRL verification as defined in RFC5280
    } else {
      throw new Error("Crypto module does not support X509Certificate in browser.");
    }

    return verifySignatureResult.payload.toString();
}

function base64decode(encoded: string): string {
  return Buffer.from(encoded, 'base64').toString();
}

Code samples in other languages demonstrating how to decrypt an encrypted container are available on Meta's GitHub repo.

Container signature

The outermost format of the container is JWS (RFC7515). The signature of the payload must be verified using the certificate provided in JWS header x5c parameter as specified in RFC7515. Additionally it must be verified that the certificate specified in JWS header x5c parameter chains up (RFC5280) to the root certificate specified below.

Meta Pay Signature Root Certificate

For Test mode containers

-----BEGIN CERTIFICATE-----
MIIEizCCA3OgAwIBAgIUDAWvJjPeEncyx1+xT9FKemx1GkwwDQYJKoZIhvcNAQEL
BQAwfTEmMCQGA1UEAwwdTWV0YSBQYXkgVGVzdCBSb290IENBIDIwMjQtMDMxCzAJ
BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRMwEQYDVQQHDApNZW5sbyBQ
YXJrMRwwGgYDVQQKDBNNZXRhIFBsYXRmb3JtcyBJbmMuMB4XDTI0MDMwNjIyMDIy
NVoXDTQ5MDMwNjIyMDIyNVowfTEmMCQGA1UEAwwdTWV0YSBQYXkgVGVzdCBSb290
IENBIDIwMjQtMDMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRMw
EQYDVQQHDApNZW5sbyBQYXJrMRwwGgYDVQQKDBNNZXRhIFBsYXRmb3JtcyBJbmMu
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArniqE1fo1ph9znsHvO/t
VXK+rJlOlQa6+74+5b7cKnJ5DCwUTvElg6QNGyf3cZ3PcTnRBHwykwVvVpWpEbIP
BF+5AWMNa561BZeGrgQLol/lb8aDk7bb0l2M587iHbxDdKK6OM/43Tj7LSkIJSfW
GxhjFw5uHaUxk/qGG0zxMC7sKSKkEtikukRQX5UsjFwCOY5XZZTlZq3X78FPzsFf
SESHD3dUT9zRpy40cRgWjY5pTjY9JWNiH4bTDtaNenYOK5XTKuxduV6Y7VDJeP1t
wukP7I9Uh7nydPR59Cv1LooInKvP5vrTGW+Zm1MMTQIRHC/Ln0zhFLl+vfG8cnaz
UQIDAQABo4IBATCB/jAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTAg1nnRDBt
520NXlpkCivUuHiLUTCBuwYDVR0jBIGzMIGwgBTAg1nnRDBt520NXlpkCivUuHiL
UaGBgaR/MH0xJjAkBgNVBAMMHU1ldGEgUGF5IFRlc3QgUm9vdCBDQSAyMDI0LTAz
MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UEBwwKTWVu
bG8gUGFyazEcMBoGA1UECgwTTWV0YSBQbGF0Zm9ybXMgSW5jLoIUDAWvJjPeEncy
x1+xT9FKemx1GkwwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQCs
HpsED2HxcKuRaEejc+fKij3Qx4vyYagzC0MHJnbz6IsL3l9l51VSzCM2ZuJx6zMk
vjer0dxVqE1I4TNC5vhtMLvrwlF/r9gV8KclY2UEwiJWxowikO6grCW/OJOC73Vx
tNOzy66hjZxpa1F/Ii2cVg2CTSmv6G/P8MaeNYiDoC3h4wVbqwo3cY3hxchNiq8T
CNJXjqGs917tNBt0UFii6GSoHv4z86lFtoOjYsJhqgLe20HtsmcHXkf7WTg8BmK0
ZUmgzahLFqSaH88xNaQTxxa+aegLvlpAGplEO/gamiNIxOhT+hoPKkbi3ZGFbTq1
I3tdm+tc2i6dpAUVNoug
-----END CERTIFICATE-----

For Live mode containers

-----BEGIN CERTIFICATE-----
MIIC8DCCApagAwIBAgIUbAy5P226baZmwNysloMirbU9D7owCgYIKoZIzj0EAwIw
eDEnMCUGA1UEAwweRmFjZWJvb2sgUGF5IENvbnRhaW5lciBSb290IENBMQswCQYD
VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UEBwwKTWVubG8gUGFy
azEWMBQGA1UECgwNRmFjZWJvb2sgSW5jLjAgFw0yMDEyMTEwMDA5NDNaGA8yMDUw
MTIxMTAwMDk0M1oweDEnMCUGA1UEAwweRmFjZWJvb2sgUGF5IENvbnRhaW5lciBS
b290IENBMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTETMBEGA1UE
BwwKTWVubG8gUGFyazEWMBQGA1UECgwNRmFjZWJvb2sgSW5jLjBZMBMGByqGSM49
AgEGCCqGSM49AwEHA0IABDiGbVM1PQ2mbzXMbGvQQ/Bce/4XDiGnMZS2WGgW4RwT
UgWKvFoXOLl22Oeoc07Bl0jriqD8P29cP8gGn7QFThKjgfswgfgwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUldu9FgG+FMaFMAffgrHDwKw9p7swgbUGA1UdIwSB
rTCBqoAUldu9FgG+FMaFMAffgrHDwKw9p7uhfKR6MHgxJzAlBgNVBAMMHkZhY2Vi
b29rIFBheSBDb250YWluZXIgUm9vdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgM
CkNhbGlmb3JuaWExEzARBgNVBAcMCk1lbmxvIFBhcmsxFjAUBgNVBAoMDUZhY2Vi
b29rIEluYy6CFGwMuT9tum2mZsDcrJaDIq21PQ+6MA4GA1UdDwEB/wQEAwIBhjAK
BggqhkjOPQQDAgNIADBFAiEAwULK+UPNPaa60FEYz9Av5MqVPl5QkyqqmSI1W6x7
DI8CIHFexZ8WaT4utSnqvgekEZ5DQM6lL2DwlynfJo6ZUSol
-----END CERTIFICATE-----