Skip to content

Commit 7ee2b02

Browse files
thomson-tclaude
andauthored
feat: adopt Rokt SDK 5.1.0 and add MPRokt.handleURLCallback (#759)
Bumps the Rokt dependencies to pick up Afterpay payment support shipped in Rokt SDK 5.1.0 and PayPal / PaymentPreparation totals shipped in RoktContracts 2.0.0, and exposes a new MPRokt.handleURLCallback: passthrough so partners can complete redirect-based payment flows through the mParticle facade. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 13f6230 commit 7ee2b02

16 files changed

Lines changed: 296 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,29 @@ For each release, **Core** (main SDK) changes are listed first, followed by **Ki
1010

1111
## [Unreleased]
1212

13+
### Core
14+
15+
#### Added
16+
17+
- Add `MPRokt.handleURLCallback:` for forwarding Afterpay/PayPal redirect URLs to the registered Rokt payment extension. Call from `application:openURL:options:` (AppDelegate) or `scene:openURLContexts:` / `.onOpenURL` (Scene/SwiftUI).
18+
19+
#### Changed
20+
21+
- Bump minimum `RoktContracts` to 2.0.0 (adds `PaymentMethodType.paypal` and totals on `PaymentPreparation`).
22+
23+
### Kits
24+
25+
#### Rokt
26+
27+
##### Changed
28+
29+
- Bump minimum `Rokt-Widget` to 5.1.0 (adds Afterpay payment support).
30+
- Bump minimum `RoktContracts` to 2.0.0.
31+
32+
##### Added
33+
34+
- Pass through `handleURLCallback:` to `Rokt.handleURLCallback(with:)` on the Rokt SDK.
35+
1336
## [9.0.1] - 2026-04-22
1437

1538
### Core

Example/Podfile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ target 'mParticleExample' do
1010

1111
pod 'mParticle-Apple-Media-SDK/mParticleMediaNoLocation', '~> 1.7.0'
1212

13-
# Rokt-Widget pulls in RoktUXHelper (~> 0.8) transitively. This explicit line is only to resolve it
13+
# Rokt-Widget pulls in RoktUXHelper (~> 0.10) transitively. This explicit line is only to resolve it
1414
# from Git (tag) instead of CocoaPods trunk when CDN/trunk is unreliable — remove it if trunk works.
15-
pod 'RoktUXHelper', :git => 'https://github.com/ROKT/rokt-ux-helper-ios.git', :tag => '0.8.3'
15+
pod 'RoktUXHelper', :git => 'https://github.com/ROKT/rokt-ux-helper-ios.git', :tag => '0.10.4'
1616

17-
# Rokt iOS SDK 5.x (aligned with Kits/rokt/rokt/Package.swift). Use Git branch; not on CocoaPods trunk.
18-
pod 'Rokt-Widget', :git => 'https://github.com/ROKT/rokt-sdk-ios.git', :branch => 'workstation/5.0.0'
17+
# Rokt iOS SDK 5.1.x (aligned with Kits/rokt/rokt/Package.swift). Use Git tag; not on CocoaPods trunk.
18+
pod 'Rokt-Widget', :git => 'https://github.com/ROKT/rokt-sdk-ios.git', :tag => '5.1.0'
1919

2020
# Local Rokt kit — uses the same local mParticle-Apple-SDK above (single resolved copy via :path).
21-
# RoktContracts resolves from CocoaPods trunk per podspecs (~> 0.1).
21+
# RoktContracts resolves from CocoaPods trunk per podspecs (~> 2.0).
2222
pod 'mParticle-Rokt', :path => '../Kits/rokt/rokt'
2323
#pod 'PLCrashReporter', '~> 1.11.1'
2424
#pod 'mParticle-UrbanAirship', :path => '../../mparticle-apple-integration-urbanairship'

Example/mParticleExample/AppDelegate.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
8686
}
8787

8888

