Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a0966de40 | |||
| 2edf4062c0 | |||
| 29f22e4c7d |
+54
-7
@@ -13,6 +13,46 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
|
||||
var receiptValidator = AppReceiptValidator()
|
||||
|
||||
func testBearInitialSubscriptionReceiptParsing() {
|
||||
guard let data = assertTestAsset(filename: "hannes_bear_introductory_1_week_trial_receipt") else { return }
|
||||
|
||||
// A monthly subscription to Bear on macOS with 2 week introductory offer (free trial) just started.
|
||||
let expected = Receipt(
|
||||
bundleIdentifier: "net.shinyfrog.bear",
|
||||
bundleIdData: "DBJuZXQuc2hpbnlmcm9nLmJlYXI=",
|
||||
appVersion: "1.7.3",
|
||||
opaqueValue: "9MIfR4OoEFSOXc1fQ1ryDA==",
|
||||
sha1Hash: "fgStBECp2Yi0PONokIjcbBpnhvM=",
|
||||
originalAppVersion: "1.7.3",
|
||||
receiptCreationDate: "2019-09-26T14:08:37Z",
|
||||
expirationDate: nil,
|
||||
inAppPurchaseReceipts: [
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
productIdentifier: "net.shinyfrog.bear.pro_monthly_subscription",
|
||||
transactionIdentifier: "240000651265628",
|
||||
originalTransactionIdentifier: "240000651265628",
|
||||
purchaseDate: "2019-09-26T14:08:36Z",
|
||||
originalPurchaseDate: "2019-09-26T14:08:36Z",
|
||||
subscriptionExpirationDate: "2019-10-03T14:08:36Z",
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: 240000225193522,
|
||||
isInIntroductoryPricePeriod: false // should this not be true?
|
||||
)
|
||||
]
|
||||
)
|
||||
let result = receiptValidator.validateReceipt {
|
||||
$0.receiptOrigin = .data(data)
|
||||
$0.shouldValidateHash = false // the original device identifier is unknown
|
||||
}
|
||||
guard let receipt = result.receipt else {
|
||||
XCTFail("Unexpectedly failed parsing a receipt \(result.error!)")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(receipt, expected)
|
||||
}
|
||||
|
||||
func testNonMindNodeReceiptParsingWithoutValidation() {
|
||||
guard let data = assertB64TestAsset(filename: "grandUnifiedExpiredAppleCert_receipt.b64") else { return }
|
||||
|
||||
@@ -62,7 +102,8 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-07T20:37:55Z"),
|
||||
subscriptionExpirationDate: nil,
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: 0
|
||||
webOrderLineItemId: 0,
|
||||
isInIntroductoryPricePeriod: nil
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
@@ -73,7 +114,8 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:49:33Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T06:54:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: 1000000030274153
|
||||
webOrderLineItemId: 1000000030274153,
|
||||
isInIntroductoryPricePeriod: nil
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
@@ -84,7 +126,8 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:53:18Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T06:59:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: 1000000030274154
|
||||
webOrderLineItemId: 1000000030274154,
|
||||
isInIntroductoryPricePeriod: nil
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
@@ -95,7 +138,8 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T06:57:34Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:04:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: 1000000030274165
|
||||
webOrderLineItemId: 1000000030274165,
|
||||
isInIntroductoryPricePeriod: nil
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
@@ -106,7 +150,8 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:02:33Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:09:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: 1000000030274192
|
||||
webOrderLineItemId: 1000000030274192,
|
||||
isInIntroductoryPricePeriod: nil
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
@@ -117,7 +162,8 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:08:30Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:14:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: 1000000030274219
|
||||
webOrderLineItemId: 1000000030274219,
|
||||
isInIntroductoryPricePeriod: nil
|
||||
),
|
||||
InAppPurchaseReceipt(
|
||||
quantity: 1,
|
||||
@@ -128,7 +174,8 @@ class AppReceiptValidationInAppPurchaseTests: XCTestCase {
|
||||
originalPurchaseDate: Date.demoDate(string: "2015-08-10T07:12:34Z"),
|
||||
subscriptionExpirationDate: Date.demoDate(string: "2015-08-10T07:19:32Z"),
|
||||
cancellationDate: nil,
|
||||
webOrderLineItemId: 1000000030274249
|
||||
webOrderLineItemId: 1000000030274249,
|
||||
isInIntroductoryPricePeriod: nil
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
BIN
Binary file not shown.
@@ -31,6 +31,8 @@
|
||||
D15358B41F62C47400F297D0 /* deprecatedSinglesTypeExpiredAppleCert_receipt.b64 in Resources */ = {isa = PBXBuildFile; fileRef = D15358B11F62C3C400F297D0 /* deprecatedSinglesTypeExpiredAppleCert_receipt.b64 */; };
|
||||
D15358B51F62C47500F297D0 /* deprecatedSinglesTypeExpiredAppleCert_receipt.b64 in Resources */ = {isa = PBXBuildFile; fileRef = D15358B11F62C3C400F297D0 /* deprecatedSinglesTypeExpiredAppleCert_receipt.b64 */; };
|
||||
D15358EF1F62D2C100F297D0 /* AppReceiptValidator.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D1D6F4B51F5D684C00E86FE1 /* AppReceiptValidator.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
D1545DDD233D062A00BE82F8 /* hannes_bear_introductory_1_week_trial_receipt in Resources */ = {isa = PBXBuildFile; fileRef = D1545DDC233D062A00BE82F8 /* hannes_bear_introductory_1_week_trial_receipt */; };
|
||||
D1545DDE233D062A00BE82F8 /* hannes_bear_introductory_1_week_trial_receipt in Resources */ = {isa = PBXBuildFile; fileRef = D1545DDC233D062A00BE82F8 /* hannes_bear_introductory_1_week_trial_receipt */; };
|
||||
D15C59111F697D4D006F66FE /* pkcs7_union_accessors.h in Headers */ = {isa = PBXBuildFile; fileRef = D19095BA1F6004D10095729B /* pkcs7_union_accessors.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
D15C59151F698061006F66FE /* aes.h in Headers */ = {isa = PBXBuildFile; fileRef = D1D431061F69627600F7F39D /* aes.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
D15C59181F6981C4006F66FE /* asn1.h in Headers */ = {isa = PBXBuildFile; fileRef = D1D431071F69627600F7F39D /* asn1.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
@@ -288,6 +290,7 @@
|
||||
D150A0ED1F669A880026ED04 /* AppReceiptValidationInAppPurchaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReceiptValidationInAppPurchaseTests.swift; sourceTree = "<group>"; };
|
||||
D15358A51F62BEC100F297D0 /* grandUnifiedExpiredAppleCert_receipt.b64 */ = {isa = PBXFileReference; lastKnownFileType = text; path = grandUnifiedExpiredAppleCert_receipt.b64; sourceTree = "<group>"; };
|
||||
D15358B11F62C3C400F297D0 /* deprecatedSinglesTypeExpiredAppleCert_receipt.b64 */ = {isa = PBXFileReference; lastKnownFileType = text; path = deprecatedSinglesTypeExpiredAppleCert_receipt.b64; sourceTree = "<group>"; };
|
||||
D1545DDC233D062A00BE82F8 /* hannes_bear_introductory_1_week_trial_receipt */ = {isa = PBXFileReference; lastKnownFileType = file; path = hannes_bear_introductory_1_week_trial_receipt; sourceTree = "<group>"; };
|
||||
D15C59141F698005006F66FE /* AppReceiptValidator.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = AppReceiptValidator.modulemap; sourceTree = "<group>"; };
|
||||
D19095811F6000A40095729B /* AppReceiptValidator Demo macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AppReceiptValidator Demo macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D19095831F6000A40095729B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -903,6 +906,7 @@
|
||||
D1D6F5451F5D8DF700E86FE1 /* hannes_mac_mindnode_pro_receipt */,
|
||||
D150A0E21F66936B0026ED04 /* mindnode_ios_michaelsandbox_receipt1.b64 */,
|
||||
D150A0E81F6698950026ED04 /* mindnode_ios_michaelsandbox_receipt2.b64 */,
|
||||
D1545DDC233D062A00BE82F8 /* hannes_bear_introductory_1_week_trial_receipt */,
|
||||
D1D6F54F1F5D9E8D00E86FE1 /* not_a_receipt */,
|
||||
);
|
||||
path = "Test Assets";
|
||||
@@ -1289,6 +1293,7 @@
|
||||
D14FA7331F61476800545540 /* mac_mindnode_rebought_receipt in Resources */,
|
||||
D19095A21F6000DE0095729B /* hannes_mac_mindnode_pro_Info.plist in Resources */,
|
||||
D19095A11F6000DE0095729B /* hannes_mac_mindnode_receipt in Resources */,
|
||||
D1545DDE233D062A00BE82F8 /* hannes_bear_introductory_1_week_trial_receipt in Resources */,
|
||||
D15358AA1F62C12400F297D0 /* grandUnifiedExpiredAppleCert_receipt.b64 in Resources */,
|
||||
D19095A41F6000DE0095729B /* not_a_receipt in Resources */,
|
||||
D150A0E91F66989A0026ED04 /* mindnode_ios_michaelsandbox_receipt2.b64 in Resources */,
|
||||
@@ -1306,6 +1311,7 @@
|
||||
D14FA7321F61476700545540 /* mac_mindnode_rebought_receipt in Resources */,
|
||||
D19095CA1F601E5D0095729B /* hannes_mac_mindnode_pro_Info.plist in Resources */,
|
||||
D19095C91F601E5D0095729B /* hannes_mac_mindnode_receipt in Resources */,
|
||||
D1545DDD233D062A00BE82F8 /* hannes_bear_introductory_1_week_trial_receipt in Resources */,
|
||||
D15358AE1F62C12500F297D0 /* grandUnifiedExpiredAppleCert_receipt.b64 in Resources */,
|
||||
D19095CC1F601E5D0095729B /* not_a_receipt in Resources */,
|
||||
D150A0EA1F66989A0026ED04 /* mindnode_ios_michaelsandbox_receipt2.b64 in Resources */,
|
||||
|
||||
@@ -271,6 +271,8 @@ private extension AppReceiptValidator {
|
||||
inAppPurchaseReceipt.cancellationDate = value.dateValue
|
||||
case .webOrderLineItemId:
|
||||
inAppPurchaseReceipt.webOrderLineItemId = value.intValue
|
||||
case .isInIntroductoryOfferPeriod:
|
||||
inAppPurchaseReceipt.isInIntroductoryPricePeriod = value.intValue.flatMap { $0 != 0 }
|
||||
}
|
||||
}
|
||||
return inAppPurchaseReceipt
|
||||
@@ -351,6 +353,7 @@ private extension AppReceiptValidator {
|
||||
case subscriptionExpirationDate = 1708
|
||||
case cancellationDate = 1712
|
||||
case webOrderLineItemId = 1711
|
||||
case isInIntroductoryOfferPeriod = 1719
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,8 +193,12 @@ public struct InAppPurchaseReceipt: Equatable {
|
||||
/// This value is a unique ID that identifies purchase events across devices, including subscription renewal purchase events.
|
||||
public internal(set) var webOrderLineItemId: Int64?
|
||||
|
||||
/// For an **auto-renewable subscription**, whether or not it is in the introductory price period. For other transactions this is `nil`
|
||||
/// ASN.1 Field Type 1719
|
||||
public internal(set) var isInIntroductoryPricePeriod: Bool?
|
||||
|
||||
/// For documentation of parameters, have a look at the documented properties of `InAppPurchaseReceipt`.
|
||||
public init(quantity: Int64?, productIdentifier: String?, transactionIdentifier: String?, originalTransactionIdentifier: String?, purchaseDate: Date?, originalPurchaseDate: Date?, subscriptionExpirationDate: Date?, cancellationDate: Date?, webOrderLineItemId: Int64?) {
|
||||
public init(quantity: Int64?, productIdentifier: String?, transactionIdentifier: String?, originalTransactionIdentifier: String?, purchaseDate: Date?, originalPurchaseDate: Date?, subscriptionExpirationDate: Date?, cancellationDate: Date?, webOrderLineItemId: Int64?, isInIntroductoryPricePeriod: Bool?) {
|
||||
self.quantity = quantity
|
||||
self.productIdentifier = productIdentifier
|
||||
self.transactionIdentifier = transactionIdentifier
|
||||
@@ -204,6 +208,7 @@ public struct InAppPurchaseReceipt: Equatable {
|
||||
self.subscriptionExpirationDate = subscriptionExpirationDate
|
||||
self.cancellationDate = cancellationDate
|
||||
self.webOrderLineItemId = webOrderLineItemId
|
||||
self.isInIntroductoryPricePeriod = isInIntroductoryPricePeriod
|
||||
}
|
||||
|
||||
/// Convenience initializer with stringified parameters for `Date` and `Data` type parameters.
|
||||
@@ -215,7 +220,7 @@ public struct InAppPurchaseReceipt: Equatable {
|
||||
/// - Note: Dates must be formatted as "2017-01-01T12:00:00Z", otherwise will be assigned `nil`.
|
||||
/// Data must be formatted as Base64, otherwise will be assigned `nil`.
|
||||
/// Correct formatting will be asserted in DEBUG builds.
|
||||
public init(quantity: Int64?, productIdentifier: String, transactionIdentifier: String, originalTransactionIdentifier: String, purchaseDate: String, originalPurchaseDate: String, subscriptionExpirationDate: String?, cancellationDate: String?, webOrderLineItemId: Int64?) {
|
||||
public init(quantity: Int64?, productIdentifier: String, transactionIdentifier: String, originalTransactionIdentifier: String, purchaseDate: String, originalPurchaseDate: String, subscriptionExpirationDate: String?, cancellationDate: String?, webOrderLineItemId: Int64?, isInIntroductoryPricePeriod: Bool?) {
|
||||
self.init(quantity: quantity,
|
||||
productIdentifier: productIdentifier,
|
||||
transactionIdentifier: transactionIdentifier,
|
||||
@@ -224,7 +229,8 @@ public struct InAppPurchaseReceipt: Equatable {
|
||||
originalPurchaseDate: parseDate(string: originalPurchaseDate),
|
||||
subscriptionExpirationDate: subscriptionExpirationDate.flatMap { parseDate(string: $0) },
|
||||
cancellationDate: cancellationDate.flatMap { parseDate(string: $0) },
|
||||
webOrderLineItemId: webOrderLineItemId)
|
||||
webOrderLineItemId: webOrderLineItemId,
|
||||
isInIntroductoryPricePeriod: isInIntroductoryPricePeriod)
|
||||
}
|
||||
|
||||
public init() {}
|
||||
@@ -247,7 +253,8 @@ extension InAppPurchaseReceipt: CustomStringConvertible, CustomDebugStringConver
|
||||
("originalPurchaseDate", formatter.format(self.originalPurchaseDate)),
|
||||
("subscriptionExpirationDate", formatter.format(self.subscriptionExpirationDate)),
|
||||
("cancellationDate", formatter.format(self.cancellationDate)),
|
||||
("webOrderLineItemId", formatter.format(self.webOrderLineItemId))
|
||||
("webOrderLineItemId", formatter.format(self.webOrderLineItemId)),
|
||||
("isInIntroductoryPricePeriod", formatter.format(self.isInIntroductoryPricePeriod))
|
||||
]
|
||||
return "InAppPurchaseReceipt(\n" + formatter.format(props) + "\n)"
|
||||
}
|
||||
@@ -291,6 +298,12 @@ private struct StringFormatter {
|
||||
return quoted(data.base64EncodedString())
|
||||
}
|
||||
|
||||
func format(_ optionalBool: Bool?) -> String {
|
||||
guard let bool = optionalBool else { return fallback }
|
||||
|
||||
return String(describing: bool)
|
||||
}
|
||||
|
||||
func format(_ date: Date?) -> String {
|
||||
guard let date = date else { return fallback }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user