Collapse Menu
Docs Home
Extensibility Options
Contact Support

Passing Sensitive Data with Secure Requests

Overview

This article explains the process of passing data securely via Store Builder Library.

Along with basic operations like cart manipulation and providing coupons or "known" customer details, Store Builder Library allows you to generate advanced checkout sessions which might contain "authenticated" customer data, custom product pricing or product definitions. This is useful when building customized checkout flows and implies the use of data encryption.

 

 Tip

One example of a way to take advantage of secure requests is to provide customers with a "name your price" or "pay what you want" option on your web pages. Simply design your web site to accept price data from your customers, and use a secure payload to override the products' default prices. Alternatively, this can also be done using the /sessions endpoint of the FastSpring API.
 

Process Overview

The process of passing data securely using Store Builder Library involves three steps:

  1. Creating a session object containing customer and product data
  2. Encrypting the session object locally to protect the data that is to be transmitted
  3. Passing the encrypted payload to FastSpring

FastSpring will decrypt the payload upon receipt, and apply the decrypted data to the customer's session.

Since JavaScript and other client-side methods are inherently not secure, the sensitive data must be encrypted prior to passing it to FastSpring via the Store Builder Library. Without a securely encrypted payload, Store Builder Library will not accept certain types of session data such as product price overrides. Thus, even if an adversary were to intercept the data, they could not send FastSpring a modified payload (e.g. with a price of $0.00) without access to the encryption key.

 Important

Although you may make tests using raw / unencrypted data, live orders using the secure methods require encryption. Remember to set up encryption and pass only encrypted payloads before going live with these methods. See Passing an Encrypted Payload to the Store Builder Library, later on in this article, for more information.
 

Passing Customer Information to Be Applied to the Order in the Secure Payload

  We recommend reading Customer Information, Accounts and single sign-on and Using Store Builder Library to Provide Customer Details to familiarize yourself with the concept of Customer Accounts and various options around applying customer information.

This is required when your customer is authenticated with you and your backend is aware of customer details and the details can be passed to the checkout process.

 

Payload Example
{
    contact: {                                              // customer details - optional unless you pass accountCustomKey (below); if customer contact info is passed, customer will not be prompted to provide it during checkout
        firstName: <String>                                 // customer first name - required when passing contact info
        lastName: <String>                                  // customer last name - required when passing contact info
        email: <String>                                     // customer email address - required when passing contact info
        company: <String>                                   // customer company (optional unless required by Storefront settings)
        addressLine1: <String>                              // customer address line 1 (optional unless order includes physical product or 'Force phsyical address collection' is enabled in Storefront settings) 
        addressLine2: <String>                              // customer address line 2 (optional)
        city: <String>                                      // customer city (optional unless order includes physical product or 'Force physical address collection' is enabled in Storefront settings)
        region: <String>                                    // customer region (e.g. state for US customers, recommend two-character ISO region code)(optional unless order includes physical product or 'Force physical address collection' is enabled in Storefront settings)
        postalCode: <String>                                // customer postal code (required for credit card transactions)
        phoneNumber: <String>                               // customer phone number (optional unless required by Store settings)
        country: <String>                                   // customer country - two-character ISO country code (optional unless order includes physical product or 'Force physical address collection' enabled in Storefront settings)
    },
    accountCustomKey: <String>,                             // optional account ID from your system; if you pass this then the contact object above is required
    account: <String>,                                      // FastSpring-generated customer account ID (optional)
    taxId: <String>,                                        // optional GST ID or VAT ID for the customer; will be validated in conjunction with the country (detected via geo IP location or set as part of the contact object); if the ID is valid, it will be applied to the session and no GST or VAT will be applied to the order
}
 
  When passing contact information via the secure payload, your payload must include email, firstName and lastName at a minimum. If you only have the customer's email address and you do not need to pass sensitive data such as product price overrides, consider using the session object instead of a secure payload.

Important:  When using a secure payload, FastSpring validates customer details provided inside the contact object upon submission. If the data does not pass validation, it will be treated in the same way as a fastspring.builder.recognize() call; i.e., the data will be pre-filled but fields will be visible during the checkout. This allows customers to correct the mistakes. However, in the case of address fields, if your Storefront is not configured to display those fields, the customer will not see the fields or be able to correct the problem. Therefore, if address validation is important to your business, we encourage you to do one of the following:

  • validate the address supplied by the customer before passing it in via a secure payload
  • ensure that your Storefront has the Force physical address collection for all orders check box selected (on the Checkout page of a Popup Storefront's SETTINGS, or the General Settings page for a Web Storefront's SETTINGS) so that the fields will be displayed when necessary

 

  In addition to encrypting the data and passing it on frontend you can use the /accounts endpoint of the FastSpring API to create or modify accounts from the backend. The account ID obtained in the /accounts response can be later used with the /sessions endpoint to create a complete session. However, a mixed approach is not possible – when creating an account using the FastSpring API, you will have to create the final session using the FastSpring API and no modifications will be allowed from the Store Builder Library.

 

 