89+
// Forward redirect URLs (e.g. Afterpay/PayPal) to Rokt so registered payment extensions can complete the payment flow.
90+
// If your app uses UIScene, call handleURLCallback(with:) from scene:openURLContexts: / .onOpenURL instead.
91+
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
92+
if ([[MParticle sharedInstance].rokt handleURLCallback:url]) {
93+
return YES;
94+
}
95+
return NO;
96+
}
97+
98+
8999
- (void)applicationWillResignActive:(UIApplication *)application {
90100
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
91101
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.

Kits/rokt/rokt/Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ let package = Package(
3232
mParticleAppleSDK,
3333
.package(
3434
url: "https://github.com/ROKT/rokt-sdk-ios",
35-
.upToNextMajor(from: "5.0.0")
35+
.upToNextMajor(from: "5.1.0")
3636
),
3737
.package(
3838
url: "https://github.com/ROKT/rokt-contracts-apple.git",
39-
.upToNextMajor(from: "0.1.3")
39+
.upToNextMajor(from: "2.0.0")
4040
),
4141
.package(
4242
url: "https://github.com/erikdoe/ocmock",

Kits/rokt/rokt/Sources/mParticle-Rokt/MPKitRokt.m

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,17 @@ - (MPKitExecStatus *)selectPlacementsWithIdentifier:(NSString * _Nullable)identi
149149
return [[MPKitExecStatus alloc] initWithSDKCode:[[self class] kitCode] returnCode:MPKitReturnCodeSuccess];
150150
}
151151

152+
/// Forwards a redirect URL to the Rokt SDK so registered payment extensions (Afterpay, PayPal) can claim it.
153+
/// - Parameter url: The URL received by the host app's URL handler.
154+
/// - Returns: YES if a registered payment extension claimed the URL.
155+
- (BOOL)handleURLCallback:(NSURL * _Nonnull)url {
156+
if (!url) {
157+
return NO;
158+
}
159+
[MPKitRokt MPLog:[NSString stringWithFormat:@"Rokt Kit handleURLCallback: %@", url]];
160+
return [Rokt handleURLCallbackWith:url];
161+
}
162+
152163
/// Forwards to Rokt Shoppable payment registration. When kit \c configuration includes \c stripePublishableKey (mParticle kit settings), it is passed to Rokt as \c stripeKey in the registration config.
153164
- (MPKitExecStatus *)registerPaymentExtension:(id<RoktPaymentExtension> _Nonnull)paymentExtension {
154165
if (!paymentExtension) {

Kits/rokt/rokt/Tests/mParticle-RoktObjCTests/mParticle_RoktTests.m

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ + (void)logSelectShoppableAdsEvent:(NSDictionary<NSString *, NSString *> * _Nonn
4343

4444
- (MPKitExecStatus *)registerPaymentExtension:(id<RoktPaymentExtension>)paymentExtension;
4545

46+
- (BOOL)handleURLCallback:(NSURL *)url;
47+
4648
- (MPKitExecStatus *)selectShoppableAdsWithIdentifier:(NSString *)identifier
4749
attributes:(NSDictionary<NSString *, NSString *> *)attributes
4850
config:(RoktConfig *)config
@@ -1106,6 +1108,43 @@ - (void)testRegisterPaymentExtensionForwardsToRoktWithConfigurationStripeKey {
11061108
[mockRoktSDK stopMocking];
11071109
}
11081110

1111+
- (void)testHandleURLCallbackForwardsToRoktAndReturnsYES {
1112+
id mockRoktSDK = OCMClassMock([Rokt class]);
1113+
NSURL *url = [NSURL URLWithString:@"myapp://afterpay-redirect?token=xyz"];
1114+
OCMExpect([mockRoktSDK handleURLCallbackWith:url]).andReturn(YES);
1115+
1116+
BOOL handled = [self.kitInstance handleURLCallback:url];
1117+
1118+
XCTAssertTrue(handled);
1119+
OCMVerifyAll(mockRoktSDK);
1120+
[mockRoktSDK stopMocking];
1121+
}
1122+
1123+
- (void)testHandleURLCallbackForwardsToRoktAndReturnsNO {
1124+
id mockRoktSDK = OCMClassMock([Rokt class]);
1125+
NSURL *url = [NSURL URLWithString:@"myapp://unrelated"];
1126+
OCMExpect([mockRoktSDK handleURLCallbackWith:url]).andReturn(NO);
1127+
1128+
BOOL handled = [self.kitInstance handleURLCallback:url];
1129+
1130+
XCTAssertFalse(handled);
1131+
OCMVerifyAll(mockRoktSDK);
1132+
[mockRoktSDK stopMocking];
1133+
}
1134+
1135+
- (void)testHandleURLCallbackNilURLReturnsNOWithoutForwarding {
1136+
id mockRoktSDK = OCMClassMock([Rokt class]);
1137+
OCMReject([mockRoktSDK handleURLCallbackWith:OCMOCK_ANY]);
1138+
1139+
#pragma clang diagnostic push
1140+
#pragma clang diagnostic ignored "-Wnonnull"
1141+
BOOL handled = [self.kitInstance handleURLCallback:nil];
1142+
#pragma clang diagnostic pop
1143+
1144+
XCTAssertFalse(handled);
1145+
[mockRoktSDK stopMocking];
1146+
}
1147+
11091148
- (void)testSelectShoppableAdsInvokesRoktAndLogsEvent {
11101149
id mockRoktSDK = OCMClassMock([Rokt class]);
11111150
OCMExpect([mockRoktSDK selectShoppableAdsWithIdentifier:@"ShopView"

Kits/rokt/rokt/mParticle-Rokt.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ Pod::Spec.new do |s|
1414
s.ios.source_files = 'Sources/mParticle-Rokt/**/*.{h,m}', 'Sources/mParticle-Rokt-Swift/**/*.swift'
1515
s.ios.resource_bundles = { 'mParticle-Rokt-Privacy' => ['Sources/mParticle-Rokt/PrivacyInfo.xcprivacy'] }
1616
s.ios.dependency 'mParticle-Apple-SDK', '~> 9.0'
17-
s.ios.dependency 'RoktContracts', '~> 0.1'
18-
s.ios.dependency 'Rokt-Widget', '~> 5.0'
17+
s.ios.dependency 'RoktContracts', '~> 2.0'
18+
s.ios.dependency 'Rokt-Widget', '~> 5.1'
1919
end

MIGRATING.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,57 @@
44

55
This document provides migration guidance for breaking changes in the mParticle Apple SDK.
66

7+
## Migrating from 9.0.x to 9.1.0
8+
9+
### Rokt SDK 5.1.0 + RoktContracts 2.0.0
10+
11+
SDK 9.1.0 bumps the Rokt dependencies to pick up Afterpay payment support shipped in Rokt SDK 5.1.0 and PayPal / payment‑preparation totals shipped in RoktContracts 2.0.0. The dependency minimums moved from:
12+
13+
- `Rokt-Widget` `~> 5.0``~> 5.1`
14+
- `RoktContracts` `~> 0.1``~> 2.0`
15+
16+
If your `Podfile` pins `RoktContracts '~> 0.1'` transitively, loosen it to `'~> 2.0'` so CocoaPods can resolve the new minimum. SPM users using the mParticle manifest inherit the new bound automatically.
17+
18+
The payment extension package previously published at `https://github.com/ROKT/rokt-stripe-payment-extension-ios` has been **renamed to `https://github.com/ROKT/rokt-payment-extension-ios`** (the old name implied Stripe‑only, but it now supports Afterpay and PayPal). Update any `Package.swift` / `Podfile` references to the new URL. mParticle does not depend on this package directly — it is pulled in by partners alongside `mParticle-Rokt`.
19+
20+
### New `MPRokt.handleURLCallback:` API
21+
22+
Redirect‑based payment flows (Afterpay, PayPal) send users into a web view and then back into your app via a registered URL scheme. To complete the round trip, forward the incoming URL to Rokt via `MPRokt.handleURLCallback:`. Call it from your URL handler and return the `BOOL` it produces so the system knows the URL was handled.
23+
24+
**Objective‑C (AppDelegate):**
25+
26+
```objective-c
27+
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
28+
if ([[MParticle sharedInstance].rokt handleURLCallback:url]) {
29+
return YES;
30+
}
31+
return NO;
32+
}
33+
```
34+
35+
**Swift (SceneDelegate):**
36+
37+
```swift
38+
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
39+
for urlContext in URLContexts {
40+
_ = MParticle.sharedInstance().rokt.handleURLCallback(with: urlContext.url)
41+
}
42+
}
43+
```
44+
45+
**Swift (SwiftUI):**
46+
47+
```swift
48+
WindowGroup {
49+
ContentView()
50+
.onOpenURL { url in
51+
_ = MParticle.sharedInstance().rokt.handleURLCallback(with: url)
52+
}
53+
}
54+
```
55+
56+
Your `Info.plist` setup is unchanged — register the URL scheme you pass to your `RoktPaymentExtension` initializer under `CFBundleURLTypes` exactly as in a direct‑Rokt integration.
57+
758
## Migrating from versions < 9.0.0
859

960
### Removed AppDelegateProxy

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ let package = Package(
1414
dependencies: [
1515
.package(
1616
url: "https://github.com/ROKT/rokt-contracts-apple.git",
17-
.upToNextMajor(from: "0.1.0")
17+
.upToNextMajor(from: "2.0.0")
1818
)
1919
],
2020
targets: [

UnitTests/ObjCTests/MPRoktTests.m

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,23 @@
1414
// Rokt kit identifier for testing
1515
static NSNumber * const kTestRoktKitId = @181;
1616

17-
// Test helper class that simulates a kit with getSessionId method
17+
// Test helper class that simulates a kit with getSessionId and handleURLCallback methods
1818
@interface MPRoktTestKitInstance : NSObject
1919
@property (nonatomic, copy) NSString *sessionIdToReturn;
20+
@property (nonatomic, assign) BOOL handleURLCallbackReturn;
21+
@property (nonatomic, strong) NSURL *lastHandleURLCallbackURL;
2022
- (NSString *)getSessionId;
23+
- (BOOL)handleURLCallback:(NSURL *)url;
2124
@end
2225

2326
@implementation MPRoktTestKitInstance
2427
- (NSString *)getSessionId {
2528
return self.sessionIdToReturn;
2629
}
30+
- (BOOL)handleURLCallback:(NSURL *)url {
31+
self.lastHandleURLCallbackURL = url;
32+
return self.handleURLCallbackReturn;
33+
}
2734
@end
2835

2936
@interface MPRokt ()
@@ -1168,4 +1175,92 @@ - (void)testGetSessionIdReturnsNilWhenKitInstanceIsNil {
11681175
XCTAssertNil(result, @"Should return nil when kit wrapper instance is nil");
11691176
}
11701177

1171-
@end
1178+
#pragma mark - handleURLCallback Tests
1179+
1180+
- (void)testHandleURLCallbackReturnsYESWhenKitClaimsURL {
1181+
MParticle *instance = [MParticle sharedInstance];
1182+
self.mockInstance = OCMPartialMock(instance);
1183+
self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]);
1184+
[[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE];
1185+
[[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance];
1186+
1187+
id mockKitRegister = OCMProtocolMock(@protocol(MPExtensionKitProtocol));
1188+
OCMStub([(id<MPExtensionKitProtocol>)mockKitRegister code]).andReturn(kTestRoktKitId);
1189+
1190+
MPRoktTestKitInstance *kitInstance = [[MPRoktTestKitInstance alloc] init];
1191+
kitInstance.handleURLCallbackReturn = YES;
1192+
OCMStub([mockKitRegister wrapperInstance]).andReturn(kitInstance);
1193+
1194+
OCMStub([self.mockContainer activeKitsRegistry]).andReturn(@[mockKitRegister]);
1195+
1196+
NSURL *url = [NSURL URLWithString:@"myapp://afterpay-redirect?token=abc"];
1197+
BOOL result = [self.rokt handleURLCallback:url];
1198+
1199+
XCTAssertTrue(result, @"Should return YES when the kit claims the URL");
1200+
XCTAssertEqualObjects(kitInstance.lastHandleURLCallbackURL, url, @"Kit should have received the URL");
1201+
}
1202+
1203+
- (void)testHandleURLCallbackReturnsNOWhenKitDoesNotClaimURL {
1204+
MParticle *instance = [MParticle sharedInstance];
1205+
self.mockInstance = OCMPartialMock(instance);
1206+
self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]);
1207+
[[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE];
1208+
[[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance];
1209+
1210+
id mockKitRegister = OCMProtocolMock(@protocol(MPExtensionKitProtocol));
1211+
OCMStub([(id<MPExtensionKitProtocol>)mockKitRegister code]).andReturn(kTestRoktKitId);
1212+
1213+
MPRoktTestKitInstance *kitInstance = [[MPRoktTestKitInstance alloc] init];
1214+
kitInstance.handleURLCallbackReturn = NO;
1215+
OCMStub([mockKitRegister wrapperInstance]).andReturn(kitInstance);
1216+
1217+
OCMStub([self.mockContainer activeKitsRegistry]).andReturn(@[mockKitRegister]);
1218+
1219+
NSURL *url = [NSURL URLWithString:@"myapp://unrelated"];
1220+
BOOL result = [self.rokt handleURLCallback:url];
1221+
1222+
XCTAssertFalse(result, @"Should return NO when the kit does not claim the URL");
1223+
}
1224+
1225+
- (void)testHandleURLCallbackReturnsNOWhenNoActiveKits {
1226+
MParticle *instance = [MParticle sharedInstance];
1227+
self.mockInstance = OCMPartialMock(instance);
1228+
self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]);
1229+
[[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE];
1230+
[[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance];
1231+
1232+
OCMStub([self.mockContainer activeKitsRegistry]).andReturn(@[]);
1233+
1234+
NSURL *url = [NSURL URLWithString:@"myapp://afterpay-redirect"];
1235+
BOOL result = [self.rokt handleURLCallback:url];
1236+
1237+
XCTAssertFalse(result, @"Should return NO when no kits are active");
1238+
}
1239+
1240+
- (void)testHandleURLCallbackReturnsNOWhenRoktKitNotRegistered {
1241+
MParticle *instance = [MParticle sharedInstance];
1242+
self.mockInstance = OCMPartialMock(instance);
1243+
self.mockContainer = OCMClassMock([MPKitContainer_PRIVATE class]);
1244+
[[[self.mockInstance stub] andReturn:self.mockContainer] kitContainer_PRIVATE];
1245+
[[[self.mockInstance stub] andReturn:self.mockInstance] sharedInstance];
1246+
1247+
// A non-Rokt kit is registered
1248+
id mockKitRegister = OCMProtocolMock(@protocol(MPExtensionKitProtocol));
1249+
OCMStub([(id<MPExtensionKitProtocol>)mockKitRegister code]).andReturn(@999);
1250+
OCMStub([self.mockContainer activeKitsRegistry]).andReturn(@[mockKitRegister]);
1251+
1252+
NSURL *url = [NSURL URLWithString:@"myapp://afterpay-redirect"];
1253+
BOOL result = [self.rokt handleURLCallback:url];
1254+
1255+
XCTAssertFalse(result, @"Should return NO when the Rokt Kit is not registered");
1256+
}
1257+
1258+
- (void)testHandleURLCallbackReturnsNOForNilURL {
1259+
#pragma clang diagnostic push
1260+
#pragma clang diagnostic ignored "-Wnonnull"
1261+
BOOL result = [self.rokt handleURLCallback:nil];
1262+
#pragma clang diagnostic pop
1263+
XCTAssertFalse(result, @"Should return NO when url is nil");
1264+
}
1265+
1266+
@end

0 commit comments

Comments
 (0)