Skip to content

Commit 6da3a2e

Browse files
committed
Merge pull request #89 from adjust/receipt
Receipt verification
2 parents ab2d5d9 + 78fcf85 commit 6da3a2e

File tree

13 files changed

+181
-13
lines changed

13 files changed

+181
-13
lines changed

Adjust.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Pod::Spec.new do |s|
22
s.name = "Adjust"
3-
s.version = "4.0.8"
3+
s.version = "4.1.0"
44
s.summary = "This is the iOS SDK of adjust. You can read more about it at http://adjust.com."
55
s.homepage = "http://adjust.com"
66
s.license = { :type => 'MIT', :file => 'MIT-LICENSE' }
77
s.author = { "Christian Wellenbrock" => "[email protected]" }
8-
s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.0.8" }
8+
s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.1.0" }
99
s.platform = :ios, '4.3'
1010
s.framework = 'SystemConfiguration'
1111
s.weak_framework = 'AdSupport', 'iAd'

Adjust.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
969952D21A01309200928462 /* ADJAttribution.m in Sources */ = {isa = PBXBuildFile; fileRef = 969952D11A01309200928462 /* ADJAttribution.m */; };
2727
96C0EFE01A3EF47A00B39F31 /* NSString+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96CD2BDD1A13BFC600A40AFB /* NSString+ADJAdditions.m */; };
2828
96C0EFE11A3EF47A00B39F31 /* UIDevice+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96CD2BDF1A13BFC600A40AFB /* UIDevice+ADJAdditions.m */; };
29+
96C93DC51AC41A8300B53F56 /* Adjust.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 96E5E34C18BBB48A008E7B30 /* Adjust.h */; };
30+
96C93DF51AC47F2E00B53F56 /* NSData+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96C93DF41AC47F2E00B53F56 /* NSData+ADJAdditions.m */; };
31+
96C93DF61AC47FE000B53F56 /* NSData+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96C93DF41AC47F2E00B53F56 /* NSData+ADJAdditions.m */; };
2932
96CD2BE01A13BFC600A40AFB /* NSString+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96CD2BDD1A13BFC600A40AFB /* NSString+ADJAdditions.m */; };
3033
96CD2BE11A13BFC600A40AFB /* UIDevice+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96CD2BDF1A13BFC600A40AFB /* UIDevice+ADJAdditions.m */; };
3134
96E5E38118BBB48A008E7B30 /* Adjust.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E34D18BBB48A008E7B30 /* Adjust.m */; };
@@ -86,6 +89,7 @@
8689
dstPath = "include/$(PRODUCT_NAME)";
8790
dstSubfolderSpec = 16;
8891
files = (
92+
96C93DC51AC41A8300B53F56 /* Adjust.h in CopyFiles */,
8993
);
9094
runOnlyForDeploymentPostprocessing = 0;
9195
};
@@ -113,6 +117,8 @@
113117
969952CE1A012F5300928462 /* ADJAttributionHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJAttributionHandler.m; sourceTree = "<group>"; };
114118
969952D01A01309200928462 /* ADJAttribution.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJAttribution.h; sourceTree = "<group>"; };
115119
969952D11A01309200928462 /* ADJAttribution.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJAttribution.m; sourceTree = "<group>"; };
120+
96C93DF31AC47F2E00B53F56 /* NSData+ADJAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+ADJAdditions.h"; sourceTree = "<group>"; };
121+
96C93DF41AC47F2E00B53F56 /* NSData+ADJAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+ADJAdditions.m"; sourceTree = "<group>"; };
116122
96CD2BDC1A13BFC600A40AFB /* NSString+ADJAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+ADJAdditions.h"; sourceTree = "<group>"; };
117123
96CD2BDD1A13BFC600A40AFB /* NSString+ADJAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+ADJAdditions.m"; sourceTree = "<group>"; };
118124
96CD2BDE1A13BFC600A40AFB /* UIDevice+ADJAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIDevice+ADJAdditions.h"; sourceTree = "<group>"; };
@@ -245,6 +251,8 @@
245251
96CD2BDD1A13BFC600A40AFB /* NSString+ADJAdditions.m */,
246252
96CD2BDE1A13BFC600A40AFB /* UIDevice+ADJAdditions.h */,
247253
96CD2BDF1A13BFC600A40AFB /* UIDevice+ADJAdditions.m */,
254+
96C93DF31AC47F2E00B53F56 /* NSData+ADJAdditions.h */,
255+
96C93DF41AC47F2E00B53F56 /* NSData+ADJAdditions.m */,
248256
);
249257
path = ADJAdditions;
250258
sourceTree = "<group>";
@@ -433,6 +441,7 @@
433441
96E5E39918BBB48A008E7B30 /* ADJUtil.m in Sources */,
434442
96E5E39818BBB48A008E7B30 /* ADJTimer.m in Sources */,
435443
96E5E38C18BBB48A008E7B30 /* ADJActivityKind.m in Sources */,
444+
96C93DF51AC47F2E00B53F56 /* NSData+ADJAdditions.m in Sources */,
436445
96E5E38D18BBB48A008E7B30 /* ADJActivityPackage.m in Sources */,
437446
965307F61A000DA400107FF9 /* ADJDeviceInfo.m in Sources */,
438447
969952D21A01309200928462 /* ADJAttribution.m in Sources */,
@@ -464,6 +473,7 @@
464473
96E5E3B418BBB49E008E7B30 /* ADJRequestHandlerMock.m in Sources */,
465474
96E5E3B518BBB49E008E7B30 /* AIRequestHandlerTests.m in Sources */,
466475
96ED00391A38A4CD00209110 /* ADJAttributionHandlerMock.m in Sources */,
476+
96C93DF61AC47FE000B53F56 /* NSData+ADJAdditions.m in Sources */,
467477
96E5E3B218BBB49E008E7B30 /* ADJPackageHandlerMock.m in Sources */,
468478
);
469479
runOnlyForDeploymentPostprocessing = 0;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// NSData+ADJAdditions.h
3+
// adjust
4+
//
5+
// Created by Pedro Filipe on 26/03/15.
6+
// Copyright (c) 2015 adjust GmbH. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
@interface NSData(ADJAdditions)
12+
13+
- (NSString *)adjEncodeBase64;
14+
15+
@end
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// NSData+ADJAdditions.m
3+
// adjust
4+
//
5+
// Created by Pedro Filipe on 26/03/15.
6+
// Copyright (c) 2015 adjust GmbH. All rights reserved.
7+
//
8+
9+
#import "NSData+ADJAdditions.h"
10+
11+
@implementation NSData(ADJAdditions)
12+
13+
static const char _base64EncodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
14+
15+
// http://stackoverflow.com/a/4727124
16+
- (NSString *)adjEncodeBase64 {
17+
const unsigned char * objRawData = self.bytes;
18+
char * objPointer;
19+
char * strResult;
20+
21+
// Get the Raw Data length and ensure we actually have data
22+
NSUInteger intLength = self.length;
23+
if (intLength == 0) return nil;
24+
25+
// Setup the String-based Result placeholder and pointer within that placeholder
26+
strResult = (char *)calloc((((intLength + 2) / 3) * 4) + 1, sizeof(char));
27+
objPointer = strResult;
28+
29+
// Iterate through everything
30+
while (intLength > 2) { // keep going until we have less than 24 bits
31+
*objPointer++ = _base64EncodingTable[objRawData[0] >> 2];
32+
*objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)];
33+
*objPointer++ = _base64EncodingTable[((objRawData[1] & 0x0f) << 2) + (objRawData[2] >> 6)];
34+
*objPointer++ = _base64EncodingTable[objRawData[2] & 0x3f];
35+
36+
// we just handled 3 octets (24 bits) of data
37+
objRawData += 3;
38+
intLength -= 3;
39+
}
40+
41+
// now deal with the tail end of things
42+
if (intLength != 0) {
43+
*objPointer++ = _base64EncodingTable[objRawData[0] >> 2];
44+
if (intLength > 1) {
45+
*objPointer++ = _base64EncodingTable[((objRawData[0] & 0x03) << 4) + (objRawData[1] >> 4)];
46+
*objPointer++ = _base64EncodingTable[(objRawData[1] & 0x0f) << 2];
47+
*objPointer++ = '=';
48+
} else {
49+
*objPointer++ = _base64EncodingTable[(objRawData[0] & 0x03) << 4];
50+
*objPointer++ = '=';
51+
*objPointer++ = '=';
52+
}
53+
}
54+
55+
// Terminate the string-based result
56+
*objPointer = '\0';
57+
58+
// Return the results as an NSString object
59+
NSString *encodedString = [NSString stringWithCString:strResult encoding:NSASCIIStringEncoding];
60+
free(strResult);
61+
return encodedString;
62+
}
63+
64+
@end