Passing Gift Recipient Information to Be Applied to the Order in the Secure Payload

This method automatically selects the Gift Purchase check box in the checkout form, and allows you to pass authenticated recipient information to the library, so that Recipient Information fields of the order form will be pre-filled and not displayed to the customer (pending validation).

Payload Example
{
	"recipient": {                                          // gift recipient details
		"email": "recipient@fastspring.com",            // recipient email address (must differ from the contact email address, if provided)
		"firstName": "firstName",                       // recipient first name
		"lastName": "lastName",                         // recipient last name
		"address": {                                    // recipient address information
			"addressLine1": "Address Line 1",       // recipient address line 1 (optional unless order includes physical product or 'Force physical address collection' is enabled in Storefront settings)
			"city": "City",                         // recipient city (optional unless order includes physical product or 'Force physical address collection' is enabled in Storefront settings)
			"region": "California",                 // recipient region (e.g. state for US customers, recommend two-character ISO region code)(optional unless order includes physical product or 'Force physical address collection' is enabled in Storefront settings)
			"postalCode": "93101",                  // recipient postal code (optional unless purchaser contact is in the United States, in which case this must be a valid U.S. ZIP code)
			"country": "US"                         // recipient country - two-character ISO country code (must match purchaser contact country)
		},
		"memo": "Happy Birthday!"                       // optional personal message from purchaser to recipient (maximum 400 characters)
	}
}

 

 

Passing Product Information in the Secure Payload

This approach is useful when you want to change a product's price for a specific customer. For example, this might be the case with special pricing or price A/B testing. To do this, you can use "tags" and "attributes" to pass experiment and variation IDs. Tags provide a way to assign arbitrary key/value pairs to the order and "capture" them later in the order notification.

 

  When passing the pricing object for a subscription product in your payload, be sure to include trial, interval, intervalLength and intervalCount.  Passing the pricing object overrides the product's existing pricing data, so if you do not pass subscription details, the product will be processed as a stand-alone, single payment product.
Payload Example
{
    items: [
        {
            product: <String>,                                                  // id of the product you are adding to cart
            quantity: <Integer>,                                                // quantity of the current product to be added to the cart; ignored if "quantityBehavior" = "lock" or "hide"; in that case, use "quantityDefault" instead
            pricing: {                                                          // keep in mind - when you pass the "pricing" object for an item you need to define all possible pricing options, including, for example, subscription-related fields where applicable. 
                trial: <Integer>,                                               // number of free trial days for a subscription (required for subscription only)
                interval: "adhoc", "day", "week", "month", or "year",           // interval unit for scheduled billings; "adhoc" = managed / ad hoc subscription (required for subscription only)
                intervalLength: <Integer>,                                      // number of interval units per billing (e.g. if interval = "MONTH", and intervalLength = 1, billings will occur once per month)(required for subscription only)
                intervalCount: <Integer>,                                       // total number of billings; pass null for unlimited / until cancellation subscription (required for subscription only)
                quantityBehavior: "allow", "lock", or "hide",                   // "allow" = quantity can be edited, "lock" = quantity is displayed in the checkout but not editable, "hide" = quantity is hidden and not editable (optional, default is allow)
                quantityDefault: <Integer>,                                     // default quantity for the product (only needed when "quantityBehavior" = "lock" or "hide", or if you do NOT pass quantity above)
                price: {                                                        // multiple currencies can be passed
                   "currency_code": <Number> (e.g. "USD": 5.0),
                   ...
                },
                discountType: "percent", or "amount",                           // optional product-level discount type
                quantityDiscounts: {                                            // optional volume-based discount settings; specify quantity required to receive discount per unit followed by discount percentage or amount 
                    <Integer>: {                                                // ex. if discountType = "PERCENT", 10: 25.00 results in 11+ units each receiving a 25% discount; for a simple discount use 0: 10.00 to give a 10% discount off everything 
                       "currency_code": "Number" (e.g. "USD": 5.0),             // ex. if discountType = "AMOUNT",  5: {"USD": 5.0} results in 6+ units each receiving a discount of $5 USD; multiple currencies supported
                       ...
                    },
                    ...
                },
                discountReason: {                                               // reason for the discount, which can optionally be displayed to customers; specified language must be enabled in Store settings
                    "language_code": <String> (e.g. "DE": "German text"),
                    ...
                },
                discountDuration: <Integer>,                                    // number of subscription billings to which the discount will apply (subscription only); e.g. 1 = first billing only, 2 = first and second billings, etc.
                dateLimitsEnabled: <Boolean>,                                   // controls whether or not the discount is valid only during a certain date range 
                startDate: <Parsable Date Value>,                               // earliest date on which the discount applies
                endDate: <Parsable Date Value>,                                 // discount end / expiration date
                startTime: <Parsable Date Value>,                               // time on the earliest date when discount becomes available; specify time in UTC
                endTime: <Parsable Date Value>,                                 // time on the end / expiration date when discount is no longer available; specify time in UTC
                reminder_enabled: <Boolean>,                                    // controls whether or not payment reminder email messages are enabled for the product (subscription only)
                reminder_value:  "adhoc", "day", "week", "month", or "year",    // interval unit for payment reminder email messages (subscription only)
                reminder_count: <Integer>,                                      // number of interval units prior to the next billing date when payment reminder email messages will be sent (subscription only)
                payment_overdue: <Boolean>,                                     // controls whether or not payment overdue notification email messages are enabled for the product (subscription only)
                overdue_interval_value: "adhoc", "day", "week", "month", or "year",  // interval unit for payment overdue notification email messages (subscription only)
                overdue_interval_count: <Integer>,                              // total number of payment overdue notification messages to send (subscription only)
                overdue_interval_amount: <Integer>,                             // number of overdue_interval units between each payment overdue notification message (subscription only)
                cancellation_interval_count: <Integer>,                         // number of cancellation_interval units prior to subscription cancellation (subscription only) 
                cancellation_interval_value:  "adhoc", "day", "week", "month", or "year",  // interval unit for subscription cancellation (subscription only)
            },
            display: {                                                          // customer-visible product display name or title
                "language_code": <String> (e.g. "DE": "German text"),           // multiple languages supported, language must be enabled in Store settings
                ...
            },
            description: {                                                      // customer-visible product description
                "values": {
                    "full": {
                        "values": {
                            "language_key": "value" (e.g "en": "English description"),  // multiple languages supported, language must be enabled in Store settings
                            ...
                        }
                    }
                }
            },
            image: <String>,                                                    // product icon image file URL
            selected: <Boolean>,                                                // controls whether or not product is currently selected / in the cart
            removable: <Boolean>,                                               // controls whether or not product can be removed from the cart by the customer
            attributes: {
                key: <String>,                                                  // optional product attributes (maximum of approximately 400,000 characters)
                ...
            },
            sku: <String>                                                       // optional product SKU ID (e.g. to match your internal SKU or product ID)
        },
        ...
    ],
    tags: {                                                                     // optional order tags (maximum approximately 4,000 characters)

        key: <String>,
        ...
    }
}

 

