Skip to content

Commit

Permalink
Graceful degradation with Webview reuse
Browse files Browse the repository at this point in the history
  • Loading branch information
markmur committed Apr 25, 2024
1 parent 96c9c59 commit 53f283d
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 67 deletions.
4 changes: 4 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
disabled_rules:
- line_length
- file_length

opt_in_rules:
- array_init
Expand All @@ -18,6 +19,9 @@ opt_in_rules:
- modifier_order
- overridden_super_call
- toggle_bool
- switch_case_alignment:
indented_cases: false
- fallthrough

nesting:
type_level:
Expand Down
3 changes: 3 additions & 0 deletions Samples/MobileBuyIntegration/.swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ opt_in_rules:
- modifier_order
- overridden_super_call
- toggle_bool
- switch_case_alignment:
indented_cases: false
- fallthrough

nesting:
type_level:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class CartViewController: UIViewController, UITableViewDelegate, UITableViewData

@IBAction private func presentCheckout() {
guard let url = CartManager.shared.cart?.checkoutUrl else { return }

ShopifyCheckoutSheetKit.present(checkout: url, from: self, delegate: self)
}

Expand Down Expand Up @@ -197,8 +198,11 @@ extension CartViewController: CheckoutDelegate {
errorMessage = message
}

print(#function, error)
forceCloseCheckout(errorMessage)
print(errorMessage, "Recoverable: \(error.isRecoverable)")

if !error.isRecoverable {
forceCloseCheckout(errorMessage)
}
}

