diff --git a/Kits/RoktStripePayment/package.json b/Kits/RoktStripePayment/package.json
new file mode 100644
index 0000000..e8a7e9e
--- /dev/null
+++ b/Kits/RoktStripePayment/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@mparticle/cordova-rokt-stripe-payment-kit",
+ "version": "3.0.0",
+ "description": "Adds Rokt Stripe payment extension (Apple Pay) support to your Cordova project",
+ "homepage": "https://www.mparticle.com",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/mParticle/cordova-plugin-mparticle",
+ "directory": "Kits/RoktStripePayment"
+ },
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "provenance": true,
+ "registry": "https://registry.npmjs.org/"
+ },
+ "cordova": {
+ "id": "@mparticle/cordova-rokt-stripe-payment-kit",
+ "platforms": [
+ "ios"
+ ]
+ }
+}
diff --git a/Kits/RoktStripePayment/plugin.xml b/Kits/RoktStripePayment/plugin.xml
new file mode 100644
index 0000000..206963c
--- /dev/null
+++ b/Kits/RoktStripePayment/plugin.xml
@@ -0,0 +1,17 @@
+
+
+ MParticle Rokt Stripe Payment Kit
+ Adds Rokt Stripe payment extension (Apple Pay) support to your Cordova project
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 6dd57a0..88a784f 100755
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ cordova plugin add @mparticle/cordova-sdk
**Install the SDK** using CocoaPods:
```bash
-$ # Update your Podfile to depend on 'mParticle-Apple-SDK' version 8.5.2 or later
+$ # Update your Podfile to depend on 'mParticle-Apple-SDK' version 9.0 or later
$ pod install
```
@@ -264,6 +264,89 @@ identity.modify(request, function (userId) => {
});
```
+## Rokt
+
+### Installation
+
+Add the Rokt kit to your `config.xml`:
+
+```xml
+
+```
+
+### Select Placements
+
+Display a Rokt overlay placement on your confirmation or thank-you page:
+
+```js
+var attributes = {
+ 'email': 'j.smith@example.com',
+ 'firstname': 'Jenny',
+ 'lastname': 'Smith',
+ 'confirmationref': '54321',
+ 'country': 'US'
+};
+
+mparticle.Rokt.selectPlacements('YourPlacementIdentifier', attributes);
+```
+
+You can optionally pass a configuration object:
+
+```js
+var config = {
+ colorMode: mparticle.Rokt.ColorMode.SYSTEM, // LIGHT, DARK, or SYSTEM
+ cacheConfig: {
+ cacheDurationInSeconds: 5400,
+ cacheAttributes: {}
+ }
+};
+
+mparticle.Rokt.selectPlacements('YourPlacementIdentifier', attributes, config);
+```
+
+### Shoppable Ads
+
+Shoppable Ads enable post-purchase upsell offers with instant checkout via Apple Pay or Stripe. Currently supported on **iOS only**.
+
+#### 1. Add the Stripe payment kit
+
+```xml
+
+```
+
+#### 2. Register the payment extension
+
+The `RoktStripePaymentExtension` must be registered in your native iOS code after SDK initialization and before calling `selectShoppableAds`.
+
+Since `RoktStripePaymentExtension` is Swift-only, ObjC apps need a bridging class. See `example/platform_overrides/ios/RoktPaymentSetup.swift` for the wrapper, then call from your AppDelegate:
+
+```objc
+#import "YourApp-Swift.h"
+
+[RoktPaymentSetup registerPaymentExtensionWithMerchantId:@"merchant.com.yourapp.rokt"];
+```
+
+#### 3. Display Shoppable Ads
+
+```js
+var attributes = {
+ 'email': 'j.smith@example.com',
+ 'firstname': 'Jenny',
+ 'lastname': 'Smith',
+ 'confirmationref': 'ORD-8829-XK2',
+ 'amount': '52.25',
+ 'currency': 'USD',
+ 'paymenttype': 'visa',
+ 'shippingaddress1': '123 Main St',
+ 'shippingcity': 'Brooklyn',
+ 'shippingstate': 'NY',
+ 'shippingzipcode': '11201',
+ 'shippingcountry': 'US'
+};
+
+mparticle.Rokt.selectShoppableAds('YourPlacementIdentifier', attributes);
+```
+
# License
Apache 2.0
diff --git a/example/config.xml b/example/config.xml
index f226e75..3de2177 100644
--- a/example/config.xml
+++ b/example/config.xml
@@ -30,4 +30,5 @@
+
diff --git a/example/hooks/after_platform_add/010_copy_platform_specific_files.js b/example/hooks/after_platform_add/010_copy_platform_specific_files.js
index e8b9e0a..41a9818 100755
--- a/example/hooks/after_platform_add/010_copy_platform_specific_files.js
+++ b/example/hooks/after_platform_add/010_copy_platform_specific_files.js
@@ -7,7 +7,7 @@ var shell = require('child_process');
module.exports = function(context) {
// Get platform from context
var platform = context.opts.platforms[0];
-
+
function ensureDirectoryExistence(filePath) {
var dirname = path.dirname(filePath);
if (fs.existsSync(dirname)) {
@@ -16,35 +16,64 @@ module.exports = function(context) {
fs.mkdirSync(dirname, { recursive: true });
}
- if (platform === 'android') {
- // Copy MainActivity.java
- var srcFile = path.join(context.opts.projectRoot, 'platform_overrides', 'android', 'MainActivity.java');
- var destFile = path.join(context.opts.projectRoot, 'platforms', 'android', 'app', 'src', 'main', 'java', 'com', 'mparticle', 'example', 'MainActivity.java');
-
- if (fs.existsSync(srcFile)) {
+ function copyFile(srcRelative, destRelative, label) {
+ var src = path.join(context.opts.projectRoot, srcRelative);
+ var dest = path.join(context.opts.projectRoot, destRelative);
+ if (fs.existsSync(src)) {
try {
- ensureDirectoryExistence(destFile);
- fs.copyFileSync(srcFile, destFile);
- console.log('Copied custom MainActivity.java');
+ ensureDirectoryExistence(dest);
+ fs.copyFileSync(src, dest);
+ console.log('Copied ' + label);
} catch (err) {
- console.error('Error copying MainActivity.java:', err);
+ console.error('Error copying ' + label + ':', err);
process.exitCode = 1;
}
}
+ }
+
+ if (platform === 'android') {
+ copyFile(
+ path.join('platform_overrides', 'android', 'MainActivity.java'),
+ path.join('platforms', 'android', 'app', 'src', 'main', 'java', 'com', 'mparticle', 'example', 'MainActivity.java'),
+ 'custom MainActivity.java'
+ );
} else if (platform === 'ios') {
- // Copy AppDelegate.m
- var srcFile = path.join(context.opts.projectRoot, 'platform_overrides', 'ios', 'AppDelegate.m');
- var destFile = path.join(context.opts.projectRoot, 'platforms', 'ios', 'MParticleExample', 'AppDelegate.m');
-
- if (fs.existsSync(srcFile)) {
+ copyFile(
+ path.join('platform_overrides', 'ios', 'AppDelegate.m'),
+ path.join('platforms', 'ios', 'MParticleExample', 'AppDelegate.m'),
+ 'custom AppDelegate.m'
+ );
+
+ copyFile(
+ path.join('platform_overrides', 'ios', 'RoktPaymentSetup.swift'),
+ path.join('platforms', 'ios', 'MParticleExample', 'RoktPaymentSetup.swift'),
+ 'RoktPaymentSetup.swift'
+ );
+
+ // Create bridging header for Swift/ObjC interop
+ var bridgingHeaderPath = path.join(context.opts.projectRoot, 'platforms', 'ios', 'MParticleExample', 'MParticleExample-Bridging-Header.h');
+ if (!fs.existsSync(bridgingHeaderPath)) {
try {
- ensureDirectoryExistence(destFile);
- fs.copyFileSync(srcFile, destFile);
- console.log('Copied custom AppDelegate.m');
+ fs.writeFileSync(bridgingHeaderPath, '// Bridging header for Swift/ObjC interop\n');
+ console.log('Created bridging header');
} catch (err) {
- console.error('Error copying AppDelegate.m:', err);
+ console.error('Error creating bridging header:', err);
process.exitCode = 1;
}
}
+
+ // NOTE: RoktStripePaymentExtension pod is added automatically by the
+ // @mparticle/cordova-rokt-stripe-payment-kit declared in config.xml.
+ // If using a kit, no manual Podfile manipulation is needed here.
+
+ // Add Swift file to Xcode project and configure Swift settings
+ var iosPath = path.join(context.opts.projectRoot, 'platforms', 'ios');
+ var configureXcodeScript = path.join(context.opts.projectRoot, 'hooks', 'after_platform_add', 'configure_xcode_swift.rb');
+ try {
+ shell.execSync('cd ' + iosPath + ' && ruby ' + configureXcodeScript, { stdio: 'inherit' });
+ } catch (err) {
+ console.error('Error configuring Xcode project for Swift:', err);
+ process.exitCode = 1;
+ }
}
};
diff --git a/example/hooks/after_platform_add/configure_xcode_swift.rb b/example/hooks/after_platform_add/configure_xcode_swift.rb
new file mode 100644
index 0000000..0002cda
--- /dev/null
+++ b/example/hooks/after_platform_add/configure_xcode_swift.rb
@@ -0,0 +1,16 @@
+require 'xcodeproj'
+
+proj = Xcodeproj::Project.open('MParticleExample.xcodeproj')
+target = proj.targets.find { |t| t.name == 'MParticleExample' }
+group = proj.main_group.find_subpath('MParticleExample', false)
+
+swift_ref = group.new_file('RoktPaymentSetup.swift')
+target.source_build_phase.add_file_reference(swift_ref)
+
+target.build_configurations.each do |config|
+ config.build_settings['SWIFT_VERSION'] = '5.0'
+ config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] = 'MParticleExample/MParticleExample-Bridging-Header.h'
+end
+
+proj.save
+puts 'Configured Xcode project for Swift/Shoppable Ads'
diff --git a/example/platform_overrides/ios/AppDelegate.m b/example/platform_overrides/ios/AppDelegate.m
index 798becb..23b1dd2 100644
--- a/example/platform_overrides/ios/AppDelegate.m
+++ b/example/platform_overrides/ios/AppDelegate.m
@@ -25,6 +25,7 @@ Licensed to the Apache Software Foundation (ASF) under one
#import "AppDelegate.h"
#import "MainViewController.h"
#import "mParticle.h"
+#import "MParticleExample-Swift.h"
@implementation AppDelegate
@@ -41,8 +42,11 @@ - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(N
};
[[MParticle sharedInstance] startWithOptions:mParticleOptions];
-
+
[MParticle sharedInstance].logLevel = MPILogLevelVerbose;
+
+ // Register Rokt Stripe payment extension for Shoppable Ads
+ [RoktPaymentSetup registerPaymentExtensionWithMerchantId:@"merchant.com.mparticle.example"];
self.viewController = [[MainViewController alloc] init];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
diff --git a/example/platform_overrides/ios/RoktPaymentSetup.swift b/example/platform_overrides/ios/RoktPaymentSetup.swift
new file mode 100644
index 0000000..80f7174
--- /dev/null
+++ b/example/platform_overrides/ios/RoktPaymentSetup.swift
@@ -0,0 +1,11 @@
+import Foundation
+import RoktStripePaymentExtension
+import mParticle_Apple_SDK_ObjC
+
+@objc public class RoktPaymentSetup: NSObject {
+ @objc public static func registerPaymentExtension(merchantId: String) {
+ if let stripeExt = RoktStripePaymentExtension(applePayMerchantId: merchantId) {
+ MParticle.sharedInstance().rokt.register(stripeExt)
+ }
+ }
+}
diff --git a/example/www/index.html b/example/www/index.html
index 52d6a92..9c4cb26 100644
--- a/example/www/index.html
+++ b/example/www/index.html
@@ -44,6 +44,8 @@
mParticle
Rokt
+
+
diff --git a/example/www/js/index.js b/example/www/js/index.js
index 264060e..606b903 100644
--- a/example/www/js/index.js
+++ b/example/www/js/index.js
@@ -12,6 +12,8 @@ document.getElementById("setATTStatusBtn").addEventListener('click', setATTStatu
document.getElementById("loginBtn").addEventListener('click', login, false);
document.getElementById("logoutBtn").addEventListener('click', logout, false);
document.getElementById("trackConversionBtn").addEventListener('click', trackConversion, false);
+document.getElementById("selectShoppableAdsBtn").addEventListener('click', selectShoppableAds, false);
+document.getElementById("purchaseFinalizedBtn").addEventListener('click', purchaseFinalized, false);
var app = {
// Application Constructor
@@ -244,4 +246,37 @@ function trackConversion() {
);
}
+function selectShoppableAds() {
+ console.log('MParticleCordova Plugin Example: Selecting Shoppable Ads');
+
+ var attributes = {
+ 'email': 'j.smith@example.com',
+ 'firstname': 'Jenny',
+ 'lastname': 'Smith',
+ 'country': 'US',
+ 'shippingstate': 'NY',
+ 'shippingzipcode': '10001',
+ 'confirmationref': '54321'
+ };
+
+ var config = {
+ colorMode: mparticle.Rokt.ColorMode.SYSTEM,
+ cacheConfig: {
+ cacheDurationInSeconds: 5400,
+ cacheAttributes: {}
+ }
+ };
+
+ mparticle.Rokt.selectShoppableAds(
+ 'StgRoktShoppableAds',
+ attributes,
+ config
+ );
+}
+
+function purchaseFinalized() {
+ console.log('MParticleCordova Plugin Example: Purchase Finalized');
+ mparticle.Rokt.purchaseFinalized('StgRoktShoppableAds', 'catalog-item-123', true);
+}
+
app.initialize();
diff --git a/plugin/src/android/src/main/java/com/mparticle/cordova/MParticleCordovaPlugin.java b/plugin/src/android/src/main/java/com/mparticle/cordova/MParticleCordovaPlugin.java
index 189a68a..e5573cd 100644
--- a/plugin/src/android/src/main/java/com/mparticle/cordova/MParticleCordovaPlugin.java
+++ b/plugin/src/android/src/main/java/com/mparticle/cordova/MParticleCordovaPlugin.java
@@ -96,6 +96,12 @@ public boolean execute(String action, final JSONArray args, final CallbackContex
} else if (action.equals("selectPlacements")) {
selectPlacements(args);
return true;
+ } else if (action.equals("selectShoppableAds")) {
+ selectShoppableAds(args);
+ return true;
+ } else if (action.equals("purchaseFinalized")) {
+ purchaseFinalized(args);
+ return true;
} else {
return false;
}
@@ -442,6 +448,18 @@ public void selectPlacements(final JSONArray args) throws JSONException {
);
}
+ public void selectShoppableAds(final JSONArray args) throws JSONException {
+ Logger.warning("selectShoppableAds is not yet supported on Android");
+ }
+
+ public void purchaseFinalized(final JSONArray args) throws JSONException {
+ final String placementId = args.getString(0);
+ final String catalogItemId = args.getString(1);
+ final boolean success = args.getBoolean(2);
+
+ MParticle.getInstance().Rokt().purchaseFinalized(placementId, catalogItemId, success);
+ }
+
private static IdentityApiRequest ConvertIdentityAPIRequest(JSONObject map) throws JSONException {
IdentityApiRequest.Builder identityRequest = IdentityApiRequest.withEmptyUser();
diff --git a/plugin/src/ios/CDVMParticle.m b/plugin/src/ios/CDVMParticle.m
index d08e350..688bef1 100644
--- a/plugin/src/ios/CDVMParticle.m
+++ b/plugin/src/ios/CDVMParticle.m
@@ -330,7 +330,7 @@ - (void)getUserIdentities:(CDVInvokedUrlCommand*)command {
}
- (RoktConfig *)buildRoktConfigFromDict:(NSDictionary *)configDict {
- if (configDict == nil || configDict.count == 0) {
+ if (configDict == nil || [configDict isKindOfClass:[NSNull class]] || configDict.count == 0) {
return nil;
}
@@ -384,6 +384,39 @@ - (void)selectPlacements:(CDVInvokedUrlCommand*)command {
}];
}
+- (void)selectShoppableAds:(CDVInvokedUrlCommand*)command {
+ [self.commandDelegate runInBackground:^{
+ NSString *identifier = [command.arguments objectAtIndex:0];
+ NSDictionary *attributes = [command.arguments objectAtIndex:1];
+ NSDictionary *configDict = [command.arguments objectAtIndex:2];
+
+ RoktConfig *config = [self buildRoktConfigFromDict:configDict];
+
+ [[MParticle sharedInstance].rokt selectShoppableAds:identifier
+ attributes:attributes
+ config:config
+ onEvent:nil];
+
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+ }];
+}
+
+- (void)purchaseFinalized:(CDVInvokedUrlCommand*)command {
+ [self.commandDelegate runInBackground:^{
+ NSString *identifier = [command.arguments objectAtIndex:0];
+ NSString *catalogItemId = [command.arguments objectAtIndex:1];
+ BOOL success = [[command.arguments objectAtIndex:2] boolValue];
+
+ [[MParticle sharedInstance].rokt purchaseFinalized:identifier
+ catalogItemId:catalogItemId
+ success:success];
+
+ CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
+ [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
+ }];
+}
+
typedef NS_ENUM(NSUInteger, MPCDVCommerceEventAction) {
MPCDVCommerceEventActionAddToCart = 1,
MPCDVCommerceEventActionRemoveFromCart,
diff --git a/plugin/www/mparticle.js b/plugin/www/mparticle.js
index 69797b6..e195dda 100644
--- a/plugin/www/mparticle.js
+++ b/plugin/www/mparticle.js
@@ -482,6 +482,35 @@ var mparticle = {
}
exec('selectPlacements', [identifier, attributes || {}, finalConfig])
+ },
+
+ selectShoppableAds: function (identifier, attributes, config) {
+ var finalConfig = null
+ if (config) {
+ var requestedColorMode = config.colorMode || mparticle.Rokt.ColorMode.SYSTEM
+ requestedColorMode = mparticle.Rokt.validateColorMode(requestedColorMode)
+
+ finalConfig = {
+ colorMode: {
+ value: requestedColorMode
+ }
+ }
+
+ if (config.cacheConfig) {
+ finalConfig.cacheConfig = {
+ cacheDurationInSeconds: config.cacheConfig.cacheDurationInSeconds != null
+ ? config.cacheConfig.cacheDurationInSeconds
+ : 5400,
+ cacheAttributes: config.cacheConfig.cacheAttributes || {}
+ }
+ }
+ }
+
+ exec('selectShoppableAds', [identifier, attributes || {}, finalConfig])
+ },
+
+ purchaseFinalized: function (placementId, catalogItemId, success) {
+ exec('purchaseFinalized', [placementId, catalogItemId, !!success])
}
}
}