Customer, recipient, and product information can all be passed in a single payload.

 

Passing Other Order Details in the Secure Payload

In addition to customer and product information, you can also optionally include a coupon, specify the country to be used for the order (which controls the currency for the order) and specify the order language via the secure payload.

 

Payload Example
{
    coupon: <String>,                                       // optional coupon code (not ID) to be applied to the order
    country: <String>,                                      // customer country - pass two-character ISO country code (e.g., "DE" for Germany); controls transaction currency
    language: <String>,                                     // customer language - pass two-character ISO language code (e.g., "DE" for German); controls language used in checkout
}

 

Authenticating Customers to Redirect to Account Management

If your backend is aware of the customer's FastSpring account ID you can generate a pre-authenticated URL and redirect customer to the account management area without the additional validation. 

Encrypt the following JSON on the backend:
{
  "account": "ID obtained from FS",
  "timestamp":  1427419618678  /* epoch time in milliseconds */
}

And call fastspring.builder.authenticate(securedData, secureKey) method passing secure data and secure key as described below. The customer will be redirected to the account management page.

 

Flow

  1. Generate Private/Public key pair
  2. Build a JSON object (as string) that contains the required data/actions on your backend.
  3. Generate "Secure Key", encrypt JSON object (as string) using the Private key.
  4. Pass resulting strings (encrypted payload and secure key) to your frontend and assign them to Javascript variables.
  5. Make sure you provided "data-access-key" when initializing the Library, otherwise the secure payload won't be sent. 
 

Creating Encrypted Payloads

Setting Up Encryption

  1. In the Dashboard, select the Integrations menu and the Store Builder Library tab. 
  2. Copy the Access Key. This is the access key with which you will initialize the API.
  3. Create a Private/Public certificate pair (see instructions below). Under File Upload, click Choose File and browse to and select the file containing your public certificate.
  4. Click SAVE.

Generating "securePayload" and "secureKey"

One-Time Only - Create Private and Public Keys

  • Create a 2048-bit RSA private key. Do not share this key. You will use the private key PEM file privatekey.pem to create your "secureKey".
openssl genrsa -out privatekey.pem 2048
  • Create a 2048-bit RSA public key. Only share this key with FastSpring. FastSpring will use your public key PEM file publiccert.pem to decrypt your payload.