Adjust/ADJEvent.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
@property (nonatomic, copy, readonly) NSNumber* revenue;
1515
@property (nonatomic, readonly) NSDictionary* callbackParameters;
1616
@property (nonatomic, readonly) NSDictionary* partnerParameters;
17-
@property (nonatomic, copy) NSString* transactionId;
17+
@property (nonatomic, copy, readonly) NSString* transactionId;
1818
@property (nonatomic, copy, readonly) NSString* currency;
19+
@property (nonatomic, copy, readonly) NSData* receipt;
1920

2021
/**
2122
* Create Event object with Event Token.
@@ -69,10 +70,19 @@
6970
* A transaction ID can be used to avoid duplicate revenue events. The last ten
7071
* transaction identifiers are remembered.
7172
*
72-
* @param The identifier used to avoid duplicate revenue events
73+
* @param transactionId The identifier used to avoid duplicate revenue events
7374
*/
7475
- (void) setTransactionId:(NSString *)transactionId;
7576

7677
- (BOOL) isValid;
7778

79+
/**
80+
*
81+
* Validate a in-app-purchase receipt.
82+
*
83+
* @param receipt The receipt to validate
84+
* @param transactionId The identifier used to validate the receipt and to avoid duplicate revenue events
85+
*/
86+
- (void) setReceipt:(NSData *)receipt transactionId:(NSString *)transactionId;
87+
7888
@end