private func handleCheckoutUnavailable(_ message: String, _ code: CheckoutUnavailable) {
Expand Down
10 changes: 8 additions & 2 deletions Sources/ShopifyCheckoutSheetKit/CheckoutBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,17 @@ enum BridgeError: Swift.Error {
enum CheckoutBridge {
static let schemaVersion = "8.1"
static let messageHandler = "mobileCheckoutSdk"
internal static let userAgent = "ShopifyCheckoutSDK/\(ShopifyCheckoutSheetKit.version)"
internal static var logger: ProductionLogger = InternalLogger()

static var applicationName: String {
let theme = ShopifyCheckoutSheetKit.configuration.colorScheme.rawValue
return "ShopifyCheckoutSDK/\(ShopifyCheckoutSheetKit.version) (\(schemaVersion);\(theme);standard)"
return "\(userAgent) (\(schemaVersion);\(theme);standard)"
}

static var recoveryAgent: String {
let theme = ShopifyCheckoutSheetKit.configuration.colorScheme.rawValue
return "\(userAgent) (noconnect;\(theme);standard_recovery)"
}

static func instrument(_ webView: WKWebView, _ instrumentation: InstrumentationPayload) {
Expand Down Expand Up @@ -119,7 +125,7 @@ extension CheckoutBridge {
self = .checkoutComplete(event: checkoutCompletedEvent)
} catch {
logger.logError(error, "Error decoding CheckoutCompletedEvent")
self = .checkoutComplete(event: emptyCheckoutCompletedEvent)
self = .checkoutComplete(event: createEmptyCheckoutCompletedEvent())
}
case "error":
let errorDecoder = CheckoutErrorEventDecoder()
Expand Down
70 changes: 35 additions & 35 deletions Sources/ShopifyCheckoutSheetKit/CheckoutCompletedEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ 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.
*/

// swiftlint:disable identifier_name

import Foundation

public struct CheckoutCompletedEvent: Codable {
Expand Down Expand Up @@ -52,9 +50,11 @@ extension CheckoutCompletedEvent {

public struct CartLineImage: Codable {
public let altText: String?
// swiftlint:disable identifier_name
public let lg: String
public let md: String
public let sm: String
// swiftlint:enable identifier_name
}

public struct CartLine: Codable {
Expand Down Expand Up @@ -115,38 +115,38 @@ extension CheckoutCompletedEvent {
}
}

// swiftlint:enable identifier_name

internal let emptyCheckoutCompletedEvent = CheckoutCompletedEvent(
orderDetails: CheckoutCompletedEvent.OrderDetails(
billingAddress: CheckoutCompletedEvent.Address(
address1: nil,
address2: nil,
city: nil,
countryCode: nil,
firstName: nil,
lastName: nil,
name: nil,
phone: nil,
postalCode: nil,
referenceId: nil,
zoneCode: nil
),
cart: CheckoutCompletedEvent.CartInfo(
lines: [],
price: CheckoutCompletedEvent.Price(
discounts: nil,
shipping: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil),
subtotal: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil),
taxes: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil),
total: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil)
internal func createEmptyCheckoutCompletedEvent(id: String? = "") -> CheckoutCompletedEvent {
return CheckoutCompletedEvent(
orderDetails: CheckoutCompletedEvent.OrderDetails(
billingAddress: CheckoutCompletedEvent.Address(
address1: nil,
address2: nil,
city: nil,
countryCode: nil,
firstName: nil,
lastName: nil,
name: nil,
phone: nil,
postalCode: nil,
referenceId: nil,
zoneCode: nil
),
cart: CheckoutCompletedEvent.CartInfo(
lines: [],
price: CheckoutCompletedEvent.Price(
discounts: nil,
shipping: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil),
subtotal: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil),
taxes: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil),
total: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil)
),
token: ""
),
token: ""
),
deliveries: nil,
email: nil,
id: "",
paymentMethods: nil,
phone: nil
deliveries: nil,
email: nil,
id: id ?? "",
paymentMethods: nil,
phone: nil
)
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class CheckoutCompletedEventDecoder {
let messageBody = try container.decode(String.self, forKey: .body)

guard let data = messageBody.data(using: .utf8) else {
return emptyCheckoutCompletedEvent
return createEmptyCheckoutCompletedEvent()
}

return try JSONDecoder().decode(CheckoutCompletedEvent.self, from: data)
Expand Down
7 changes: 7 additions & 0 deletions Sources/ShopifyCheckoutSheetKit/CheckoutDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ public protocol CheckoutDelegate: AnyObject {
/// Tells the delegate that the checkout encoutered one or more errors.
func checkoutDidFail(error: CheckoutError)

/// Tells the delegate that checkout has encountered an error and the return value will determine if it is handled with a fallback
func shouldRecoverFromError(error: CheckoutError) -> Bool

/// Tells te delegate that the buyer clicked a link
/// This includes email address or telephone number via `mailto:` or `tel:` or `http` links directed outside the application.
func checkoutDidClickLink(url: URL)
Expand All @@ -56,6 +59,10 @@ extension CheckoutDelegate {
throw error
}

public func shouldRecoverFromError(error: CheckoutError) -> Bool {
return error.isRecoverable
}

private func handleUrl(_ url: URL) {
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
Expand Down
15 changes: 13 additions & 2 deletions Sources/ShopifyCheckoutSheetKit/CheckoutError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ public enum CheckoutError: Swift.Error {
/// if the issue persists, it is recommended to open a bug report in http://github.com/Shopify/checkout-sheet-kit-swift
case sdkError(underlying: Swift.Error, recoverable: Bool = true)

/// Issued when the provided checkout URL results in an error related to shop being on checkout.liquid.
/// The SDK only supports stores migrated for extensibility.
/// Issued when the storefront configuration has caused an error.
/// Note that the Checkout Sheet Kit only supports stores migrated for extensibility.
case configurationError(message: String, code: CheckoutErrorCode, recoverable: Bool = false)

/// Issued when checkout has encountered a unrecoverable error (for example server side error)
Expand All @@ -72,6 +72,17 @@ public enum CheckoutError: Swift.Error {
/// This may happen when the user has paused on checkout for a long period (hours) and then attempted to proceed again with the same checkout url
/// In event of checkoutExpired, a new checkout url will need to be generated
case checkoutExpired(message: String, code: CheckoutErrorCode, recoverable: Bool = false)

public var isRecoverable: Bool {
switch self {
case .authenticationError(_, _, let recoverable),
.checkoutExpired(_, _, let recoverable),
.checkoutUnavailable(_, _, let recoverable),
.configurationError(_, _, let recoverable),
.sdkError(_, let recoverable):
return recoverable
}
}
}

internal enum CheckoutErrorGroup: String, Codable {
Expand Down
Loading

0 comments on commit 53f283d

Please sign in to comment.