From 473441214197824dc9c6b1fbe93669dc6115c276 Mon Sep 17 00:00:00 2001 From: Mark Mroz Date: Mon, 27 Mar 2023 11:46:17 -0400 Subject: [PATCH] Release 0.3.1 --- CashAppPayKit.podspec | 2 +- CashAppPayKitUI.podspec | 2 +- Demo/PayKitDemo/PayKitViewController.swift | 78 +++++++++++++++++-- RELEASE-NOTES.md | 10 +++ Sources/PayKit/CashAppPay.swift | 7 +- Sources/PayKit/NetworkManager.swift | 2 + .../Services/Analytics/AnalyticsClient.swift | 28 ++++++- .../Services/Analytics/AnalyticsService.swift | 2 +- .../CashAppPayEndpoint+Analytics.swift | 39 ++++++++++ Tests/PayKitTests/AnalyticsClientTests.swift | 7 +- Tests/PayKitTests/AnalyticsServiceTests.swift | 2 +- .../PayKitTests/CashAppPay+EndpointTest.swift | 13 ++++ 12 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 Sources/PayKit/Services/Analytics/CashAppPayEndpoint+Analytics.swift diff --git a/CashAppPayKit.podspec b/CashAppPayKit.podspec index 62d4776..1f9d526 100644 --- a/CashAppPayKit.podspec +++ b/CashAppPayKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'CashAppPayKit' - s.version = '0.3.0' + s.version = '0.3.1' s.summary = 'PayKit iOS SDK' s.homepage = 'https://github.com/cashapp/cash-app-pay-ios-sdk' s.license = 'Apache License, Version 2.0' diff --git a/CashAppPayKitUI.podspec b/CashAppPayKitUI.podspec index 830d383..da3932e 100644 --- a/CashAppPayKitUI.podspec +++ b/CashAppPayKitUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'CashAppPayKitUI' - s.version = "0.3.0" + s.version = "0.3.1" s.summary = 'UI components for the PayKit iOS SDK' s.homepage = 'https://github.com/cashapp/cash-app-pay-ios-sdk' s.license = 'Apache License, Version 2.0' diff --git a/Demo/PayKitDemo/PayKitViewController.swift b/Demo/PayKitDemo/PayKitViewController.swift index 7e102d0..2a78dcf 100644 --- a/Demo/PayKitDemo/PayKitViewController.swift +++ b/Demo/PayKitDemo/PayKitViewController.swift @@ -20,11 +20,10 @@ import UIKit // swiftlint:disable file_length class PayKitViewController: UIViewController { - private let sandboxClientID = "CAS-CI_PAYKIT_MOBILE_DEMO" - private let sandboxBrandID = "BRAND_9t4pg7c16v4lukc98bm9jxyse" - private lazy var stackView = makeStackView() + private lazy var endpointLabel = makeLabel(text: environment.title) + private lazy var endpointToggle = makeEndpointToggle() private lazy var paymentTypeControlLabel = makeLabel(text: "Payment Type") private lazy var paymentTypeControl = makePaymentTypeControl() @@ -39,6 +38,12 @@ class PayKitViewController: UIViewController { private lazy var authorizeRequestButton = makeAuthorizeRequestButton() private lazy var statusTextView = makeStatusTextView() + var environment: Environment = .sandbox { + didSet { + updateViewState() + } + } + var pendingRequest: CustomerRequest? { didSet { updateViewState() @@ -46,13 +51,13 @@ class PayKitViewController: UIViewController { } private lazy var sdk: CashAppPay = { - let sdk = CashAppPay(clientID: sandboxClientID, endpoint: .sandbox) + let sdk = CashAppPay(clientID: environment.clientID, endpoint: environment.endpoint) sdk.addObserver(self) return sdk }() private var brandID: String { - sandboxBrandID + environment.brandID } override func viewDidLoad() { @@ -75,6 +80,10 @@ class PayKitViewController: UIViewController { stackView.setCustomSpacing(20, after: accountReferenceIDTextField) + stackView.addArrangedSubview(endpointToggle) + + stackView.setCustomSpacing(20, after: endpointToggle) + stackView.addArrangedSubview(createRequestButton) stackView.addArrangedSubview(updateRequestButton) stackView.addArrangedSubview(authorizeRequestButton) @@ -87,6 +96,8 @@ class PayKitViewController: UIViewController { stackView.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor), stackView.centerYAnchor.constraint(equalTo: view.readableContentGuide.centerYAnchor), + endpointToggle.widthAnchor.constraint(equalTo: stackView.widthAnchor), + paymentTypeControlLabel.widthAnchor.constraint(equalTo: stackView.widthAnchor), paymentTypeControl.widthAnchor.constraint(equalTo: stackView.widthAnchor), @@ -111,6 +122,7 @@ class PayKitViewController: UIViewController { authorizeRequestButton.isEnabled = (pendingRequest != nil) amountTextField.isEnabled = (paymentType == .ONE_TIME_PAYMENT) accountReferenceIDTextField.isEnabled = (paymentType == .ON_FILE_PAYMENT) + endpointLabel.text = environment.title } // MARK: - Button Actions @@ -128,6 +140,13 @@ class PayKitViewController: UIViewController { guard let request = pendingRequest else { return } sdk.authorizeCustomerRequest(request) } + + func endpointToggleChanged() { + self.environment = (environment == .sandbox) ? .staging : .sandbox + sdk.removeObserver(self) + sdk = CashAppPay(clientID: environment.clientID, endpoint: environment.endpoint) + sdk.addObserver(self) + } } // MARK: - Computed Properties @@ -197,6 +216,15 @@ extension PayKitViewController { return stackView } + func makeEndpointToggle() -> UIStackView { + let toggle = UISwitch(frame: .zero, primaryAction: UIAction { [weak self] _ in + self?.endpointToggleChanged() + }) + let stack = UIStackView(arrangedSubviews: [toggle, endpointLabel]) + stack.spacing = 16 + return stack + } + func makeCreateRequestButton() -> UIButton { var configuration = UIButton.Configuration.filled() configuration.buttonSize = .large @@ -354,3 +382,43 @@ extension PayKitViewController: UITextFieldDelegate { return string.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil } } + +extension PayKitViewController { + enum Environment { + private static let sandboxClientID = "CAS-CI_PAYKIT_MOBILE_DEMO" + private static let sandboxBrandID = "BRAND_9t4pg7c16v4lukc98bm9jxyse" + private static let stagingClientID = "CASH_CHECKOUT" + private static let stagingBrandID = "BRAND_4wv02dz5v4eg22b3enoffn6rt" + + case sandbox + case staging + + var title: String { + switch self { + case .staging: return "Staging" + case .sandbox: return "Sandbox" + } + } + + var clientID: String { + switch self { + case .sandbox: return Environment.sandboxClientID + case .staging: return Environment.stagingClientID + } + } + + var brandID: String { + switch self { + case .sandbox: return Environment.sandboxBrandID + case .staging: return Environment.stagingBrandID + } + } + + var endpoint: CashAppPay.Endpoint { + switch self { + case .sandbox: return .sandbox + case .staging: return .staging + } + } + } +} diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 0d179ce..412a182 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,3 +1,13 @@ +## PayKit 0.3.1 Release Notes + +*PayKit 0.3.1* supports *iOS* and requires *Xcode 9* or later. The minimum supported *Base SDK* is *11.0*. + +*PayKit 0.3.1* includes the following new features and enhancements. + +- **PayKit Demo** + + The *PayKitDemo App* now includes a toggle to test in the staging environment. + ## PayKit 0.3.0 Release Notes *PayKit 0.3.0* supports *iOS* and requires *Xcode 9* or later. The minimum supported *Base SDK* is *11.0*. diff --git a/Sources/PayKit/CashAppPay.swift b/Sources/PayKit/CashAppPay.swift index 2c095a0..b44d1a8 100644 --- a/Sources/PayKit/CashAppPay.swift +++ b/Sources/PayKit/CashAppPay.swift @@ -19,7 +19,7 @@ import UIKit public class CashAppPay { - public static let version = "0.3.0" + public static let version = "0.3.1" public static let RedirectNotification: Notification.Name = Notification.Name("CashAppPayRedirect") @@ -34,10 +34,10 @@ public class CashAppPay { EventStream2.CommonFields.platform.rawValue: "iOS", EventStream2.CommonFields.sdkVersion.rawValue: CashAppPay.version, EventStream2.CommonFields.clientUA.rawValue: UserAgent.userAgent, - EventStream2.CommonFields.isSandbox.rawValue: (endpoint == .sandbox), + EventStream2.CommonFields.environment.rawValue: endpoint.analyticsField, ], store: AnalyticsDataSource(), - client: AnalyticsClient(restService: ResilientRESTService()) + client: AnalyticsClient(restService: ResilientRESTService(), endpoint: endpoint.analyticsEndpoint) ) let stateMachine = StateMachine(networkManager: networkManager, analyticsService: analytics) @@ -52,6 +52,7 @@ public class CashAppPay { public enum Endpoint { case production case sandbox + case staging } /// The endpoint that the requests are routed to diff --git a/Sources/PayKit/NetworkManager.swift b/Sources/PayKit/NetworkManager.swift index 9417ef7..b705799 100644 --- a/Sources/PayKit/NetworkManager.swift +++ b/Sources/PayKit/NetworkManager.swift @@ -159,6 +159,8 @@ extension CashAppPay.Endpoint { return URL(string: "https://api.cash.app/")! case .sandbox: return URL(string: "https://sandbox.api.cash.app/")! + case .staging: + return URL(string: "https://api.cashstaging.app/")! } } } diff --git a/Sources/PayKit/Services/Analytics/AnalyticsClient.swift b/Sources/PayKit/Services/Analytics/AnalyticsClient.swift index 17d9cef..22e7016 100644 --- a/Sources/PayKit/Services/Analytics/AnalyticsClient.swift +++ b/Sources/PayKit/Services/Analytics/AnalyticsClient.swift @@ -22,10 +22,9 @@ class AnalyticsClient { private let restService: RESTService - private let endpointURL = URL(string: "https://api.squareup.com/")! private let apiPath = "2.0/log/eventstream" private var baseURL: URL { - URL(string: apiPath, relativeTo: endpointURL)! + URL(string: apiPath, relativeTo: endpoint.baseURL)! } private var requestHeaders: [String: String] { @@ -37,10 +36,15 @@ class AnalyticsClient { private let encoder: JSONEncoder = .eventStream2Encoder() + // MARK: - Public Properties + + private let endpoint: Endpoint + // MARK: - Lifecycle - init(restService: RESTService) { + init(restService: RESTService, endpoint: Endpoint) { self.restService = restService + self.endpoint = endpoint } // MARK: - Public @@ -82,6 +86,24 @@ class AnalyticsClient { } } +// MARK: - Endpoint + +extension AnalyticsClient { + enum Endpoint { + case production + case staging + + var baseURL: URL { + switch self { + case .production: + return URL(string: "https://api.squareup.com/")! + case .staging: + return URL(string: "https://api.squareupstaging.com/")! + } + } + } +} + // MARK: - Request Models struct EventStream2CollectionParams: Encodable { diff --git a/Sources/PayKit/Services/Analytics/AnalyticsService.swift b/Sources/PayKit/Services/Analytics/AnalyticsService.swift index 38ef600..dc4a699 100644 --- a/Sources/PayKit/Services/Analytics/AnalyticsService.swift +++ b/Sources/PayKit/Services/Analytics/AnalyticsService.swift @@ -137,6 +137,6 @@ extension EventStream2 { case platform = "platform" case sdkVersion = "sdk_version" case clientUA = "client_ua" - case isSandbox = "is_sandbox" + case environment = "environment" } } diff --git a/Sources/PayKit/Services/Analytics/CashAppPayEndpoint+Analytics.swift b/Sources/PayKit/Services/Analytics/CashAppPayEndpoint+Analytics.swift new file mode 100644 index 0000000..dbb9b16 --- /dev/null +++ b/Sources/PayKit/Services/Analytics/CashAppPayEndpoint+Analytics.swift @@ -0,0 +1,39 @@ +// +// CashAppPayEndpoint+Analytics.swift +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension CashAppPay.Endpoint { + var analyticsEndpoint: AnalyticsClient.Endpoint { + switch self { + case .production, .sandbox: + return .production + case .staging: + return .staging + } + } + + var analyticsField: String { + switch self { + case .production: + return "production" + case .sandbox: + return "sandbox" + case .staging: + return "staging" + } + } +} diff --git a/Tests/PayKitTests/AnalyticsClientTests.swift b/Tests/PayKitTests/AnalyticsClientTests.swift index 6ab1447..59ee36c 100644 --- a/Tests/PayKitTests/AnalyticsClientTests.swift +++ b/Tests/PayKitTests/AnalyticsClientTests.swift @@ -28,6 +28,11 @@ class AnalyticsClientTests: XCTestCase { XCTAssertEqual(try param.event.jsonString(), try event.jsonString()) } + func test_endpoint() { + XCTAssertEqual(AnalyticsClient.Endpoint.staging.baseURL.absoluteString, "https://api.squareupstaging.com/") + XCTAssertEqual(AnalyticsClient.Endpoint.production.baseURL.absoluteString, "https://api.squareup.com/") + } + func test_request_body() { let expectation = self.expectation(description: "Uploaded") let mock = MockRestService { request, retry, _ in @@ -38,7 +43,7 @@ class AnalyticsClientTests: XCTestCase { expectation.fulfill() } - let client = AnalyticsClient(restService: mock) + let client = AnalyticsClient(restService: mock, endpoint: .production) client.upload(appName: "app", events: [event]) { _ in } waitForExpectations(timeout: 0.5) diff --git a/Tests/PayKitTests/AnalyticsServiceTests.swift b/Tests/PayKitTests/AnalyticsServiceTests.swift index cc56eba..36307bd 100644 --- a/Tests/PayKitTests/AnalyticsServiceTests.swift +++ b/Tests/PayKitTests/AnalyticsServiceTests.swift @@ -253,7 +253,7 @@ private class MockAnalyticsClient: AnalyticsClient { init(uploadStub: @escaping ((String, [AnalyticsEvent], (Result) -> Void) -> Void) = { _, _, _ in }) { self.uploadStub = uploadStub - super.init(restService: ResilientRESTService()) + super.init(restService: ResilientRESTService(), endpoint: .production) } override func upload( diff --git a/Tests/PayKitTests/CashAppPay+EndpointTest.swift b/Tests/PayKitTests/CashAppPay+EndpointTest.swift index 2de8938..d8698ea 100644 --- a/Tests/PayKitTests/CashAppPay+EndpointTest.swift +++ b/Tests/PayKitTests/CashAppPay+EndpointTest.swift @@ -19,7 +19,20 @@ import XCTest class CashAppPay_EndpointTest: XCTestCase { func test_endpoint_url() { + XCTAssertEqual(CashAppPay.Endpoint.staging.baseURL.absoluteString, "https://api.cashstaging.app/") XCTAssertEqual(CashAppPay.Endpoint.sandbox.baseURL.absoluteString, "https://sandbox.api.cash.app/") XCTAssertEqual(CashAppPay.Endpoint.production.baseURL.absoluteString, "https://api.cash.app/") } + + func test_endpoint_analytics_endpoint() { + XCTAssertEqual(CashAppPay.Endpoint.staging.analyticsEndpoint, .staging) + XCTAssertEqual(CashAppPay.Endpoint.production.analyticsEndpoint, .production) + XCTAssertEqual(CashAppPay.Endpoint.sandbox.analyticsEndpoint, .production) + } + + func test_analytics_field() { + XCTAssertEqual(CashAppPay.Endpoint.staging.analyticsField, "staging") + XCTAssertEqual(CashAppPay.Endpoint.production.analyticsField, "production") + XCTAssertEqual(CashAppPay.Endpoint.sandbox.analyticsField, "sandbox") + } }