From 64529e30c2fc3b04242e29997f4a3ab3a90b0ca6 Mon Sep 17 00:00:00 2001 From: Daniel Romero Date: Mon, 24 Jun 2019 15:27:03 -0400 Subject: [PATCH 1/3] update Customer model and test --- Source/Order.swift | 37 ++++++------------- Tests/UnitTests/OrderTests.swift | 61 +++++++++----------------------- 2 files changed, 27 insertions(+), 71 deletions(-) diff --git a/Source/Order.swift b/Source/Order.swift index b12739a..68e1675 100644 --- a/Source/Order.swift +++ b/Source/Order.swift @@ -59,7 +59,7 @@ final public class Order: NSObject, Codable { /** The customer related to the order */ - public var customer: Customer = Customer() + public var customer: Customer? /** The total order value in pennies (e.g. 3999 for $39.99) @@ -120,38 +120,23 @@ final public class Order: NSObject, Codable { /** The id for the transacting customer in your system (required). */ - public var id: String? - - /** - If you prefer to provide your own hashed email, you can set it here following the below instructions, - otherwise, use **setEmail(email:)** and we will hash it for you. + let id: String + /** The SHA-256 hash of the transacting customer’s lowercase email, as a 64-character hex string. - The value of the e-mail address must be converted to lowercase before computing the hash. - The hash itself may use uppercase or lowercase hex characters. - */ - public var emailSha256: String? - /** - The email of the transacting customer. - Providing a value for this field will generate a SHA-256 hash - and populate the **emailSha256** field for you. + **Note**: The value of the e-mail address must be converted to lowercase before + computing the hash. The hash itself may use uppercase or lowercase hex characters. + */ + var email: String? - - Parameter email: the plain-text email to be converted to SHA-256 hash. - */ - public func setEmail(_ email: String) { - emailSha256 = email.lowercased().sha256 + @objc public init(id: String) { + self.id = id } - - /** - The customer’s IDFA. - */ - public var advertisingId: String? - + enum CodingKeys: String, CodingKey { + case email case id - case emailSha256 = "email_sha256" - case advertisingId = "advertising_id" } } diff --git a/Tests/UnitTests/OrderTests.swift b/Tests/UnitTests/OrderTests.swift index 926941e..916a4e4 100644 --- a/Tests/UnitTests/OrderTests.swift +++ b/Tests/UnitTests/OrderTests.swift @@ -27,7 +27,7 @@ import XCTest class OrderTests: XCTestCase { - func testInitialization_requiredProperties() { + func testInitialization_requiredPropertiesOnly() { // Arrange let id = "order-abc" let date: Date = Date.ISO8601Formatter.date(from: "2019-06-17T12:08:10-04:00")! @@ -43,7 +43,7 @@ class OrderTests: XCTestCase { XCTAssertEqual(order.currencyCode, "USD") XCTAssertEqual(order.purchaseDate, date.ISO8601String) XCTAssertEqual(order.lineItems, lineItems) - XCTAssertNotNil(order.customer) + XCTAssertNil(order.customer) XCTAssertNil(order.customerOrderId) } @@ -54,8 +54,9 @@ class OrderTests: XCTestCase { let currencyCode = "EUR" let lineItems = [Order.LineItem(identifier: "unique-id-1234", total: 400)] let customerOrderId = "123" - let customer = Order.Customer() - + let customer = Order.Customer(id: "123") + + // Act let order = Order(id: id, purchaseDate: date, lineItems: lineItems) @@ -86,7 +87,7 @@ class OrderTests: XCTestCase { let currencyCode = "EUR" let customerOrderId = "123" - let customer = Order.Customer() + let customer = Order.Customer(id: "123") // Act order.currencyCode = currencyCode @@ -105,15 +106,13 @@ class OrderTests: XCTestCase { let id = "derp123" let amount: Int64 = 499 let lineItems: [Order.LineItem] = [] - let customerDictionary: [String: AnyHashable] = [:] let order = Order(id: id, amount: amount) let date = order.purchaseDate let expectedOrderDictionary: [String: AnyHashable] = ["order_id": id, "amount": amount, "currency": "USD", "purchase_date": date, - "line_items": lineItems, - "customer": customerDictionary] + "line_items": lineItems] // Act guard let actualOrderDictionary = order.dictionaryRepresentation as? [String: AnyHashable] else { @@ -125,58 +124,30 @@ class OrderTests: XCTestCase { XCTAssertEqual(expectedOrderDictionary, actualOrderDictionary) } - func testCustomerInitialization_hashingEmail() { + func testCustomerInitialization_requiredPropertiesOnly() { // Arrange let id = "123" - let email = "betty@usebutton.com" - let emailSha256 = "c399e8d0e89e9f09aa14a36392e4cb0d058ab28b16247e80eab78ea5541a20d3" - let advertisingId = "123" // Act - let customer = Order.Customer() - customer.id = id - customer.setEmail(email) - customer.advertisingId = advertisingId + let customer = Order.Customer(id: id) // Assert XCTAssertEqual(customer.id, id) - XCTAssertEqual(customer.emailSha256, emailSha256) - XCTAssertEqual(customer.advertisingId, advertisingId) + XCTAssertNil(customer.email) } - - func testCustomerInitialization_providingHashedEmail() { - // Arrange - let id = "123" - let emailSha256 = "1234567" - let advertisingId = "123" - - // Act - let customer = Order.Customer() - customer.id = id - customer.emailSha256 = emailSha256 - customer.advertisingId = advertisingId - // Assert - XCTAssertEqual(customer.id, id) - XCTAssertEqual(customer.emailSha256, emailSha256) - XCTAssertEqual(customer.advertisingId, advertisingId) - } - - func testCustomerInitialization_hashingCapitalizedEmail() { + func testCustomerInitialization_allProperties() { // Arrange let id = "123" - let email = "BETTY@usebutton.com" - let emailSha256 = "c399e8d0e89e9f09aa14a36392e4cb0d058ab28b16247e80eab78ea5541a20d3" - let advertisingId = "123" + let email = "betty@usebutton.com" // Act - let customer = Order.Customer() - customer.id = id - customer.setEmail(email) - customer.advertisingId = advertisingId + let customer = Order.Customer(id: id) + customer.email = email // Assert - XCTAssertEqual(customer.emailSha256, emailSha256) + XCTAssertEqual(customer.id, id) + XCTAssertEqual(customer.email, email) } func testLineItemInitialization_requiredPropertiesOnly() { From 7c0f1b43211cae59147ed5c0eb0af7df0afd12ab Mon Sep 17 00:00:00 2001 From: Kirtan Patel Date: Thu, 13 Jun 2019 13:34:46 -0400 Subject: [PATCH 2/3] Added reportOrder method and tests --- Source/ButtonMerchant.swift | 15 ++++ Source/Client.swift | 14 ++- Source/Core.swift | 20 ++++- Source/Order.swift | 2 +- Tests/UnitTests/ButtonMerchantTests.swift | 17 ++++ Tests/UnitTests/ClientTests.swift | 66 ++++++++++++++ Tests/UnitTests/CoreTests.swift | 91 ++++++++++++++++++++ Tests/UnitTests/OrderTests.swift | 1 + Tests/UnitTests/TestObjects/TestClient.swift | 6 ++ Tests/UnitTests/TestObjects/TestCore.swift | 4 + 10 files changed, 232 insertions(+), 4 deletions(-) diff --git a/Source/ButtonMerchant.swift b/Source/ButtonMerchant.swift index ad95cce..890d4b6 100644 --- a/Source/ButtonMerchant.swift +++ b/Source/ButtonMerchant.swift @@ -143,9 +143,24 @@ final public class ButtonMerchant: NSObject { This does not replace server-side order reporting to Button. [See: order reporting](https://developer.usebutton.com/guides/merchants/ios/report-orders-to-button#report-orders-to-buttons-order-api) */ + @available(*, deprecated, message: "Use reportOrder() instead") @objc public static func trackOrder(_ order: Order, completion: ((Error?) -> Void)? = nil) { core.trackOrder(order, completion) } + + /** + Tracks an order. + + This signal is used to power the Instant Rewards feature for Publishers to notify + their customer as quickly as possible that their order has been correctly tracked. + + - Attention: + This does not replace server-side order reporting to Button. + [See: order reporting](https://developer.usebutton.com/guides/merchants/ios/report-orders-to-button#report-orders-to-buttons-order-api) + */ + @objc public static func reportOrder(_ order: Order, completion: ((Error?) -> Void)? = nil) { + core.reportOrder(order, completion) + } /** Discards the current session and all persisted data. diff --git a/Source/Client.swift b/Source/Client.swift index 0e1785a..fb2899d 100644 --- a/Source/Client.swift +++ b/Source/Client.swift @@ -42,6 +42,7 @@ internal protocol ClientType: class { var userAgent: UserAgentType { get } func fetchPostInstallURL(parameters: [String: Any], _ completion: @escaping (URL?, String?) -> Void) func trackOrder(parameters: [String: Any], _ completion: ((Error?) -> Void)?) + func reportOrder(parameters: [String: Any], _ completion: ((Error?) -> Void)?) init(session: URLSessionType, userAgent: UserAgentType) } @@ -69,7 +70,8 @@ internal final class Client: ClientType { completion(URL(string: action)!, attributionObject["btn_ref"] as? String) }) } - + + @available(*, deprecated, message: "Use reportOrder() instead") func trackOrder(parameters: [String: Any], _ completion: ((Error?) -> Void)?) { let request = urlRequest(url: Service.activity.url, parameters: parameters) enqueueRequest(request: request) { _, error in @@ -78,6 +80,16 @@ internal final class Client: ClientType { } } } + + func reportOrder(parameters: [String: Any], _ completion: ((Error?) -> Void)?) { + let request = urlRequest(url: Service.activity.url, parameters: parameters) + enqueueRequest(request: request) { _, error in + if let completion = completion { + completion(error) + } + } + } + } internal extension Client { diff --git a/Source/Core.swift b/Source/Core.swift index d51f51e..9a140d4 100644 --- a/Source/Core.swift +++ b/Source/Core.swift @@ -36,6 +36,7 @@ internal protocol CoreType { func trackIncomingURL(_ url: URL) func handlePostInstallURL(_ completion: @escaping (URL?, Error?) -> Void) func trackOrder(_ order: Order, _ completion: ((Error?) -> Void)?) + func reportOrder(_ order: Order, _ completion: ((Error?) -> Void)?) init(buttonDefaults: ButtonDefaultsType, client: ClientType, system: SystemType, @@ -138,8 +139,8 @@ final internal class Core: CoreType { completion(url, nil) } } - - @available(*, deprecated) + + @available(*, deprecated, message: "Use reportOrder() instead") func trackOrder(_ order: Order, _ completion: ((Error?) -> Void)?) { guard let appId = applicationId, !appId.isEmpty else { if let completion = completion { @@ -157,5 +158,20 @@ final internal class Core: CoreType { client.trackOrder(parameters: parameters, completion) } + + func reportOrder(_ order: Order, _ completion: ((Error?) -> Void)?) { + guard let appId = applicationId, !appId.isEmpty else { + if let completion = completion { + completion(ConfigurationError.noApplicationId) + } + return + } + + let trackOrderBody = TrackOrderBody(system: system, applicationId: appId, attributionToken: buttonDefaults.attributionToken, order: order) + + let parameters = trackOrderBody.dictionaryRepresentation + + client.reportOrder(parameters: parameters, completion) + } } diff --git a/Source/Order.swift b/Source/Order.swift index 68e1675..74c06f8 100644 --- a/Source/Order.swift +++ b/Source/Order.swift @@ -59,7 +59,7 @@ final public class Order: NSObject, Codable { /** The customer related to the order */ - public var customer: Customer? + public var customer: Customer = Customer() /** The total order value in pennies (e.g. 3999 for $39.99) diff --git a/Tests/UnitTests/ButtonMerchantTests.swift b/Tests/UnitTests/ButtonMerchantTests.swift index c5b2f90..6b7c2aa 100644 --- a/Tests/UnitTests/ButtonMerchantTests.swift +++ b/Tests/UnitTests/ButtonMerchantTests.swift @@ -163,4 +163,21 @@ class ButtonMerchantTests: XCTestCase { // Assert XCTAssertEqual(testCore.testOrder, expectedOrder) } + + func testReportOrderInvokesCoreWithOrder() { + // Arrange + let testSystem = TestSystem() + let testCore = TestCore(buttonDefaults: TestButtonDefaults(userDefaults: TestUserDefaults()), + client: TestClient(session: TestURLSession(), userAgent: TestUserAgent(system: testSystem)), + system: testSystem, + notificationCenter: TestNotificationCenter()) + let expectedOrder = Order(id: "test", purchaseDate: Date(), lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 400, quantity: 2)]) + + // Act + ButtonMerchant._core = testCore + ButtonMerchant.reportOrder(expectedOrder) { _ in } + + // Assert + XCTAssertEqual(testCore.testOrder, expectedOrder) + } } diff --git a/Tests/UnitTests/ClientTests.swift b/Tests/UnitTests/ClientTests.swift index b7d1738..648d0c4 100644 --- a/Tests/UnitTests/ClientTests.swift +++ b/Tests/UnitTests/ClientTests.swift @@ -303,4 +303,70 @@ class ClientTests: XCTestCase { self.wait(for: [expectation], timeout: 2.0) } + + func testReportOrderEnqueuesRequests() { + // Arrange + let testURLSession = TestURLSession() + let client = Client(session: testURLSession, userAgent: TestUserAgent()) + let expectedParameters = ["blargh": "blergh"] + let expectedURL = URL(string: "https://api.usebutton.com/v1/activity/order")! + + // Act + client.reportOrder(parameters: expectedParameters) { _ in } + let request = (testURLSession.lastDataTask?.originalRequest)! + let requestParameters = try? JSONSerialization.jsonObject(with: request.httpBody!) + let parameters = requestParameters as? [String: String] + + // Assert + XCTAssertTrue(testURLSession.didCallDataTaskWithRequest) + XCTAssertNotNil(testURLSession.lastDataTask) + XCTAssertNotNil(testURLSession.lastDataTask?.originalRequest!.url) + XCTAssertEqual(testURLSession.lastDataTask?.originalRequest!.url, expectedURL) + XCTAssertNotNil(parameters) + XCTAssertEqual(parameters!, expectedParameters) + } + + func testReportOrderSuccess() { + // Arrange + let expectation = XCTestExpectation(description: "track order success") + let testURLSession = TestURLSession() + let client = Client(session: testURLSession, userAgent: TestUserAgent()) + let url = URL(string: "https://api.usebutton.com/v1/activity/order")! + + // Act + client.reportOrder(parameters: [:]) { error in + + // Assert + XCTAssertNil(error) + + expectation.fulfill() + } + let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil) + testURLSession.lastDataTask?.completion(Data(), response, nil) + + self.wait(for: [expectation], timeout: 2.0) + } + + func testReportOrderFails() { + // Arrange + let expectation = XCTestExpectation(description: "track order fails") + let testURLSession = TestURLSession() + let client = Client(session: testURLSession, userAgent: TestUserAgent()) + let url = URL(string: "https://api.usebutton.com/v1/activity/order")! + let expectedError = TestError.known + + // Act + client.trackOrder(parameters: [:]) { error in + + // Assert + XCTAssertNotNil(error) + XCTAssertEqual(error as? TestError, expectedError) + + expectation.fulfill() + } + let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil) + testURLSession.lastDataTask?.completion(nil, response, expectedError) + + self.wait(for: [expectation], timeout: 2.0) + } } diff --git a/Tests/UnitTests/CoreTests.swift b/Tests/UnitTests/CoreTests.swift index 720d07d..f2703ce 100644 --- a/Tests/UnitTests/CoreTests.swift +++ b/Tests/UnitTests/CoreTests.swift @@ -376,4 +376,95 @@ class CoreTests: XCTestCase { self.wait(for: [expectation], timeout: 2.0) } + + func testReportOrder() { + // Arrange + let expectation = XCTestExpectation(description: "track order") + let order = Order(id: "order-abc", purchaseDate: Date(), lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 120, quantity: 2)]) + let testSystem = TestSystem() + let testClient = TestClient(session: TestURLSession(), userAgent: TestUserAgent(system: testSystem)) + let testDefaults = TestButtonDefaults(userDefaults: TestUserDefaults()) + testDefaults.testToken = "srctok-abc123" + let core = Core(buttonDefaults: testDefaults, + client: testClient, + system: testSystem, + notificationCenter: TestNotificationCenter()) + core.applicationId = "app-abc123" + // Act + core.reportOrder(order) { error in + + // Assert + XCTAssertNil(error) + expectation.fulfill() + } + + print(testClient.testParameters) + XCTAssertEqual(testClient.testParameters as NSDictionary, + ["app_id": "app-abc123", + "user_local_time": "2018-01-23T12:00:00Z", + "btn_ref": "srctok-abc123", + "order_id": "order-abc", + "total": 0, + "currency": "USD", + "source": "merchant-library"]) + testClient.trackOrderCompletion!(nil) + + self.wait(for: [expectation], timeout: 2.0) + } + + func testReportOrderWithoutAttributionToken() { + // Arrange + let expectation = XCTestExpectation(description: "track order") + let order = Order(id: "order-abc", purchaseDate: Date(), lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 120, quantity: 2)]) + let testSystem = TestSystem() + let testClient = TestClient(session: TestURLSession(), userAgent: TestUserAgent(system: testSystem)) + let testDefaults = TestButtonDefaults(userDefaults: TestUserDefaults()) + testDefaults.testToken = nil + let core = Core(buttonDefaults: testDefaults, + client: testClient, + system: testSystem, + notificationCenter: TestNotificationCenter()) + core.applicationId = "app-abc123" + + // Act + core.reportOrder(order) { error in + + // Assert + XCTAssertNil(error) + expectation.fulfill() + } + XCTAssertEqual(testClient.testParameters as NSDictionary, + ["app_id": "app-abc123", + "user_local_time": "2018-01-23T12:00:00Z", + "order_id": "order-abc", + "total": 0, + "currency": "USD", + "source": "merchant-library"]) + testClient.trackOrderCompletion!(nil) + + self.wait(for: [expectation], timeout: 2.0) + + } + + func testReportOrderError() { + // Arrange + let expectation = XCTestExpectation(description: "tracker order error") + let order = Order(id: "order-abc", purchaseDate: Date(), lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 120, quantity: 2)]) + let testSystem = TestSystem() + let testClient = TestClient(session: TestURLSession(), userAgent: TestUserAgent(system: testSystem)) + let core = Core(buttonDefaults: TestButtonDefaults(userDefaults: TestUserDefaults()), + client: testClient, + system: testSystem, + notificationCenter: TestNotificationCenter()) + core.applicationId = "" + + // Act + core.reportOrder(order) { error in + + // Assert + XCTAssertEqual(error as? ConfigurationError, ConfigurationError.noApplicationId) + expectation.fulfill() + } + self.wait(for: [expectation], timeout: 2.0) + } } diff --git a/Tests/UnitTests/OrderTests.swift b/Tests/UnitTests/OrderTests.swift index 916a4e4..8c8657d 100644 --- a/Tests/UnitTests/OrderTests.swift +++ b/Tests/UnitTests/OrderTests.swift @@ -106,6 +106,7 @@ class OrderTests: XCTestCase { let id = "derp123" let amount: Int64 = 499 let lineItems: [Order.LineItem] = [] + let customerDictionary: [String: AnyHashable] = [:] let order = Order(id: id, amount: amount) let date = order.purchaseDate let expectedOrderDictionary: [String: AnyHashable] = ["order_id": id, diff --git a/Tests/UnitTests/TestObjects/TestClient.swift b/Tests/UnitTests/TestObjects/TestClient.swift index 5c2e389..d83c54e 100644 --- a/Tests/UnitTests/TestObjects/TestClient.swift +++ b/Tests/UnitTests/TestObjects/TestClient.swift @@ -55,4 +55,10 @@ class TestClient: ClientType { didCallTrackOrder = true trackOrderCompletion = completion } + + func reportOrder(parameters: [String: Any], _ completion: ((Error?) -> Void)?) { + testParameters = parameters + didCallTrackOrder = true + trackOrderCompletion = completion + } } diff --git a/Tests/UnitTests/TestObjects/TestCore.swift b/Tests/UnitTests/TestObjects/TestCore.swift index 0df919e..e7b1bdc 100644 --- a/Tests/UnitTests/TestObjects/TestCore.swift +++ b/Tests/UnitTests/TestObjects/TestCore.swift @@ -74,6 +74,10 @@ class TestCore: CoreType { testOrder = order } + func reportOrder(_ order: Order, _ completion: ((Error?) -> Void)?) { + testOrder = order + } + func trackIncomingURL(_ url: URL) { testUrl = url } From dfedb03897bb13c2098d4030aa53528e645f85f3 Mon Sep 17 00:00:00 2001 From: Kirtan Patel Date: Tue, 25 Jun 2019 10:01:42 -0400 Subject: [PATCH 3/3] Added ReportOrderBody and fixed tests --- .swiftlint.yml | 4 +- ButtonMerchant.xcodeproj/project.pbxproj | 8 ++ Source/ButtonMerchant.swift | 4 +- Source/Client.swift | 1 - Source/Core.swift | 8 +- Source/Order.swift | 4 +- Source/ReportOrderBody.swift | 69 +++++++++++++++ Tests/UnitTests/ButtonMerchantTests.swift | 4 +- Tests/UnitTests/ClientTests.swift | 6 +- Tests/UnitTests/CoreTests.swift | 46 +++++++--- Tests/UnitTests/ReportOrderBodyTests.swift | 89 ++++++++++++++++++++ Tests/UnitTests/TestObjects/TestClient.swift | 6 +- 12 files changed, 222 insertions(+), 27 deletions(-) create mode 100644 Source/ReportOrderBody.swift create mode 100644 Tests/UnitTests/ReportOrderBodyTests.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 15db0d1..5df138d 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -173,8 +173,8 @@ trailing_whitespace: ignores_comments: true type_body_length: - warning: 300 - error: 350 + warning: 400 + error: 450 unused_optional_binding: severity: error diff --git a/ButtonMerchant.xcodeproj/project.pbxproj b/ButtonMerchant.xcodeproj/project.pbxproj index 1f53302..22dc35a 100644 --- a/ButtonMerchant.xcodeproj/project.pbxproj +++ b/ButtonMerchant.xcodeproj/project.pbxproj @@ -85,6 +85,8 @@ DADE90C2209B9A630073144B /* TestError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADE90C1209B9A630073144B /* TestError.swift */; }; DAE8B96F22AF5F0700D11AF9 /* TrustEvaluator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE8B96D22AF5F0400D11AF9 /* TrustEvaluator.swift */; }; DC4AAD4422B13CD3005CE460 /* OrderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4AAD4322B13CD3005CE460 /* OrderTests.swift */; }; + DCF4AD0422B421FB000DA3B2 /* ReportOrderBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF4AD0322B421FB000DA3B2 /* ReportOrderBody.swift */; }; + DCF4AD0722B7E31C000DA3B2 /* ReportOrderBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF4AD0522B7E2FF000DA3B2 /* ReportOrderBodyTests.swift */; }; DE0E7C2B209B915E001A5EE0 /* ApplicationId.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0E7C2A209B915E001A5EE0 /* ApplicationId.generated.swift */; }; DE1706DB20855B06009FF30B /* UIDeviceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1706DA20855B06009FF30B /* UIDeviceExtensions.swift */; }; DE1706DD20855B22009FF30B /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE1706DC20855B22009FF30B /* UIScreenExtensions.swift */; }; @@ -219,6 +221,8 @@ DADE90C1209B9A630073144B /* TestError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestError.swift; sourceTree = ""; }; DAE8B96D22AF5F0400D11AF9 /* TrustEvaluator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrustEvaluator.swift; sourceTree = ""; }; DC4AAD4322B13CD3005CE460 /* OrderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTests.swift; sourceTree = ""; }; + DCF4AD0322B421FB000DA3B2 /* ReportOrderBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportOrderBody.swift; sourceTree = ""; }; + DCF4AD0522B7E2FF000DA3B2 /* ReportOrderBodyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportOrderBodyTests.swift; sourceTree = ""; }; DDA5C34AC9410445D780EB22 /* Pods-UnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-UnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-UnitTests/Pods-UnitTests.debug.xcconfig"; sourceTree = ""; }; DE0E7C2A209B915E001A5EE0 /* ApplicationId.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationId.generated.swift; sourceTree = ""; }; DE153DA4FDE6DCF451A438B1 /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = ""; }; @@ -370,6 +374,7 @@ DE175A1D20A0ADF7005C97B9 /* Version */, 607FACEB1AFB9204008FA782 /* ButtonMerchantTests.swift */, 9E4C496520616B040053E4CA /* CoreTests.swift */, + DCF4AD0522B7E2FF000DA3B2 /* ReportOrderBodyTests.swift */, 9E4C496620616B040053E4CA /* ButtonDefaultsTests.swift */, 9E0DBB1B207BB55A0066A35D /* SystemTests.swift */, 9E5475E1206D7E0C00947A1C /* ClientTests.swift */, @@ -576,6 +581,7 @@ children = ( DE175A1920A09BB1005C97B9 /* Version */, DE865F792052F90600F4054D /* ButtonMerchant.swift */, + DCF4AD0322B421FB000DA3B2 /* ReportOrderBody.swift */, DA0FA29F205C1B3A008296A6 /* Core.swift */, 9E77202120605506005F740B /* ButtonDefaults.swift */, 9EB1B09F207AB43E00BE0A1A /* System.swift */, @@ -1104,6 +1110,7 @@ 9EB1B0A0207AB43E00BE0A1A /* System.swift in Sources */, DAE8B96F22AF5F0700D11AF9 /* TrustEvaluator.swift in Sources */, DE2F7450208F6552001E4BD6 /* ConfigurationError.swift in Sources */, + DCF4AD0422B421FB000DA3B2 /* ReportOrderBody.swift in Sources */, DE865FA620530BCE00F4054D /* ButtonMerchant.swift in Sources */, 9E56CE5A22B812AE00E75884 /* StringExtensions.swift in Sources */, 9E2B4317206C1335009F2886 /* EncodableExtensions.swift in Sources */, @@ -1157,6 +1164,7 @@ 9E4C496820616B040053E4CA /* ButtonDefaultsTests.swift in Sources */, 9E6F4341206C160C004242A1 /* TestClient.swift in Sources */, 9E4C496720616B040053E4CA /* CoreTests.swift in Sources */, + DCF4AD0722B7E31C000DA3B2 /* ReportOrderBodyTests.swift in Sources */, DEE61B2D20656A090039E47A /* XCTestExtensions.swift in Sources */, 9E77202520607331005F740B /* ButtonMerchantTests.swift in Sources */, 9EDEB859206ECE3000D58FE2 /* PostInstallBodyTests.swift in Sources */, diff --git a/Source/ButtonMerchant.swift b/Source/ButtonMerchant.swift index 890d4b6..ee101e4 100644 --- a/Source/ButtonMerchant.swift +++ b/Source/ButtonMerchant.swift @@ -143,13 +143,13 @@ final public class ButtonMerchant: NSObject { This does not replace server-side order reporting to Button. [See: order reporting](https://developer.usebutton.com/guides/merchants/ios/report-orders-to-button#report-orders-to-buttons-order-api) */ - @available(*, deprecated, message: "Use reportOrder() instead") + @available(*, deprecated, message: "Use ButtonMerchant.reportOrder(order:completion:) instead") @objc public static func trackOrder(_ order: Order, completion: ((Error?) -> Void)? = nil) { core.trackOrder(order, completion) } /** - Tracks an order. + Reports an order. This signal is used to power the Instant Rewards feature for Publishers to notify their customer as quickly as possible that their order has been correctly tracked. diff --git a/Source/Client.swift b/Source/Client.swift index fb2899d..9e6508c 100644 --- a/Source/Client.swift +++ b/Source/Client.swift @@ -71,7 +71,6 @@ internal final class Client: ClientType { }) } - @available(*, deprecated, message: "Use reportOrder() instead") func trackOrder(parameters: [String: Any], _ completion: ((Error?) -> Void)?) { let request = urlRequest(url: Service.activity.url, parameters: parameters) enqueueRequest(request: request) { _, error in diff --git a/Source/Core.swift b/Source/Core.swift index 9a140d4..19ee0b5 100644 --- a/Source/Core.swift +++ b/Source/Core.swift @@ -140,7 +140,7 @@ final internal class Core: CoreType { } } - @available(*, deprecated, message: "Use reportOrder() instead") + @available(*, deprecated) func trackOrder(_ order: Order, _ completion: ((Error?) -> Void)?) { guard let appId = applicationId, !appId.isEmpty else { if let completion = completion { @@ -167,10 +167,10 @@ final internal class Core: CoreType { return } - let trackOrderBody = TrackOrderBody(system: system, applicationId: appId, attributionToken: buttonDefaults.attributionToken, order: order) - - let parameters = trackOrderBody.dictionaryRepresentation + let reportOrderBody = ReportOrderBody(system: system, applicationId: appId, attributionToken: buttonDefaults.attributionToken, order: order) + let parameters = reportOrderBody.dictionaryRepresentation + client.reportOrder(parameters: parameters, completion) } diff --git a/Source/Order.swift b/Source/Order.swift index 74c06f8..79b3718 100644 --- a/Source/Order.swift +++ b/Source/Order.swift @@ -26,7 +26,7 @@ import Foundation import CommonCrypto /** -Represents an order placed by the user to be tracked using `ButtonMerchant.trackOrder(order)`. +Represents an order placed by the user to be tracked using `ButtonMerchant.reportOrder(order)`. */ @objcMembers final public class Order: NSObject, Codable { @@ -59,7 +59,7 @@ final public class Order: NSObject, Codable { /** The customer related to the order */ - public var customer: Customer = Customer() + public var customer: Customer? /** The total order value in pennies (e.g. 3999 for $39.99) diff --git a/Source/ReportOrderBody.swift b/Source/ReportOrderBody.swift new file mode 100644 index 0000000..2dbe895 --- /dev/null +++ b/Source/ReportOrderBody.swift @@ -0,0 +1,69 @@ +// +// ReportOrderBody.swift +// +// Copyright © 2019 Button, Inc. All rights reserved. (https://usebutton.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +internal struct ReportOrderBody: Codable { + + let applicationId: String + let userLocalTime: String + let attributionToken: String? + let orderId: String + let currency: String + let purchaseDate: String + let customerOrderId: String? + let lineItems: [Order.LineItem]? + let customer: Order.Customer? + let source: String = "merchant-library" + + enum CodingKeys: String, CodingKey { + case applicationId = "app_id" + case userLocalTime = "user_local_time" + case attributionToken = "btn_ref" + case orderId = "order_id" + case currency + case purchaseDate = "purchase_date" + case customerOrderId = "customer_order_id" + case lineItems = "line_items" + case customer + case source + } + + init(system: SystemType, + applicationId: String, + attributionToken: String?, + order: Order) { + + self.applicationId = applicationId + userLocalTime = system.currentDate.ISO8601String + self.attributionToken = attributionToken + self.orderId = order.id + self.currency = order.currencyCode + self.purchaseDate = order.purchaseDate + self.customerOrderId = order.customerOrderId + self.lineItems = order.lineItems + self.customer = order.customer + } + +} diff --git a/Tests/UnitTests/ButtonMerchantTests.swift b/Tests/UnitTests/ButtonMerchantTests.swift index 6b7c2aa..82aa102 100644 --- a/Tests/UnitTests/ButtonMerchantTests.swift +++ b/Tests/UnitTests/ButtonMerchantTests.swift @@ -171,7 +171,9 @@ class ButtonMerchantTests: XCTestCase { client: TestClient(session: TestURLSession(), userAgent: TestUserAgent(system: testSystem)), system: testSystem, notificationCenter: TestNotificationCenter()) - let expectedOrder = Order(id: "test", purchaseDate: Date(), lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 400, quantity: 2)]) + let expectedOrder = Order(id: "test", + purchaseDate: Date(), + lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 400)]) // Act ButtonMerchant._core = testCore diff --git a/Tests/UnitTests/ClientTests.swift b/Tests/UnitTests/ClientTests.swift index 648d0c4..515682b 100644 --- a/Tests/UnitTests/ClientTests.swift +++ b/Tests/UnitTests/ClientTests.swift @@ -328,7 +328,7 @@ class ClientTests: XCTestCase { func testReportOrderSuccess() { // Arrange - let expectation = XCTestExpectation(description: "track order success") + let expectation = XCTestExpectation(description: "report order success") let testURLSession = TestURLSession() let client = Client(session: testURLSession, userAgent: TestUserAgent()) let url = URL(string: "https://api.usebutton.com/v1/activity/order")! @@ -349,14 +349,14 @@ class ClientTests: XCTestCase { func testReportOrderFails() { // Arrange - let expectation = XCTestExpectation(description: "track order fails") + let expectation = XCTestExpectation(description: "report order fails") let testURLSession = TestURLSession() let client = Client(session: testURLSession, userAgent: TestUserAgent()) let url = URL(string: "https://api.usebutton.com/v1/activity/order")! let expectedError = TestError.known // Act - client.trackOrder(parameters: [:]) { error in + client.reportOrder(parameters: [:]) { error in // Assert XCTAssertNotNil(error) diff --git a/Tests/UnitTests/CoreTests.swift b/Tests/UnitTests/CoreTests.swift index f2703ce..2ad8009 100644 --- a/Tests/UnitTests/CoreTests.swift +++ b/Tests/UnitTests/CoreTests.swift @@ -380,7 +380,15 @@ class CoreTests: XCTestCase { func testReportOrder() { // Arrange let expectation = XCTestExpectation(description: "track order") - let order = Order(id: "order-abc", purchaseDate: Date(), lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 120, quantity: 2)]) + Date.ISO8601Formatter.timeZone = TimeZone(identifier: "UTC") + let date: Date = Date.ISO8601Formatter.date(from: "2019-06-17T12:08:10-04:00")! + let email = "test@button.com" + let lineItems = [Order.LineItem(identifier: "unique-id-1234", total: 120)] + let customer = Order.Customer(id: "customer-id-123") + customer.email = email + let order = Order(id: "order-abc", purchaseDate: date, lineItems: lineItems) + order.customer = customer + order.customerOrderId = "customer-order-id-123" let testSystem = TestSystem() let testClient = TestClient(session: TestURLSession(), userAgent: TestUserAgent(system: testSystem)) let testDefaults = TestButtonDefaults(userDefaults: TestUserDefaults()) @@ -390,6 +398,7 @@ class CoreTests: XCTestCase { system: testSystem, notificationCenter: TestNotificationCenter()) core.applicationId = "app-abc123" + // Act core.reportOrder(order) { error in @@ -397,17 +406,19 @@ class CoreTests: XCTestCase { XCTAssertNil(error) expectation.fulfill() } - - print(testClient.testParameters) + XCTAssertEqual(testClient.testParameters as NSDictionary, ["app_id": "app-abc123", "user_local_time": "2018-01-23T12:00:00Z", "btn_ref": "srctok-abc123", "order_id": "order-abc", - "total": 0, "currency": "USD", + "purchase_date": date.ISO8601String, + "customer_order_id": "customer-order-id-123", + "line_items": [["identifier": "unique-id-1234", "quantity": 1, "total": 120]], + "customer": ["id": "customer-id-123", "email": email], "source": "merchant-library"]) - testClient.trackOrderCompletion!(nil) + testClient.reportOrderCompletion!(nil) self.wait(for: [expectation], timeout: 2.0) } @@ -415,7 +426,15 @@ class CoreTests: XCTestCase { func testReportOrderWithoutAttributionToken() { // Arrange let expectation = XCTestExpectation(description: "track order") - let order = Order(id: "order-abc", purchaseDate: Date(), lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 120, quantity: 2)]) + Date.ISO8601Formatter.timeZone = TimeZone(identifier: "UTC") + let date: Date = Date.ISO8601Formatter.date(from: "2019-06-17T12:08:10-04:00")! + let email = "test@button.com" + let customer = Order.Customer(id: "customer-id-123") + customer.email = email + let lineItems = [Order.LineItem(identifier: "unique-id-1234", total: 120)] + let order = Order(id: "order-abc", purchaseDate: date, lineItems: lineItems) + order.customer = customer + order.customerOrderId = "customer-order-id-123" let testSystem = TestSystem() let testClient = TestClient(session: TestURLSession(), userAgent: TestUserAgent(system: testSystem)) let testDefaults = TestButtonDefaults(userDefaults: TestUserDefaults()) @@ -433,23 +452,30 @@ class CoreTests: XCTestCase { XCTAssertNil(error) expectation.fulfill() } + XCTAssertEqual(testClient.testParameters as NSDictionary, ["app_id": "app-abc123", "user_local_time": "2018-01-23T12:00:00Z", "order_id": "order-abc", - "total": 0, "currency": "USD", + "purchase_date": date.ISO8601String, + "customer_order_id": "customer-order-id-123", + "line_items": [["identifier": "unique-id-1234", "quantity": 1, "total": 120]], + "customer": ["id": "customer-id-123", "email": email], "source": "merchant-library"]) - testClient.trackOrderCompletion!(nil) + testClient.reportOrderCompletion!(nil) self.wait(for: [expectation], timeout: 2.0) - } func testReportOrderError() { // Arrange let expectation = XCTestExpectation(description: "tracker order error") - let order = Order(id: "order-abc", purchaseDate: Date(), lineItems: [Order.LineItem(identifier: "unique-id-1234", total: 120, quantity: 2)]) + let date = Date() + let customer = Order.Customer(id: "customer-id-123") + customer.email = "test@button.com" + let lineItems = [Order.LineItem(identifier: "unique-id-1234", total: 120)] + let order = Order(id: "order-abc", purchaseDate: date, lineItems: lineItems) let testSystem = TestSystem() let testClient = TestClient(session: TestURLSession(), userAgent: TestUserAgent(system: testSystem)) let core = Core(buttonDefaults: TestButtonDefaults(userDefaults: TestUserDefaults()), diff --git a/Tests/UnitTests/ReportOrderBodyTests.swift b/Tests/UnitTests/ReportOrderBodyTests.swift new file mode 100644 index 0000000..481b336 --- /dev/null +++ b/Tests/UnitTests/ReportOrderBodyTests.swift @@ -0,0 +1,89 @@ +// +// ReportOrderBodyTests.swift +// +// Copyright © 2019 Button, Inc. All rights reserved. (https://usebutton.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import XCTest +@testable import ButtonMerchant + +class ReportOrderBodyTests: XCTestCase { + + func testInitialization() { + // Arrange + let date = Date() + let customer = Order.Customer(id: "customer-id-123") + customer.email = "test@button.com" + let lineItems = [Order.LineItem(identifier: "unique-id-1234", total: 120)] + let order = Order(id: "order-abc", purchaseDate: date, lineItems: lineItems) + + // Act + let body = ReportOrderBody(system: TestSystem(), + applicationId: "app-abc123", + attributionToken: "srctok-abc123", + order: order) + + // Assert + XCTAssertEqual(body.applicationId, "app-abc123") + XCTAssertEqual(body.userLocalTime, "2018-01-23T12:00:00Z") + XCTAssertEqual(body.attributionToken, "srctok-abc123") + XCTAssertEqual(body.orderId, order.id) + XCTAssertEqual(body.currency, order.currencyCode) + XCTAssertEqual(body.purchaseDate, order.purchaseDate) + XCTAssertEqual(body.customerOrderId, order.customerOrderId) + XCTAssertEqual(body.lineItems, body.lineItems) + XCTAssertEqual(body.customer, body.customer) + XCTAssertEqual(body.source, "merchant-library") + } + + func testSerializationToDictionary() { + // Arrange + Date.ISO8601Formatter.timeZone = TimeZone(identifier: "UTC") + let date: Date = Date.ISO8601Formatter.date(from: "2019-06-17T12:08:10-04:00")! + let email = "test@button.com" + let customer = Order.Customer(id: "customer-id-123") + customer.email = email + let lineItems = [Order.LineItem(identifier: "unique-id-1234", total: 120)] + let order = Order(id: "order-abc", purchaseDate: date, lineItems: lineItems) + order.customer = customer + order.customerOrderId = "customer-order-id-123" + + // Act + let body = ReportOrderBody(system: TestSystem(), + applicationId: "app-abc123", + attributionToken: "srctok-abc123", + order: order) + + // Assert + XCTAssertEqual(body.dictionaryRepresentation as NSDictionary, + ["app_id": "app-abc123", + "user_local_time": "2018-01-23T12:00:00Z", + "btn_ref": "srctok-abc123", + "order_id": "order-abc", + "currency": "USD", + "purchase_date": date.ISO8601String, + "customer_order_id": "customer-order-id-123", + "line_items": [["identifier": "unique-id-1234", "quantity": 1, "total": 120]], + "customer": ["id": "customer-id-123", "email": email], + "source": "merchant-library"]) + } + +} diff --git a/Tests/UnitTests/TestObjects/TestClient.swift b/Tests/UnitTests/TestObjects/TestClient.swift index d83c54e..1eea7f3 100644 --- a/Tests/UnitTests/TestObjects/TestClient.swift +++ b/Tests/UnitTests/TestObjects/TestClient.swift @@ -31,12 +31,14 @@ class TestClient: ClientType { var testParameters: [String: Any] var didCallGetPostInstallLink = false var didCallTrackOrder = false + var didCallReportOrder = false var session: URLSessionType var userAgent: UserAgentType var postInstallCompletion: ((URL?, String?) -> Void)? var trackOrderCompletion: ((Error?) -> Void)? + var reportOrderCompletion: ((Error?) -> Void)? required init(session: URLSessionType, userAgent: UserAgentType) { self.session = session @@ -58,7 +60,7 @@ class TestClient: ClientType { func reportOrder(parameters: [String: Any], _ completion: ((Error?) -> Void)?) { testParameters = parameters - didCallTrackOrder = true - trackOrderCompletion = completion + didCallReportOrder = true + reportOrderCompletion = completion } }