Secure Embedded forms

How to setup and use our various Embedded forms

There are different use cases for these forms. (an exmaple of the options are in the example Embedded Payment Form code below)

  • Payment Intent: use this to create a payment intent / tokenize the account to be used in your own payment attempt. This creates a temporary token that can be used in the payment request and turned into a permanent token by using the store_card parameter in the API request.

  • Create / Get payment token: use this to create a payment token to be used in your own payment attempt. This simply saves a payment method and creates a token for your customer.

  • Use saved Payment Form ID: We can save form templates (please consult our tech support) - send a form ID for a saved form template built from the assorted option of parameters / attributes. An example of the code and the JSON parameters that are saved for future as are below. The JSON parameters can be any of the various options in the parameter / attributes tables with defaults. This allows less setup and the re-use of tempaltes across your app.

Example formid saved attributes ((frme67ba9b701de11ed87940aac2e024c3e):

    {
      "client-key": "01dffeb784c64d098c8c691ea589eb82",
      "domain": "https://secure.qorcommerce.io",
      "mid": "887728203",
      "use3DS": 0,
      "css-url": "https://secure.qorcommerce.io/css/standard.css",
      "include-cardholder": 1,
      "include-street": 0,
      "include-zip": 1,
      "button-submit": 1,
      "button-text": "Place Order",
      "auto-reload": 0,
      "include-store-card": 1,
      "store-card-text": "Store for later use",
      "required-fields": "cardholdername,account,expdate,cv,zip"
    }
  • Initiate with a payment token and verify the CC CVV for the payment attempt.

  • Update an existing token.

  • Prepare all parameters / attributes for the Embedded Form:

Forms (iFrame src enabled)

PayFrame (flex) is a feature that allows you to build and set the src of your iframe containing a payment form on an HTTPS-enabled web page with a return-url and error-url for the payment response. This prevents payment data from ever touching your systems, while allowing your customers to complete seamless eCommerce transactions on your site.

  1. Prepare your credentials: Before rendering your payment page (the page that will host the iframe), you will need to generate a 256-bit keyed-hash message authentication code (HMAC-SHA256).

  2. Add iframe element and components: Your html payment page must include an iframe element (with the src attribute specified with proper query string parameters) with a unique id. The HMAC message components (along with the HMAC itself from step 1) must be included in the query string parameters in the iframe src attribute. Note: The order in which the query string parameters are concatenated for the HMAC message must match the order of the parameters in the HMAC parameter. The ordering is crucial for QorCommerce to be able to properly verify the HMAC.

  <div style="padding:5px; height: auto; width: 530px;">
    <iframe id="pay-form" style="padding:5px; height: auto;" src="https://secure.qorcommerce.io/FlexFrame/?{{querystring...}} ></iframe>
  </div>

Example query string:

  ...?hmacsha256=afc7768ebc0ff9d60e3ef3a0d96f7333364ec8c27e3edcf8c5bea1a793bb43f9&width=540px&height=500px&form_id=frm_e67ba9b701de11ed87940aac2e024c3e&domain=https://secure.qorcommerce.io&client-key=01dffeb784c64d098c8c691ea589eb82&timestamp=1658175582&amount=20.10&orderid=oid-351038113&return-url=https://secure.qorcommerce.io/payFrame/thank-you.html&error-url=https://secure.qorcommerce.io/payFrame/error.html"
  1. Load Payment Form: The iFrame will load with the properly formatted payment form. You can find an example of this sequence in the code example below.

  2. Submit Cardholder Data: Once the iframe has loaded, it will render a payment form into which the user can enter their payment information. When the user submits the form, their information will be sent directly to the QorCommerce server, which will return a response.

  3. At this point, your return-url for approved responses and error-url for declined responses will be executed, receiving a query string representing the payment response.

  4. Process the response: Once you have a response then you can run through your own workflow to save the sale / order and save the token as needed.

Styling the Payment Form

The iframe content can be styled using custom CSS. You can use the classes and IDs in the example CSSs in your CSS to style the payment form and the elements it contains.

You can also provide custom text for the form's labels by specifying appropriate CSS rules. Each label contains an empty span element, so that you can use the ::before selector in conjunction with the content property to insert your desired text into the label.

query string params:

parameterdescriptionvalue
timestamp *Current Unix timestamp -> used for auto-reloadunix timestamp
form_idA form ID for a saved form template built from these attributesex. frm_e67ba9b701de11ed87940aac2e024c3e
domain *Domain of the website that will host this embedded payment formex. https://your.domain
app-key *The unique key identifying the app making this requestex. T6554252567241061980
client-key *An authorized merchant account and is assigned by QorCommerce or your merchant service account provider.ex. 01dffeb784c64d098c8c691ea589eb82
mid *The merchant id assigned by QorCommerce or your merchant service provider (MSP)ex. 887728203
profile-idThis is your profile / customer id that we can assign to the order
accountUse a tokenized account to be charged
amount *Amount of order or shopping cart to be chargedrequired on sales only
ex. 20.10
orderid *Unique order id - REPLACE with your own order id
use3DSTurn on 3D Secure (3DS)0 or 1 - default = 0
css-urlCustom CSS that you would like useex. 'your domain' + '/css/stadard.css'
expdate-formatThis will render exp month/year MM/YYYY or render separate month and year select fieldsmerged or separate - default = separate
exp-show-monthshow 3 character month helper in expiration month select box0 or 1 - default = 1
include-cardholderTurn cardholder field on / off0 or 1 - default = 0
include-streetTurn street field on / off0 or 1 - default = 0
include-zipTurn zip field on / off0 or 1 - default = 0
include-emailTurn email field on / off0 or 1 - default = 0
include-phoneTurn phone field on / off0 or 1 - default = 0
store-cardstore card for later use (manual push) - if this is set then include-store-card is by passed0 or 1 - default = 0
include-store-cardShow "Store for later use" checkbox0 or 1 - default = 0
store-card-textSet the store card text on the formex. Store card for later use
button-cencelTurn Cancel button on / off within the iFrame0 or 1 - default = 0
button-submitTurn Submit button on / off within the iFrame0 or 1 - default = 1
button-textset the submit button textex. Place Order
return-url *Redirect to this URL on an approval
error-url *Redirect to this URL on a decline / error
cancel-url *Redirect to this URL on a user cancel

Forms (Embedded)

PayFrame (embedded) is a feature that allows you to embed a secure iframe via javascript containing a payment form on an HTTPS-enabled web page using various events to define your payment workflow. This prevents payment data from ever touching your systems, while allowing your customers to complete seamless ecommerce transactions on your site.

  1. Prepare your credentials: Before rendering your payment page (the page that will host the iframe), you will need to generate a 256-bit keyed-hash message authentication code (HMAC-SHA256).

  2. Add iframe element and components: Your html payment page must include an empty iframe element (with no src attribute specified) with a unique id which will be referenced by the JavaScript where you would like the payment form to appear. The HMAC message components (along with the HMAC itself from step 1) must be included as data- attributes on the iframe element. Note: The order in which the data attributes are concatenated for the HMAC message must match the order of the attributes in the table Payment Form Attributes. The ordering is crucial for QorCommerce to be able to properly verify the HMAC.

  3. Load script: Your payment page will need to load https://secure.qorcommerce.io/payFrame/js/QorPaymentFrame.js, which provides the client-side logic for displaying the iframe.

  4. Load Payment Form: From here you'll just need to write a few lines of your own Javascript to instantiate a QorPaymentForm object and request the iframe. You will
    need to define a callback function to be executed after the user's payment information has been submitted. The full sequence would be to instantiate PaymentFrame with the iFrame id and URL for QorCommerce, define callback using the .onSubmit() method, and call the .request() method to load the iFrame. You can find an example of this sequence in the code example below.

  5. Submit Cardholder Data: Once the iframe has loaded, it will render a payment form into which the user can enter their payment information. When the user submits the form, their information will be sent directly to the QorCommerce server, which will return a response.

  6. At this point, the callback function that you defined in your Javascript will be executed, receiving a JSON object representing the payment response.

  paymentForm.onSubmit(function(response) {
    if (response.code === 'approved') {
      alert( "Received an approval: " + response.onSuccess );
      // record the transaction, save the token if needed and finish order/sale
      console.log(response.onSuccess);
      window.location.assign("thank-you.html");
    } else {
      alert( "Received a decline: " + response.onError );
      console.log(response.onError);
      paymentForm.enableSubmitButton();
    }
  });
  1. Process the response: Once you have a response then you can run through your own workflow to save the sale / order and save the token as needed.

Styling the Payment Form

The iframe content can be styled using custom CSS. You can use the classes and IDs in the example CSSs in your CSS to style the payment form and the elements it contains.

You can also provide custom text for the form's labels by specifying appropriate CSS rules. Each label contains an empty span element, so that you can use the ::before selector in conjunction with the content property to insert your desired text into the label.

Using an External Submit Button

Depending on the layout of your payment page, you might wish to use your own submit button for the payment form rather than use the one shown in the iframe by default. Here are the steps required for this option:

  • When generating the iframe, send the parameter hmac-include-submit-button="no". This will hide the iframe's default submit button.
  • To trigger a submit of the iframe payment form, use the QorPaymentForm object's submitPayment method as demonstrated in the below example:
 var paymentForm = new QorPaymentForm("pay-form", "https://secure.qorcommerce.io");
 var mySubmitButton = document.getElementId("my-submit-button");

 var mySubmitButton.addEventListener("click", function() {
   paymentForm.submitPayment();
 });

Payment Form attributes:

attributesdescriptionvalue
timestamp *Current Unix timestamp -> used for auto-reloadunix timestamp
form_idA form ID for a saved form template built from these attributesex. frm_e67ba9b701de11ed87940aac2e024c3e
domain *Domain of the website that will host this embedded payment formex. https://your.domain
app-key *The unique key identifying the app making this requestex. T6554252567241061980
client-key *An authorized merchant account and is assigned by QorCommerce or your merchant service account provider.ex. 01dffeb784c64d098c8c691ea589eb82
mid *The merchant id assigned by QorCommerce or your merchant service provider (MSP)ex. 887728203
profile-idThis is your profile / customer id that we can assign to the order
accountUse a tokenized account to be charged
amount *Amount of order or shopping cart to be chargedrequired on sales only
ex. 20.10
orderid *Unique order id - REPLACE with your own order id
use3DSTurn on 3D Secure (3DS)0 or 1 - default = 0
css-urlCustom CSS that you would like useex. 'your domain' + '/css/stadard.css'
expdate-formatThis will render exp month/year MM/YYYY or render separate month and year select fieldsmerged or separate - default = separate
exp-show-monthshow 3 character month helper in expiration month select box0 or 1 - default = 1
include-cardholderTurn cardholder field on / off0 or 1 - default = 0
cardholder-textset the cardholder value
include-streetTurn street field on / off0 or 1 - default = 0
include-zipTurn zip field on / off0 or 1 - default = 0
zip-textset the zip value
include-emailTurn email field on / off0 or 1 - default = 0
include-phoneTurn phone field on / off0 or 1 - default = 0
store-cardstore card for later use (manual push) - if this is set then include-store-card is by passed0 or 1 - default = 0
include-store-cardShow "Store for later use" checkbox0 or 1 - default = 0
store-card-textSet the store card text on the formex. Store card for later use
button-cancelTurn Cancel button on / off within the iFrame0 or 1 - default = 0
button-submitTurn Submit button on / off within the iFrame0 or 1 - default = 1
button-textset the submit button textex. Place Order

As an Example (only) Embedded Payment Form using HTML / JS - choosing options

(this is for example purpose only and testing- not a secure implementation: your HMAC is to be done server side and never in JS and your secret should never be shared with annyone nor exposed)

    <DOCTYPE html>
    <html>
      <head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Example Grouped Embedded Checkout (html/js)</title>
        <link rel="stylesheet" type="text/css" href="./css/your.css" />
      </head>
      <body>
        <main>
          <div id="myForm" style="padding:5px; height: auto; width: 530px">
            <iframe id="pay-form"></iframe>

            <!--button id="my-submit-button" type="submit" style="padding: 5px; width: <?=$payfields['width']?>;height: 40px; border-radius: 3px;">
              <span id="my-submit-button-text"><?=$payfields['button-text']?>></span>
            </button-->

          </div>
          
        </main>
        <script src="https://secure.qorcommerce.io/payFrame/js/QorPaymentForm.js"></script>
        <script>

          async function HMAC(key, message){
            const g = str => new Uint8Array([...unescape(encodeURIComponent(str))].map(c => c.charCodeAt(0))),
            k = g(key),
            m = g(message),
            c = await crypto.subtle.importKey('raw', k, { name: 'HMAC', hash: 'SHA-256' },true, ['sign']),
            s = await crypto.subtle.sign('HMAC', c, m);
            return [...new Uint8Array(s)].map(b => b.toString(16).padStart(2, '0')).join('');
          }

          /* Values that will be needed for generating the HMAC */
          let host_domain = "https://secure.qorcommerce.io";
          let qor_form_id = "frm_e67ba9b701de11ed87940aac2e024c3e"; // test form_id from a saved embedded form template
          let qor_client_key = "01dffeb784c64d098c8c691ea589eb82"; // test client-key
          let qor_mid = "887728203"; // test MID

          const payfields = {};

          payfields["width"] = '520'+'px'; // sets payment form DIV width and passed along to payment form -> 400px is single column, 520px (+) grid rendered

          document.getElementById("myForm").style.width = payfields["width"];

          const params = (new URL(location)).searchParams;
          
          switch(params.get('option')) {
            case '1':

              /** use this to create a payment intent and tokenize the account to be used in your own payment attempt ***/

              payfields["timestamp"] = Math.floor(Date.now() / 1000); // Current Unix timestamp -> used for auto-reload
              payfields["payment-intent"] = 1; 
              payfields["domain"] = host_domain; // Domain of the website that will host this embeddedpayment form
              payfields["app-key"] = qor_app_key;
              payfields["client-key"] = qor_client_key;
              payfields["mid"] = qor_mid;
              payfields["include-cardholder"] = 1; // turn cardholder field on / off
              payfields["include-street"] = 0;  // turn steet field on / off
              payfields["include-zip"] = 1; // turn zip field on / off -> used for AVS
              payfields["button-submit"] = 1;
              payfields["button-text"] = "Place Order (intent)"; // set the submit button text
              //payfields["profile-id"] = '12345'; // this is your profile / customer id that we can assign to the token

              break;

            case '2':

              /** -- OR -- **/
              /** use this to create a payment token to be used in your own payment attempt or simply save a payment method and store that token for your customer ***/
              
              payfields["timestamp"] = Math.floor(Date.now() / 1000); // Current Unix timestamp -> used for auto-reload
              payfields["payment-token"] = 1; 
              payfields["domain"] = host_domain; // Domain of the website that will host this embedded payment form
              payfields["app-key"] = qor_app_key;
              payfields["client-key"] = qor_client_key;
              payfields["mid"] = qor_mid;
              payfields["include-cardholder"] = 1; // turn cardholder field on / off
              payfields["include-street"] = 0;  // turn steet field on / off
              payfields["include-zip"] = 1; // turn zip field on / off -> used for AVS
              payfields["button-submit"] = 1;
              payfields["button-text"] = "Create Payment Token"; // set the submit button text
              //payfields["profile-id"] = '12345'; // this is your profile / customer id that we can assign to the token

              break;

            case '3':

              /** -- OR -- **/
              /*** send a form ID for a saved form template built from the assorted option of attributes ***/

              // JSON of attributes saved in the form template (DB)
              /*
              {
                "app-key": "{{secret}}",
                "client-key": "01dffeb784c64d098c8c691ea589eb82",
                "domain": "https://secure.qorcommerce.io",
                "mid": "887728203",
                "use3DS": 0,
                "css-url": "https://secure.qorcommerce.io/css/standard.css",
                "include-cardholder": 1,
                "include-street": 0,
                "include-zip": 1,
                "button-submit": 1,
                "button-text": "Place Order",
                "auto-reload": 0,
                "include-store-card": 1,
                "store-card-text": "Store for later use",
                "required-fields": "cardholdername,account,expdate,cv,zip"
              }
              */

              payfields["timestamp"] = Math.floor(Date.now() / 1000); // Current Unix timestamp -> used for auto-reload
              payfields["form_id"] = qor_form_id; 
              payfields["domain"] = host_domain; // id this is sent this overrides the saved host domain
              payfields["client-key"] = qor_client_key;
              payfields["amount"] = "20.10"; // amount of order or shopping cart to be charged
              payfields["orderid"] = "oid-" + Math.floor(Math.random() * 100000) + 1000; // unique order id - REPLACE with your own order id
              //payfields["profile-id"] = '12345'; // this is your profile / customer id that we can assign to the order
              //payfields["exp-show-month"] = 0; // show 3 character month helper in expiration month select box

              break;

            case '4':

              /** -- OR -- **/
              /*** send everything to build an embedded form ***/
            
              payfields["timestamp"] = time(); // Current Unix timestamp -> used for auto-reload
              payfields["domain"] = host_domain; // Domain of the website that will host this embedded payment form
              payfields["client-key"] = qor_client_key;
              payfields["mid"] = qor_mid;
              //payfields["profile-id"] = '12345'; // tis is yor profile / customer id that we can assign to the order

              //payfields["background-color"] = "#e7f0ff"; // a color eg. red -or- the hex eg. #e7f0ff

              payfields["amount"] = "20.10"; // amount of order or shopping cart to be charged
              payfields["orderid"] = "oid-" + Math.floor(Math.random() * 100000) + 1000; // unique order id - REPLACE with your own order id

              //payfields["account"] = "131942$U60MLwx2"; // send a tokenized account to be charged (???? needs to be encrypted)

              payfields["use3DS"] = 0; // turn on 3D secure usage

              payfields["css-url"] = host_domain + "/css/standard.css";
              //payfields["css-url"] = $host_domain + "/css/modern.css";

              //payfields["expdate-format"] = "separate"; // will also render exp month/year MM/YYYY - separate is default
              //payfields["expdate-format"] = "merged"; // will also render exp month/year MM/YYYY - separate is default
              payfields["include-cardholder"] = 1; // turn cardholder field on / off
              //payfields["include-street"] = 1;  // turn street field on / off
              payfields["include-zip"] = 1; // turn zip field on / off -> used for AVS

              //$payfields["auto-reload"] = 1; // not done yet -> used for AVS

              //payfields["store-card"] = 1; // store card for later use (manual push) - if this is set then include-store-card is by passed
              //payfields["include-store-card"] = 1; // show "Store for later use" checkbox
              //payfields["store-card-text"] = "Store for later use"; // set the store card text on the form

              payfields["button-submit"] = 0;
              payfields["button-text"] = "Place Order"; // set the submit button text

              break;

            case '5':

              /** use this form to send a payment token and verify the CC CVV for the payment attempt ***/

              payfields["timestamp"] = Math.floor(Date.now() / 1000); // Current Unix timestamp -> used for auto-reload
              payfields["payment-cvv"] = 1; 
              payfields["domain"] = host_domain; // Domain of the website that will host this embedded payment form
              payfields["client-key"] = qor_client_key;
              payfields["mid"] = qor_mid;
              payfields["account"] = "131942$U60MLwx2"; // send a tokenized account to be charged (???? needs to be encrypted)
              payfields["amount"] = "20.10"; // amount of order or shopping cart to be charged
              payfields["orderid"] = "oid-" + Math.floor(Math.random() * 100000) + 1000; // unique order id - REPLACE with our order id
              payfields["button-submit"] = 1;
              payfields["button-text"] = "Place Order (verify CVV)"; // set the submit button text

              break;

            case '6':

             /** use this form to update an existing token ***/

              payfields["timestamp"] = Math.floor(Date.now() / 1000); // Current Unix timestamp -> used for auto-reload
              payfields["cctoken-update"] = 1; 
              payfields["domain"] = host_domain; // Domain of the website that will host this embedded payment form
              payfields["client-key"] = qor_client_key;
              payfields["account"] = "131942$U60MLwx2"; // send a tokenized account to be updated
              payfields["button-submit"] = 1;
              payfields["button-text"] = "Update Token"; // set the submit button text

              break;

            case '7':

              /*** Use the modern CSS example **/
              /*** send a form ID for a saved form template built from the assorted option of attributes ***/         

              payfields["timestamp"] = Math.floor(Date.now() / 1000); // Current Unix timestamp -> used for auto-reload
              payfields["form_id"] = qor_form_id2; 
              payfields["css-url"] = host_domain + "/css/modern.css";
              payfields["domain"] = host_domain; // id this is sent this overrides the saved host domain
              payfields["client-key"] = qor_client_key;
              payfields["amount"] = "10.10"; // amount of order or shopping cart to be charged
              payfields["orderid"] = "oid-" + Math.floor(Math.random() * 100000) + 1000; // unique order id - REPLACE with your own order id

              break;

            default:

              payfields["timestamp"] = Math.floor(Date.now() / 1000); // Current Unix timestamp -> used for auto-reload
              payfields["form_id"] = qor_form_id; 
              payfields["domain"] = host_domain; // id this is sent this overrides the saved host domain
              payfields["client-key"] = qor_client_key;
              payfields["amount"] = "10.10"; // amount of order or shopping cart to be charged
              payfields["orderid"] = "oid-" + Math.floor(Math.random() * 100000) + 1000;; // unique order id - REPLACE with your own order id

          }


          /* Response onSuccess INTENT:

          {
            "status":"approved",
            "code":"GW00",
            "message":"Get token was processed successfully (existing token).",
            "last_4":"1319",
            "token":"t:131942$U60MLwx2"  // temporary token
          }

          /* Response onSuccess GET TOKEN:

          {
            "status":"approved",
            "code":"GW00",
            "message":"Get token was processed successfully (existing token).",
            "last_4":"1319",
            "token":"131942$U60MLwx2"
          }

          /* Response onSuccess PAYMENT:

          {
            "status":"approved",
            "code":"GW00",
            "message":"Sale processed successfully.",
            "transaction_date":"2022-07-14 08:46:31",
            "transaction_id":"219445991395050",
            "order_id":"oid-2008028340",
            "last_4":"1319",
            "brand":"visa",
            "authcode":"222625",
            "amount_approved":"20.10",
            "isGC":"no"
          }

          /* Response onSuccess w/ token PAYMENT:

          {
            "status":"approved",
            "code":"GW00",
            "message":"Sale processed successfully.",
            "transaction_date":"2022-07-14 08:46:31",
            "transaction_id":"219445991395050",
            "order_id":"oid-2008028340",
            "last_4":"1319",
            "brand":"visa",
            "authcode":"222625",
            "amount_approved":"20.10",
            "store_card":1,
            "token":"131942$U60MLwx2",
            "token_last4":"1319",
            "token_exp_m":"03",
            "token_exp_y":"24",
            "token_brand":"visa",
            "isGC":"no"
          }

          /* Response onError PAYMENT:

          {
            "status":"declined",
            "code":"GW97",
            "message":"Sale was not processed. (97: CVV2\/CID error)",
            "order_id":"oid-153098432",
            "isGC":"no"
          }

          */

          let data_to_hash = Object.values(payfields).join(''); // implode values only

          // hash and build hmac example (should be done server side and in your language of choice)
          HMAC{{secret}}, data_to_hash)
            .then(function(hash) { 
              document.getElementById("pay-form").setAttribute("data-hmac-hmacsha256", hash); 

              for (const [key, value] of Object.entries(payfields)) {
                 document.getElementById("pay-form").setAttribute("data-hmac-"+key, value); 
              }

              var paymentForm = new QorPaymentForm( "pay-form",host_domain);

              /***
                * define your own submit button 
                * When generating the iframe, send the attribute button-submit = 0.
                  This will hide the payment forms default submit button.
                • to trigger a submit of the payment form, use the paymentForm object's
                  submitPayment method
              ***/
              /*
              var mySubmitButton = document.getElementById("my-submit-button");
              mySubmitButton.addEventListener("click", function() {
                  paymentForm.submitPayment();
                });
              */

              paymentForm.onSubmit(function(response) {
                if (response.code === 'approved') {
                  alert( "Received an approval: " + response.onSuccess );
                  // record the transaction, save the token if needed and finish order/sale
                  console.log(response.onSuccess);
                  window.location.assign("thank-you.html");
                } else {
                  alert( "Received a decline: " + response.onError );
                  console.log(response.onError);
                  paymentForm.enableSubmitButton();
                }
              });

              paymentForm.request();
            } );
        </script>
      </body>
    </html>