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.
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 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', ... }
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 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.
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:
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
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.
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.
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-----