diff --git a/integrations/appboy/lib/index.js b/integrations/appboy/lib/index.js
index bf861e7af..17e10539e 100644
--- a/integrations/appboy/lib/index.js
+++ b/integrations/appboy/lib/index.js
@@ -407,19 +407,9 @@ Appboy.prototype.orderCompleted = function(track) {
del(purchaseProperties, 'currency');
// we have to make a separate call to appboy for each product
- each(function(product) {
- var track = new Track({ properties: product });
- var productId = track.productId();
- var price = track.price();
- var quantity = track.quantity();
- window.appboy.logPurchase(
- productId,
- price,
- currencyCode,
- quantity,
- purchaseProperties
- );
- }, products);
+ for (var i = 0; i < products.length; i++) {
+ logProduct(products[i], currencyCode, purchaseProperties);
+ }
};
/**
@@ -449,3 +439,92 @@ function getGender(gender) {
if (otherGenders.indexOf(gender.toLowerCase()) > -1)
return window.appboy.ab.User.Genders.OTHER;
}
+
+/**
+ * Logs a Purchase containing a product as described in Braze's documentation:
+ * https://js.appboycdn.com/web-sdk/latest/doc/module-appboy.html#.logPurchase
+ *
+ * @param {Object} product Product from the Order Completed call
+ * @param {String} currencyCode Currency code
+ * @param {Object} extraProperties Root properties from the track call
+ */
+function logProduct(product, currencyCode, extraProperties) {
+ var track = new Track({ properties: product });
+ var productId = track.productId();
+ var price = track.price();
+ var quantity = track.quantity();
+ var productProperties = track.properties();
+ var properties = {};
+
+ del(productProperties, 'productId');
+ del(productProperties, 'price');
+ del(productProperties, 'quantity');
+
+ for (var productProperty in productProperties) {
+ if (!productProperties.hasOwnProperty(productProperty)) {
+ continue;
+ }
+
+ var value = productProperties[productProperty];
+ if (isValidProperty(productProperty, value)) {
+ properties[productProperty] = value;
+ }
+ }
+
+ for (var property in extraProperties) {
+ if (!extraProperties.hasOwnProperty(property)) {
+ continue;
+ }
+
+ var val = extraProperties[property];
+ if (
+ !productProperties.hasOwnProperty(property) &&
+ isValidProperty(property, val)
+ ) {
+ properties[property] = val;
+ }
+ }
+
+ window.appboy.logPurchase(
+ productId,
+ price,
+ currencyCode,
+ quantity,
+ properties
+ );
+}
+
+/**
+ * Validates a name and value of a property, following Braze's restrictions:
+ *
+ * Names are limited to 255 characters in length, cannot begin with a $, and
+ * can only contain alphanumeric characters and punctuation. Values can be
+ * numeric, boolean, Date objects, or strings 255 characters or shorter.
+ *
+ * @param {String} name Name of the property.
+ * @param {*} value Value of the property.
+ *
+ * @return {boolean} true
if the name and value are valid, false
otherwise.
+ */
+function isValidProperty(name, value) {
+ if (name.length > 255 || name.startsWith('$')) {
+ return false;
+ }
+
+ if (typeof value === 'number' || typeof value === 'boolean') {
+ return true;
+ }
+
+ if (typeof value === 'object' && value instanceof Date) {
+ return true;
+ }
+
+ if (
+ (typeof value === 'string' || value instanceof String) &&
+ value.length <= 255
+ ) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/integrations/appboy/package.json b/integrations/appboy/package.json
index fb6323da6..c5d13c9b8 100644
--- a/integrations/appboy/package.json
+++ b/integrations/appboy/package.json
@@ -1,7 +1,7 @@
{
"name": "@segment/analytics.js-integration-appboy",
"description": "The Appboy analytics.js integration.",
- "version": "1.9.0",
+ "version": "1.10.0",
"keywords": [
"analytics.js",
"analytics.js-integration",
diff --git a/integrations/appboy/test/index.test.js b/integrations/appboy/test/index.test.js
index 97cc4beee..eeeee9770 100644
--- a/integrations/appboy/test/index.test.js
+++ b/integrations/appboy/test/index.test.js
@@ -440,11 +440,15 @@ describe('Appboy', function() {
});
it('should call logPurchase for each product in a Completed Order event', function() {
+ var today = new Date();
+
analytics.track('Order Completed', {
total: 30,
revenue: 25,
shipping: 3,
currency: 'USD',
+ date: today,
+ invalidPropertyLength: 'a'.repeat(500),
products: [
{
product_id: '507f1f77bcf86cd799439011',
@@ -452,7 +456,9 @@ describe('Appboy', function() {
name: 'Monopoly: 3rd Edition',
price: 19.23,
quantity: 1,
- category: 'Games'
+ category: 'Games',
+ $invalidPropertyName: 3,
+ invalidPropertyValue: ['red', 'blue']
},
{
product_id: '505bd76785ebb509fc183733',
@@ -460,7 +466,8 @@ describe('Appboy', function() {
name: 'Uno Card Game',
price: 3,
quantity: 2,
- category: 'Games'
+ category: 'Games',
+ size: 6
}
]
});
@@ -473,7 +480,11 @@ describe('Appboy', function() {
{
total: 30,
revenue: 25,
- shipping: 3
+ shipping: 3,
+ sku: '45790-32',
+ name: 'Monopoly: 3rd Edition',
+ category: 'Games',
+ date: today
}
);
analytics.called(
@@ -485,7 +496,12 @@ describe('Appboy', function() {
{
total: 30,
revenue: 25,
- shipping: 3
+ shipping: 3,
+ sku: '46493-32',
+ name: 'Uno Card Game',
+ category: 'Games',
+ size: 6,
+ date: today
}
);
});