Adjust/ADJEvent.m

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,26 @@ - (BOOL) checkRevenue:(NSNumber*) revenue
146146
- (BOOL) isValid {
147147
if (![self checkEventToken:self.eventToken]) return NO;
148148
if (![self checkRevenue:self.revenue currency:self.currency]) return NO;
149+
if (![self checkReceipt:self.receipt transactionId:self.transactionId]) return NO;
149150

150151
return YES;
151152
}
152153

154+
- (void) setReceipt:(NSData *)receipt transactionId:(NSString *)transactionId {
155+
if (![self checkReceipt:receipt transactionId:transactionId]) return;
156+
157+
_receipt = receipt;
158+
_transactionId = transactionId;
159+
}
160+
161+
- (BOOL) checkReceipt:(NSData *)receipt transactionId:(NSString *)transactionId {
162+
if (receipt != nil && transactionId == nil) {
163+
[self.logger error:@"Missing transactionId"];
164+
return NO;
165+
}
166+
return YES;
167+
}
168+
153169
- (BOOL) isValidParameter:(NSString *)attribute
154170
attributeType:(NSString *)attributeType
155171
parameterName:(NSString *)parameterName
@@ -178,7 +194,13 @@ -(id)copyWithZone:(NSZone *)zone
178194
}
179195
copy.callbackMutableParameters = [self.callbackMutableParameters copyWithZone:zone];
180196
copy.partnerMutableParameters = [self.partnerMutableParameters copyWithZone:zone];
181-
copy.transactionId = [self.transactionId copyWithZone:zone];
197+
if (self.transactionId != nil) {
198+
if (self.receipt != nil) {
199+
[copy setReceipt:self.receipt transactionId:self.transactionId];
200+
} else {
201+
[copy setTransactionId:self.transactionId];
202+
}
203+
}
182204
}
183205
return copy;
184206
}

