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)
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.
-
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).
-
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×tamp=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"
-
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.
-
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.
-
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.
-
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:
parameter | description | value |
timestamp * | Current Unix timestamp -> used for auto-reload | unix timestamp |
form_id | A form ID for a saved form template built from these attributes | ex. frm_e67ba9b701de11ed87940aac2e024c3e |
domain * | Domain of the website that will host this embedded payment form | ex. https://your.domain |
app-key * | The unique key identifying the app making this request | ex. 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-id | This is your profile / customer id that we can assign to the order | |
account | Use a tokenized account to be charged | |
amount * | Amount of order or shopping cart to be charged | required on sales only ex. 20.10 |
orderid * | Unique order id - REPLACE with your own order id | |
use3DS | Turn on 3D Secure (3DS) | 0 or 1 - default = 0 |
css-url | Custom CSS that you would like use | ex. 'your domain' + '/css/stadard.css' |
expdate-format | This will render exp month/year MM/YYYY or render separate month and year select fields | merged or separate - default = separate |
exp-show-month | show 3 character month helper in expiration month select box | 0 or 1 - default = 1 |
include-cardholder | Turn cardholder field on / off | 0 or 1 - default = 0 |
include-street | Turn street field on / off | 0 or 1 - default = 0 |
include-zip | Turn zip field on / off | 0 or 1 - default = 0 |
include-email | Turn email field on / off | 0 or 1 - default = 0 |
include-phone | Turn phone field on / off | 0 or 1 - default = 0 |
store-card | store card for later use (manual push) - if this is set then include-store-card is by passed | 0 or 1 - default = 0 |
include-store-card | Show "Store for later use" checkbox | 0 or 1 - default = 0 |
store-card-text | Set the store card text on the form | ex. Store card for later use |
button-cencel | Turn Cancel button on / off within the iFrame | 0 or 1 - default = 0 |
button-submit | Turn Submit button on / off within the iFrame | 0 or 1 - default = 1 |
button-text | set the submit button text | ex. 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.
-
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).
-
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.
-
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.
-
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. -
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.
-
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();
}
});
- 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:
attributes | description | value |
timestamp * | Current Unix timestamp -> used for auto-reload | unix timestamp |
form_id | A form ID for a saved form template built from these attributes | ex. frm_e67ba9b701de11ed87940aac2e024c3e |
domain * | Domain of the website that will host this embedded payment form | ex. https://your.domain |
app-key * | The unique key identifying the app making this request | ex. 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-id | This is your profile / customer id that we can assign to the order | |
account | Use a tokenized account to be charged | |
amount * | Amount of order or shopping cart to be charged | required on sales only ex. 20.10 |
orderid * | Unique order id - REPLACE with your own order id | |
use3DS | Turn on 3D Secure (3DS) | 0 or 1 - default = 0 |
css-url | Custom CSS that you would like use | ex. 'your domain' + '/css/stadard.css' |
expdate-format | This will render exp month/year MM/YYYY or render separate month and year select fields | merged or separate - default = separate |
exp-show-month | show 3 character month helper in expiration month select box | 0 or 1 - default = 1 |
include-cardholder | Turn cardholder field on / off | 0 or 1 - default = 0 |
cardholder-text | set the cardholder value | |
include-street | Turn street field on / off | 0 or 1 - default = 0 |
include-zip | Turn zip field on / off | 0 or 1 - default = 0 |
zip-text | set the zip value | |
include-email | Turn email field on / off | 0 or 1 - default = 0 |
include-phone | Turn phone field on / off | 0 or 1 - default = 0 |
store-card | store card for later use (manual push) - if this is set then include-store-card is by passed | 0 or 1 - default = 0 |
include-store-card | Show "Store for later use" checkbox | 0 or 1 - default = 0 |
store-card-text | Set the store card text on the form | ex. Store card for later use |
button-cancel | Turn Cancel button on / off within the iFrame | 0 or 1 - default = 0 |
button-submit | Turn Submit button on / off within the iFrame | 0 or 1 - default = 1 |
button-text | set the submit button text | ex. 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>
Updated about 1 year ago