openssl req -new -key privatekey.pem -x509 -days 3650 -out publiccert.pem

Create Your "Secure Key" and "Secure Payload" for Each Request

When preparing a secure request encrypt your generated payload.

Using Node.js

/**
* Encrypts a JSON payload given the private key
* corresponding to the public key stored in Dashboard
**/
const crypto = require('crypto');
module.exports = function encrypt(payload, privateKey) {
  const aesKey = crypto.randomBytes(16);
  const iv = new Buffer("");
  const cipher = crypto.createCipheriv('aes-128-ecb', aesKey, iv);
  var encryptedPayload = cipher.update(new Buffer(payload, 'utf8'), 'utf8', 'base64');
  const securePayload = encryptedPayload + cipher.final('base64');
  const secureKey = crypto.privateEncrypt(privateKey, aesKey).toString('base64');
  return {
     securePayload: securePayload,
     secureKey: secureKey
  };
};

Using Java

  • Create a new random 16-byte aesKey for each payload.
final KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
final SecretKey secretKey = keyGenerator.generateKey() ;
final byte[] aesKey = secretKey.getEncoded() ;
  • Use the random 16-byte aesKey to create the securePayload string from your unencryptedString (typically Base64 string, but could be raw byte[]).
final SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
final Cipher encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
final byte[] cleartext = unencryptedString.getBytes("UTF-8");
final byte[] ciphertext = encryptCipher.doFinal(cleartext);
final String securePayload = Base64.getEncoder().encodeToString(ciphertext);
  • Use your privateKeyPEM string to encrypt your aesKey to create your secureKey string.
final org.bouncycastle.openssl.PEMReader pemReader = new org.bouncycastle.openssl.PEMReader(new StringReader(privateKeyPEM));|
final KeyPair keyPair = (KeyPair) pemReader.readObject() ;
pemReader.close() ;
final PrivateKey privateKey = keyPair.getPrivate() ;
final Cipher clientPrivateCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") ;
clientPrivateCipher.init(Cipher.ENCRYPT_MODE, privateKey) ;
final byte[] aesKeyEncrypted = clientPrivateCipher.doFinal(aesKey) ;
final String secureKey = Base64.getEncoder().encodeToString(aesKeyEncrypted) ;

FastSpring uses your secureKey string and publiccert.pem to decrypt your securePayload string.

Using PHP

You can also download the working example from here: encryption.php

  • Create a new random 16 byte aesKey for each payload.
$aesKey = openssl_random_pseudo_bytes(16) ; # or urandom() or any other random byte generator
  • Use the random 16 byte aesKey to create the securePayload string from your unencryptedString (typically Base64 string, but could be raw byte[]).
$cipherText = openssl_encrypt($unencryptedString, "AES-128-ECB", $aesKey, OPENSSL_RAW_DATA) ;
$securePayload = base64_encode($cipherText) ;
  • Use your privatekey.pem file to encrypt your aesKey to create your secureKey string.
$privateKeyFileName = realpath("privatekey.pem") ;
$privateKey = openssl_pkey_get_private("file://$privateKeyFileName") ;
openssl_private_encrypt($aesKey, $aesKeyEncrypted, $privateKey) ;
$secureKey = base64_encode($aesKeyEncrypted) ;

FastSpring uses your secureKey string and publiccert.pem to decrypt your securePayload string.

 

Passing an Encrypted Payload to the Store Builder Library

Option 1: Build the sessionObject before the API is initialized

  This method implies understanding a concept of the Session Object. Refer to Accessing the Library from Javascript for more information.

This method works best if a secure payload is available to the page at the moment the page loads. This way you will also apply the data in the first Library call to server.

<script>
var fscSession = {
	'secure': {
		'payload': securedData, // string of encrypted data passed from the server
		'key': secureKey // secure key passed from the server
	}
}
</script>
<!-- placing the session object before the Library is initialized ensures that the data will be sent with the very first request -->
<script
	id="fsc-api"
	src="https://d1f8f9xcsvx3ha.cloudfront.net/sbl/0.8.2/fastspring-builder.min.js" type="text/javascript" 
...

Option 2: Use the fastspring.builder.secure() method after the API is initialized

<script>
	fastspring.builder.secure(securedData, secureKey);
</script>
 

Testing Secure Payloads

To simplify the testing process, "Test" storefronts accept unencrypted payloads. Each storefront can be used in both Live and Test modes simultaneously. For example:

To test your payload:

  • Pass JSON as object to your frontend
  • Send JSON object in a secure payload call leaving "key" empty. 

    <script>
    	fastspring.builder.secure(nonEncryptedJSON, '');
    </script>

Try FastSpring

Get a free account and see why FastSpring is the ecommerce partner of choice for software providers around the world. Try our full-service ecommerce solution today to unlock revenue growth for your online company.