Adjust/ADJPackageBuilder.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import "ADJActivityPackage.h"
1111
#import "ADJUtil.h"
1212
#import "ADJAttribution.h"
13+
#import "NSData+ADJAdditions.h"
1314

1415
#pragma mark -
1516
@implementation ADJPackageBuilder
@@ -51,6 +52,12 @@ - (ADJActivityPackage *)buildEventPackage:(ADJEvent *) event{
5152
[self parameters:parameters setDictionaryJson:event.callbackParameters forKey:@"callback_params"];
5253
[self parameters:parameters setDictionaryJson:event.partnerParameters forKey:@"partner_params"];
5354

55+
if (event.receipt != nil) {
56+
NSString *receiptBase64 = [event.receipt adjEncodeBase64];
57+
[self parameters:parameters setString:receiptBase64 forKey:@"receipt"];
58+
[self parameters:parameters setString:event.transactionId forKey:@"transaction_id"];
59+
}
60+
5461
ADJActivityPackage *eventPackage = [self defaultActivityPackage];
5562
eventPackage.path = @"/event";
5663
eventPackage.activityKind = ADJActivityKindEvent;

Adjust/ADJUtil.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#include <sys/xattr.h>
1717

1818
static NSString * const kBaseUrl = @"https://app.adjust.com";
19-
static NSString * const kClientSdk = @"ios4.0.8";
19+
static NSString * const kClientSdk = @"ios4.1.0";
2020

2121
static NSString * const kDateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'Z";
2222
static NSDateFormatter *dateFormat;

AdjustTests/ADJActivityHandlerTests.m

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ - (void)testFirstRun
121121
ADJActivityPackage *activityPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[0];
122122

123123
// check the Sdk version is being tested
124-
XCTAssertEqual(@"ios4.0.8", activityPackage.clientSdk, @"%@", activityPackage.extendedString);
124+
XCTAssertEqual(@"ios4.1.0", activityPackage.clientSdk, @"%@", activityPackage.extendedString);
125125

126126
// check the server url
127127
XCTAssertEqual(@"https://app.adjust.com", ADJUtil.baseUrl);
@@ -358,6 +358,9 @@ - (void)testEventsBuffered {
358358
// add revenue
359359
[thirdEvent setRevenue:0 currency:@"USD"];
360360

361+
// add receipt information
362+
[thirdEvent setReceipt:[@"{ \"transaction-id\" = \"t_id_2\"; }" dataUsingEncoding:NSUTF8StringEncoding] transactionId:@"t_id_2"];
363+
361364
// track the third event
362365
[activityHandler trackEvent:thirdEvent];
363366

@@ -449,6 +452,9 @@ - (void)testEventsBuffered {
449452
XCTAssert([(NSString *)firstEventPackageParameters[@"revenue"] isEqualToString:@"0.0001"], @"%@", firstEventPackage.extendedString);
450453
XCTAssert([(NSString *)firstEventPackageParameters[@"currency"] isEqualToString:@"EUR"], @"%@", firstEventPackage.extendedString);
451454

455+
// check the that the transaction id was not injected
456+
XCTAssertNil(firstEventPackageParameters[@"transaction_id"], @"%@", firstEventPackage.extendedString);
457+
452458
// check the injected parameters
453459
XCTAssert([(NSString *)firstEventPackageParameters[@"callback_params"] isEqualToString:@"{\"keyCall\":\"valueCall2\",\"fooCall\":\"barCall\"}"],
454460
@"%@", firstEventPackage.extendedString);
@@ -473,6 +479,10 @@ - (void)testEventsBuffered {
473479
XCTAssert([(NSString *)thirdEventPackageParameters[@"revenue"] isEqualToString:@"0"], @"%@", thirdEventPackage.extendedString);
474480
XCTAssert([(NSString *)thirdEventPackageParameters[@"currency"] isEqualToString:@"USD"], @"%@", thirdEventPackage.extendedString);
475481

482+
// check the receipt and transaction_id
483+
XCTAssert([(NSString *)thirdEventPackageParameters[@"receipt"] isEqualToString:@"eyAidHJhbnNhY3Rpb24taWQiID0gInRfaWRfMiI7IH0="], @"%@", thirdEventPackage.extendedString);
484+
XCTAssert([(NSString *)thirdEventPackageParameters[@"transaction_id"] isEqualToString:@"t_id_2"], @"%@", thirdEventPackage.extendedString);
485+
476486
// check the that the parameters were not injected
477487
XCTAssertNil(thirdEventPackageParameters[@"callback_params"], @"%@", thirdEventPackage.extendedString);
478488
XCTAssertNil(thirdEventPackageParameters[@"partner_params"], @"%@", thirdEventPackage.extendedString);
@@ -611,6 +621,8 @@ - (void)testChecks {
611621
[firstEvent setRevenue:0 currency:@""];
612622
[firstEvent setRevenue:-0.0001 currency:@"EUR"];
613623

624+
[firstEvent setReceipt:@"value" transactionId:nil];
625+
614626
[activityHandler trackEvent:firstEvent];
615627

616628
[NSThread sleepForTimeInterval:2];
@@ -646,6 +658,8 @@ - (void)testChecks {
646658
// check revenue is invalid
647659
XCTAssert([self.loggerMock containsMessage:ADJLogLevelError beginsWith:@"Invalid amount -0.0001"], @"%@", self.loggerMock);
648660

661+
// check the receipt had a nil transaction id
662+
XCTAssert([self.loggerMock containsMessage:ADJLogLevelError beginsWith:@"Missing transactionId"], @"%@", self.loggerMock);
649663

650664
// check the first parameters
651665
ADJActivityPackage *firstEventPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[1];

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ If you're using [CocoaPods][cocoapods], you can add the following line to your
1717
`Podfile` and continue with [step 3](#step3):
1818

1919
```ruby
20-
pod 'Adjust', :git => 'git://github.com/adjust/ios_sdk.git', :tag => 'v4.0.8'
20+
pod 'Adjust', :git => 'git://github.com/adjust/ios_sdk.git', :tag => 'v4.1.0'
2121
```
2222

2323
### 1. Get the SDK
@@ -201,7 +201,7 @@ that you have set in your adjust dashboard.**
201201

202202
You can read more about revenue and event tracking in the [event tracking guide.][event-tracking]
203203

204-
#### Revenue deduplication
204+
#### <a id="deduplication"></a> Revenue deduplication
205205

206206
You can also pass in an optional transaction ID to avoid tracking duplicate
207207
revenues. The last ten transaction IDs are remembered and revenue events with
@@ -232,6 +232,25 @@ tracking revenue that is not actually being generated.
232232
}
233233
```
234234

235+
#### Receipt verification
236+
237+
If you track in-app purchases, you can also attach the receipt to the tracked
238+
event. In that case our servers will verify that receipt with Apple and discard
239+
the event if the verification failed. To make this work, you also need to send
240+
us the transaction ID of the purchase. The transaction ID will also be used for
241+
SDK side deduplication as explained [above](#deduplication):
242+
243+
```objc
244+
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
245+
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
246+
247+
ADJEvent *event = [ADJEvent eventWithEventToken:...];
248+
[event setRevenue:... currency:...];
249+
[event setReceipt:receipt transactionId:transaction.transactionIdentifier];
250+
251+
[Adjust trackEvent:event];
252+
```
253+
235254
### 7. Set up deep link reattributions
236255
237256
You can set up the adjust SDK to handle deep links that are used to open your

0 commit comments

Comments
 (0)