Giter VIP home page Giter VIP logo

checkout-sheet-kit-swift's Introduction

Shopify Checkout Sheet Kit - Swift

GitHub license Swift Package Manager compatible Tests GitHub Release image

Shopify Checkout Sheet Kit is a Swift Package library that enables Swift apps to provide the world’s highest converting, customizable, one-page checkout within the app. The presented experience is a fully-featured checkout that preserves all of the store customizations: Checkout UI extensions, Functions, branding, and more. It also provides platform idiomatic defaults such as support for light and dark mode, and convenient developer APIs to embed, customize, and follow the lifecycle of the checkout experience. Check out our blog to learn how and why we built the Checkout Sheet Kit.

Requirements

  • Swift 5.7+
  • iOS SDK 13.0+
  • The SDK is not compatible with checkout.liquid. The Shopify Store must be migrated for extensibility

Getting Started

The SDK is an open-source Swift Package library. As a quick start, see sample projects or use one of the following ways to integrate the SDK into your project:

Package.swift

dependencies: [
  .package(url: "https://github.com/Shopify/checkout-sheet-kit-swift", from: "3")
]

Xcode

  1. Open your Xcode project
  2. Navigate to File > Add Package Dependencies...
  3. Enter https://github.com/Shopify/checkout-sheet-kit-swift into the search box
  4. Click Add Package

For more details on managing Swift Package dependencies in Xcode, please see Apple's documentation.

CocoaPods

pod "ShopifyCheckoutSheetKit", "~> 3"

For more information on CocoaPods, please see their getting started guide.

Programmatic Usage

Once the SDK has been added as a dependency, you can import the library:

import ShopifyCheckoutSheetKit

To present a checkout to the buyer, your application must first obtain a checkout URL. The most common way is to use the Storefront GraphQL API to assemble a cart (via cartCreate and related update mutations) and query the checkoutUrl. You can use any GraphQL client to accomplish this and we recommend Shopify's Mobile Buy SDK for iOS to simplify the development workflow:

import Buy

let client = Graph.Client(
  shopDomain: "yourshop.myshopify.com",
  apiKey: "<storefront access token>"
)

let query = Storefront.buildQuery { $0
  .cart(id: "myCartId") { $0
    .checkoutUrl()
  }
}

let task = client.queryGraphWith(query) { response, error in
  let checkoutURL = response?.cart.checkoutUrl
}
task.resume()

The checkoutURL object is a standard web checkout URL that can be opened in any browser. To present a native checkout sheet in your application, provide the checkoutURL alongside optional runtime configuration settings to the present(checkout:) function provided by the SDK:

import UIKit
import ShopifyCheckoutSheetKit

class MyViewController: UIViewController {
  func presentCheckout() {
    let checkoutURL: URL = // from cart object
    ShopifyCheckoutSheetKit.present(checkout: checkoutURL, from: self, delegate: self)
  }
}

SwiftUI Usage

import SwiftUI
import ShopifyCheckoutSheetKit

struct ContentView: View {
  @State var isPresented = false
  @State var checkoutURL: URL?

  var body: some View {
    Button("Checkout") {
      isPresented = true
    }
    .sheet(isPresented: $isPresented) {
      if let url = checkoutURL {
        CheckoutSheet(url: url)
           /// Configuration
           .title("Checkout")
           .colorScheme(.automatic)
           .tintColor(.blue)
           .backgroundColor(.white)

           /// Lifecycle events
           .onCancel {
             isPresented = false
           }
           .onComplete { event in
             handleCompletedEvent(event)
           }
           .onFail { error in
             handleError(error)
           }
           .onPixelEvent { event in
             handlePixelEvent(event)
           }
           .onLinkClick { url in
              if UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url)
              }
           }
           .edgesIgnoringSafeArea(.all)
      }
    }
  }
}

Tip

To help optimize and deliver the best experience, the SDK also provides a preloading API which can be used to initialize the checkout session ahead of time.

Configuration

The SDK provides a way to customize the presented checkout experience via the ShopifyCheckoutSheetKit.configuration object.

colorScheme

By default, the SDK will match the user's device color appearance. This behavior can be customized via the colorScheme property:

// [Default] Automatically toggle idiomatic light and dark themes based on device preference (`UITraitCollection`)
ShopifyCheckoutSheetKit.configuration.colorScheme = .automatic

// Force idiomatic light color scheme
ShopifyCheckoutSheetKit.configuration.colorScheme = .light

// Force idiomatic dark color scheme
ShopifyCheckoutSheetKit.configuration.colorScheme = .dark

// Force web theme, as rendered by a mobile browser
ShopifyCheckoutSheetKit.configuration.colorScheme = .web

tintColor

If the checkout session is not ready and being initialized, a progress bar is shown and can be customized via the tintColor property:

// Use a custom UI color
ShopifyCheckoutSheetKit.configuration.tintColor = UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00)

// Use a system color
ShopifyCheckoutSheetKit.configuration.tintColor = .systemBlue

Note: use preloading to optimize and deliver an instant buyer experience.

backgroundColor

While the checkout session is being initialized, the background color of the view can be customized via the backgroundColor property:

// Use a custom UI color
ShopifyCheckoutSheetKit.configuration.backgroundColor = UIColor(red: 0.09, green: 0.45, blue: 0.69, alpha: 1.00)

// Use a system color
ShopifyCheckoutSheetKit.configuration.backgroundColor = .systemBackground

title

By default, the Checkout Sheet Kit will look for a shopify_checkout_sheet_title key in a Localizable.xcstrings file to set the sheet title, otherwise it will fallback to "Checkout" across all locales.

The title of the sheet can be customized by either setting a value for the shopify_checkout_sheet_title key in the Localizable.xcstrings file for your application or by configuring the title property of the ShopifyCheckoutSheetKit.configuration object manually.

// Hardcoded title, applicable to all languages
ShopifyCheckoutSheetKit.configuration.title = "Custom title"

Here is an example of a Localizable.xcstrings containing translations for 2 locales - en and fr.

{
  "sourceLanguage": "en",
  "strings": {
    "shopify_checkout_sheet_title": {
      "extractionState": "manual",
      "localizations": {
        "en": {
          "stringUnit": {
            "state": "translated",
            "value": "Checkout"
          }
        },
        "fr": {
          "stringUnit": {
            "state": "translated",
            "value": "Caisse"
          }
        }
      }
    }
  }
}

SwiftUI Configuration

Similarly, configuration modifiers are available to set the configuration of your checkout when using SwiftUI:

CheckoutSheet(checkout: checkoutURL)
  .title("Checkout")
  .colorScheme(.automatic)
  .tintColor(.blue)
  .backgroundColor(.black)

Note

Note that if the values of your SwiftUI configuration are variable and you are using preload(), you will need to call preload() each time your variables change to ensure that the checkout cache has been invalidated, for checkout to be loaded with the new configuration.

Preloading

Initializing a checkout session requires communicating with Shopify servers and, depending on the network weather and the quality of the buyer's connection, can result in undesirable wait time for the buyer. To help optimize and deliver the best experience, the SDK provides a preloading hint that allows app developers to signal and initialize the checkout session in the background and ahead of time.

Preloading is an advanced feature that can be toggled via a runtime flag:

ShopifyCheckoutSheetKit.configure {
  $0.preloading.enabled = false // defaults to true
}

When enabled, preloading a checkout is as simple as:

ShopifyCheckoutSheetKit.preload(checkout: checkoutURL)

Setting enabled to false will cause all calls to the preload function to be ignored. This allows the application to selectively toggle preloading behavior as a remote feature flag or dynamically in response to client conditions — e.g. when data saver functionality is enabled by the user.

ShopifyCheckoutSheetKit.preloading.enabled = false
ShopifyCheckoutSheetKit.preload(checkout: checkoutURL) // no-op

Lifecycle management for preloaded checkout

Preloading renders a checkout in a background webview, which is brought to foreground when ShopifyCheckoutSheetKit.present() is called. The content of preloaded checkout reflects the state of the cart when preload() was initially called. If the cart is mutated after preload() is called, the application is responsible for invalidating the preloaded checkout to ensure that up-to-date checkout content is displayed to the buyer:

  1. To update preloaded contents: call preload() once again
  2. To invalidate/disable preloaded content: toggle ShopifyCheckoutSheetKit.preloading.enabled

The library will automatically invalidate/abort preload under following conditions:

  • Request results in network error or non 2XX server response code
  • The checkout has successfully completed, as indicated by the server response
  • When ShopifyCheckoutSheetKit.Configuration object is updated by the application (e.g., theming changes)

A preloaded checkout is not automatically invalidated when checkout sheet is closed. For example, if a buyer loads the checkout and then exits, the preloaded checkout is retained and should be updated when cart contents change.

Additional considerations for preloaded checkout

  1. Preloading is a hint, not a guarantee: the library may debounce or ignore calls depending on various conditions; the preload may not complete before present(checkout:) is called, in which case the buyer may still see a progress bar while the checkout session is finalized.
  2. Preloading results in background network requests and additional CPU/memory utilization for the client and should be used responsibly. For example, conditionally based on state of the client and when there is a high likelihood that the buyer will soon request to checkout.

Monitoring the lifecycle of a checkout session

You can use the ShopifyCheckoutSheetKitDelegate protocol to register callbacks for key lifecycle events during the checkout session:

extension MyViewController: ShopifyCheckoutSheetKitDelegate {
  func checkoutDidComplete(event: CheckoutCompletedEvent) {
    // Called when the checkout was completed successfully by the buyer.
    // Use this to update UI, reset cart state, etc.
  }

  func checkoutDidCancel() {
    // Called when the checkout was canceled by the buyer.
    // Use this to call `dismiss(animated:)`, etc.
  }

  func checkoutDidFail(error: CheckoutError) {
    // Called when the checkout encountered an error and has been aborted. The callback
    // provides a `CheckoutError` enum, with one of the following values:
    // Internal error: exception within the Checkout SDK code
    // You can inspect and log the Erorr and stacktrace to identify the problem.
    case sdkError(underlying: Swift.Error)

    // Issued when the provided checkout URL results in an error related to shop configuration.
    // Note: The SDK only supports stores migrated for extensibility.
    case configurationError(message: String)

    // Unavailable error: checkout cannot be initiated or completed, e.g. due to network or server-side error
    // The provided message describes the error and may be logged and presented to the buyer.
    case checkoutUnavailable(message: String)

    // Expired error: checkout session associated with provided checkoutURL is no longer available.
    // The provided message describes the error and may be logged and presented to the buyer.
    case checkoutExpired(message: String)
  }

  func checkoutDidClickLink(url: URL) {
    // Called when the buyer clicks a link within the checkout experience:
    //  - email address (`mailto:`),
    //  - telephone number (`tel:`),
    //  - web (`http:`)
    // and is being directed outside the application.
  }

  // Issued when the Checkout has emit a standard or custom Web Pixel event.
  // Note that the event must be handled by the consuming app, and will not be sent from inside the checkout.
  // See below for more information.
  func checkoutDidEmitWebPixelEvent(event: PixelEvent) {
    switch event {
      case .standardEvent(let standardEvent):
        recordAnalyticsEvent(standardEvent)
      case .customEvent(let customEvent):
        recordAnalyticsEvent(customEvent)
    }
  }
}

Error handling

In the event of a checkout error occurring, the Checkout Sheet Kit may attempt a retry to recover from the error. Recovery will happen in the background by discarding the failed webview and creating a new "recovery" instance. Recovery will be attempted in the following scenarios:

  • The webview receives a response with a 5XX status code
  • An internal SDK error is emitted

There are some caveats to note when this scenario occurs:

  1. The checkout experience may look different to buyers. Though the sheet kit will attempt to load any checkout customizations for the storefront, there is no guarantee they will show in recovery mode.
  2. The checkoutDidComplete(event:) will be emitted with partial data. Invocations will only receive the order ID via event.orderDetails.id.
  3. checkoutDidEmitWebPixelEvent lifecycle methods will not be emitted.

Should you wish to opt-out of this fallback experience entirely, you can do so by adding a shouldRecoverFromError(error:) method to your delegate controller. Errors given to the checkoutDidFail(error:) lifecycle method, will contain an isRecoverable property by default indicating whether the request should be retried or not.

func shouldRecoverFromError(error: CheckoutError) {
  return error.isRecoverable // default
}

CheckoutError

Type Description Recommendation
.configurationError(code: .checkoutLiquidNotAvailable) checkout.liquid is not supported. Please migrate to checkout extensibility.
.checkoutUnavailable(message: "Forbidden") Access to checkout is forbidden. This error is unrecoverable.
.checkoutUnavailable(message: "Internal Server Error") An internal server error occurred. This error will be ephemeral. Try again shortly.
.checkoutUnavailable(message: "Storefront password required") Access to checkout is password restricted. We are working on ways to enable the Checkout Sheet Kit for usage with password protected stores.
.checkoutExpired(message: "Checkout already completed") The checkout has already been completed If this is incorrect, create a new cart and open a new checkout URL.
.checkoutExpired(message: "Cart is empty") The cart session has expired. Create a new cart and open a new checkout URL.
.sdkError(underlying:) An error was thrown internally. Please open an issue in this repo with as much detail as possible. URL.

Integrating with Web Pixels, monitoring behavioral data

App developers can use lifecycle events to monitor and log the status of a checkout session.

For behavioural monitoring, Checkout Web Pixel standard and custom events will be relayed back to your application through the checkoutDidEmitWebPixelEvent delegate hook. The responsibility then falls on the application developer to ensure adherence to Apple's privacy policy and local regulations like GDPR and ePrivacy directive before disseminating these events to first-party and third-party systems.

Here's how you might intercept these events:

class MyViewController: UIViewController {
  private func sendEventToAnalytics(event: StandardEvent) {
    // Send standard event to third-party providers
  }

  private func sendEventToAnalytics(event: CustomEvent) {
    // Send custom event to third-party providers
  }

  private func recordAnalyticsEvent(standardEvent: StandardEvent) {
    if hasPermissionToCaptureEvents() {
      sendEventToAnalytics(event: standardEvent)
    }
  }

  private func recordAnalyticsEvent(customEvent: CustomEvent) {
    if hasPermissionToCaptureEvents() {
      sendEventToAnalytics(event: CustomEvent)
    }
  }
}

extension MyViewController: ShopifyCheckoutSheetKitDelegate {
  func checkoutDidEmitWebPixelEvent(event: PixelEvent) {
    switch event {
      case .standardEvent(let standardEvent):
        recordAnalyticsEvent(standardEvent: standardEvent)
      case .customEvent(let customEvent):
        recordAnalyticsEvent(customEvent: customEvent)
    }
  }
}

Note

You may need to augment these events with customer/session information derived from app state.

Note

The customData attribute of CustomPixelEvent can take on any shape. As such, this attribute will be returned as a String. Client applications should define a custom data type and deserialize the customData string into that type.

Integrating identity & customer accounts

Buyer-aware checkout experience reduces friction and increases conversion. Depending on the context of the buyer (guest or signed-in), knowledge of buyer preferences, or account/identity system, the application can use one of the following methods to initialize a personalized and contextualized buyer experience.

Cart: buyer bag, identity, and preferences

In addition to specifying the line items, the Cart can include buyer identity (name, email, address, etc.), and delivery and payment preferences: see guide. Included information will be used to present pre-filled and pre-selected choices to the buyer within checkout.

Multipass

Shopify Plus merchants using Classic Customer Accounts can use Multipass (API documentation) to integrate an external identity system and initialize a buyer-aware checkout session.

{
  "email": "<Customer's email address>",
  "created_at": "<Current timestamp in ISO8601 encoding>",
  "remote_ip": "<Client IP address>",
  "return_to": "<Checkout URL obtained from Storefront API>"
}
  1. Follow the Multipass documentation to create a Multipass URL and set return_to to be the obtained checkoutUrl
  2. Provide the Multipass URL to present(checkout:)

Important

The above JSON omits useful customer attributes that should be provided where possible and encryption and signing should be done server-side to ensure Multipass keys are kept secret.

Shop Pay

To initialize accelerated Shop Pay checkout, the cart can set a walletPreference to 'shop_pay'. The sign-in state of the buyer is app-local. The buyer will be prompted to sign in to their Shop account on their first checkout, and their sign-in state will be remembered for future checkout sessions.

Customer Account API

We are working on a library to provide buyer sign-in and authentication powered by the new Customer Account API—stay tuned.


Contributing

We welcome code contributions, feature requests, and reporting of issues. Please see guidelines and instructions.

License

Shopify's Checkout Sheet Kit is provided under an MIT License.

checkout-sheet-kit-swift's People

Contributors

allyshiasewdat avatar cianbuckley avatar cocoahero avatar dependabot[bot] avatar igrigorik avatar josemiguel-alvarez avatar kiftio avatar leandrooriente avatar lynnsh avatar markmur avatar ocastx avatar tejasm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

checkout-sheet-kit-swift's Issues

Preloading checkout triggers an SMS with Shop Pay verification code

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0.0

Reproducible sample code

No response

Steps to Reproduce

In many cases when the preload method is called, the user is sent an OTP code for shop pay despite not yet opening the checkout view controller. This is disconcerting to the user, who is not expecting to get an SMS at this point and has nothing visible to use it for.

Steps to reproduce:

  1. With something in your cart, open the Shopify checkout sheet once and then close it. (This step might not be necessary, but for me it always triggers the bug. I'm not sure why.)
  2. Perform an action that causes the app to call ShopifyCheckoutSheetKit.preload again (e.g. adding something to the cart)

Expected Behavior

Nothing happens visibly, checkout sheet preloads in background

Actual Behavior

I receive a text message of the format "123456 is your Shop verification code"

Screenshots/Videos/Log output

RPReplay_Final1716570197.MOV

Storefront domain

yaysay.myshopify.com

UI discrepancy between UIKit and SwiftUI

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

2.0.1

Do you have a minimum reproducible example?

No response

Steps to Reproduce

  • Display sheet via Swift UI
  • Display sheet via UIKit
  • Compare navigation bars

Expected Behavior

Navigation bar on UIKit sheet to filled in on presentation, not just when the checkout scrolls down.

Actual Behavior

UIKit sheet displays a clear navigation bar on presentation which looks off. When you scroll down the on the UIKit sheet the navigation bar fills in. This feels a buggy and looks off which is not desired in a checkout flow.

Screenshots/Videos/Log output

  1. Swift UI on present (looks good)

Simulator Screenshot - iPhone 15 Pro - 2024-05-08 at 10 25 19

  1. UIKit on present (looks off)

Simulator Screenshot - iPhone 15 Pro Max - 2024-05-08 at 14 01 02

Storefront domain

No response

Regressed Issue: buyer identity attach not working

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

1.x 2.x and 3.x

Reproducible sample code

No response

Steps to Reproduce

Using buy sdk:
Create a new cart
Add product
Attach buyer identity via access token

  • can verify via buy sdk that identity is attached
    using this library:
    open checkout vwith checkoutURL

Expected Behavior

User should be attached to cart in checkout

Actual Behavior

Checkout fields are prefilled but no actual user is attached/logged in.
Same result when copying the checkout url in iPhone safari browser.
BUT, working as expected when checkout url is copied into a MacBook safari browser

Similar thing already happened earlier this year: see issue: https://github.com/Shopify/checkout-sheet-kit-swift/issues/160

This bug has a big impact on us, as this affects our loyalty program, and we need urgent resolution

Screenshots/Videos/Log output

Screenshot 2024-08-07 at 19 11 49 Screenshot 2024-08-07 at 19 16 09 Screenshot 2024-08-07 at 19 15 53

Sreenshots show the checkout on the app before opening the checkout url in laptop browser and after.
so opening the checkout url once in a browser fixes the issue for this single cart...

Storefront domain

waterdrop-official.myshopify.com

but happens on multiple of our stores

Can we change Pay Now Button Color?

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

2.0.1

Reproducible sample code

No response

Steps to Reproduce

Not able to change Pay Now Button Color with Existing Properties

Expected Behavior

There should be a property to change button Pay Now Color

Actual Behavior

Not able to change Pay Now Button Color with Existing Properties

Screenshots/Videos/Log output

image

Storefront domain

https://revibe-me.myshopify.com/

Not able to open checkout sheet in any other language

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

2.0.1

Reproducible sample code

No response

Steps to Reproduce

We tried two approaches

  1. Created cart URL via GraphQL Store API passing language "AR" in buyer identity
    and passed to CheckoutSheet but yet it opened up in English
  2. We opened the checkout web page from our website in Arabic language because it works really fine there and tried that url in checkoutsheet but yet it opened up in English

Expected Behavior

It was supposed to be opened in Arabic

Actual Behavior

But it opened up in English

Screenshots/Videos/Log output

No response

Storefront domain

revibe.me
revibe-me

Presenting in SwiftUI

Hi, I'm trying to use this SDK in a SwiftUi app. Since this view appears to be a UIKit one, I wrapped in it a UIViewControllerRepresentable to be used in Swiftui.

struct CheckoutView: UIViewControllerRepresentable {
    let url: URL
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = UIViewController()
        return viewController
    }
    
    func updateUIViewController(_ viewController: UIViewController, context: Context) {
        ShopifyCheckout.present(checkout: url, from: viewController, delegate: context.coordinator)
    }
    
    class Coordinator: NSObject, CheckoutDelegate {
        var parent: CheckoutView
        
        init(_ checkoutView: CheckoutView) {
            parent = checkoutView
        }
        
        func checkoutDidFail(error: CheckoutError) {
        }
        
        func checkoutDidComplete() {
        }
        
        func checkoutDidCancel() {
        }
    }
}

However when I try to present this view as a modal sheet like below,

VStack {
// ...
}
.sheet(isPresented: $cartViewModel.isShowingCheckoutView) {
    CheckoutView(url: cartViewModel.cart!.checkoutURL)
}

I get the following error.

Attempt to present <UINavigationController: 0x10513ae00> on <UIViewController: 0x104e0b860> (from <UIViewController: 0x104e0b860>) whose view is not in the window hierarchy.

Not sure what might be the issue. An example on how to use this SDK in SwiftUI in the docs would be nice.

Thank you!


Update 1

I looked at how view controllers are displayed in SwiftUI. This example shows how it's done for MFMailComposeViewController. As you can see, the view controller is configured in the makeUIViewController() method and SwiftUI automatically handles the presenting part.

I think the current way of showing the checkout view controller might have to be re-written to support both UIKit and SwiftUI. Maybe exposing the internal CheckoutViewController?

[Feature Request] Test mode when creating checkouts

During the implementation process we found it tricky to test all of delegate methods on checkout completion. It would be useful if there was a way to artificially trigger these methods so that we can test the flows and handle the error states/analytics events accordingly.

The only way we were able to do this was to complete checkouts using test payment details which seemed cumbersome.

CheckoutSheet SwiftUI issue 2

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0.1

Reproducible sample code

No response

Steps to Reproduce

NavigationView{
Vstack{
.sheet(isPresented: $isGuestCheckout){
if let url = URL(string: self.myCheckoutURL){
CheckoutSheet(checkout: url)
.onCancel {
if isCheckoutCompleted {
UserDefaults.standard.set("", forKey: k_cartID)
GlobalObjects.myCartID = ""
}
isGuestCheckout = false
}
.onComplete { event in
print("COMPLETE:: (event)")
print("ORDER SUCCESS")
isCheckoutCompleted = true
}
.onFail { error in
print("FAIL:: (error)")
print("FAIL:: (error.localizedDescription)")
}
.onPixelEvent { event in
print("PIXEL:: (event)")
}
.onLinkClick { url in
print("URL: (url)")
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
.edgesIgnoringSafeArea(.all)
}
}
}
}

Expected Behavior

I want to stay checkout sheet presented and load my checkouturl successfully and do checkout for existing cart and logged in user.

Actual Behavior

When i tries to present checkout sheet by input checkout url from my addresseslist view after updating buyeridentity it's automatically dismissed and moves back to the cart page.

Screenshots/Videos/Log output

No response

Storefront domain

digimonk-shopee.myshopify.com

Tasks

No tasks being tracked yet.

Camera is opening as Live Broadcast Video instead of normal Camera to take picture.

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

2.0.1

Reproducible sample code

No response

Steps to Reproduce

  1. Open Checkout
  2. Go with any installment plan, in my case it's Tamara.
  3. Tamara asks for ID Verification where the camera opens
  4. When camera opens it opens as Live broadcast video instead of Camera to take picture of ID.

Expected Behavior

Camera should open normal Camera to take pictures of ID instead of opening Live Broadcast Video.

Actual Behavior

When Opening camera on checkout it opens Live broadcast video instead of Camera to take picture of ID on Tamara.

Screenshots/Videos/Log output

No response

Storefront domain

https://revibe.me/

Crash when tapping AmazonPay in ShopifyCheckoutKit presented from SwiftUI

Hi, I'm encountering a crash in the app, and it's causing me some trouble!

Description

When tapping AmazonPay within ShopifyCheckoutKit displayed from SwiftUI, it crashes.

Steps to Reproduce

  1. Use SwiftUI to call the following ShopifyCheckout ViewController.
  2. Display ShopifyCheckoutKit.
  3. Tap the AmazonPay button to proceed with the payment.

Expected Behavior

The AmazonPay payment process should proceed without any issues, similar to credit card payments, ShopPay, and ApplePay.

Actual Behavior

The app crashes when attempting to proceed with the AmazonPay payment. Other payment methods, such as credit card payments, ShopPay, and ApplePay, work correctly.

Code

struct ShopifyCheckout: UIViewControllerRepresentable {
    let url: URL
    let onCompleted: () -> Void

    func makeUIViewController(context: Context) -> UIViewController {
        let viewController = CheckoutViewController(url: url, onCompleted: onCompleted)
        return viewController
    }

    func updateUIViewController(_ viewController: UIViewController, context: Context) {}
}

private final class CheckoutViewController: UIViewController, CheckoutDelegate {
    let url: URL
    let onCompleted: () -> Void

    init(url: URL, onCompleted: @escaping () -> Void) {
        self.url = url
        self.onCompleted = onCompleted
        super.init(nibName: nil, bundle: nil)
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidAppear(_ animated: Bool) {
        ShopifyCheckoutKit.present(checkout: url, from: self, delegate: self)
        super.viewDidAppear(animated)
    }

    func checkoutDidComplete() {
        dismiss(animated: true)
        super.dismiss(animated: true)
        onCompleted()
    }

    func checkoutDidCancel() {
        dismiss(animated: true)
        super.dismiss(animated: true)
        onCompleted()
    }

    func checkoutDidFail(error: ShopifyCheckoutKit.CheckoutError) {
        fatalError(error.localizedDescription)
    }

    func checkoutDidClickContactLink(url: URL) {
        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url)
        }
    }
}

Environment

iOS Version: iOS 17.1.2
Xcode Version: Xcode 15.0.1
ShopifyCheckoutKit Version: 0.6.0

Cart buyer identity is not pre-filled in the checkout

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0.1 (latest)

Reproducible sample code

MobileBuyIntegration Sample code

Steps to Reproduce

  1. Create a cart with buyerIdentity using the cartCreate API.
  2. Show CheckoutSheet using the cart's checkoutUrl.
  3. Do not pre-fill the buyerIdentity address (only pre-fill the email).

Expected Behavior

buyerIdentity data in Cart should be pre-filled including address in checkout

Actual Behavior

Do not pre-fill the buyerIdentity address (only pre-fill the email).

Screenshots/Videos/Log output

Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-07-11.at.10.56.28.mp4

Storefront domain

Checkout Sheet Kit doesn't work with an extensibility preview store

We're trying to test our implementation of CSK using a developer preview store setup with checkout extensibility.

However, as we are using a preview store we are asked to enter the store password in the checkout flow which redirects the user back to the homepage rather than the checkout flow. This effectively makes it impossible to test the CSK.

Simulator.Screen.Recording.-.iPhone.15.Pro.-.2024-06-30.at.10.05.02.mp4

Sheet always displays loading template at the top

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0.0

Reproducible sample code

No response

Steps to Reproduce

Building the cart and checkout URL via:

let linesInput: [Buy.Storefront.CartLineInput] = [
  .create(merchandiseId: GraphQL.ID(rawValue: productId), quantity: .value(Int32(quantity)))
]

let cartInput = Storefront.CartInput.create(
  lines: .value(linesInput)
)

let createCartMutation = Storefront.buildMutation { $0
    .cartCreate(input: cartInput) { $0
        .cart { $0
            .id()
            .checkoutUrl()
        }
    }
}

let mutateTask = shopifyClient.mutateGraphWith(createCartMutation) { response, error in
    if let error = error {
        print("Error creating cart: \(error.localizedDescription)")
        return
    }

    guard let cart = response?.cartCreate?.cart else {
        print("No cart found in response")
        return
    }

    print("Cart Id, url: \(cart.checkoutUrl)")
    DispatchQueue.main.async {
        self.checkoutURL = cart.checkoutUrl
        self.isPresented = true
    }
}

mutateTask.resume()

Combined with showing the Sheet via:

                      `             .sheet(isPresented: $isPresented) {
               if let url = checkoutURL {
                   CheckoutSheet(checkout: url)
                   /// Configuration
                       .title("Checkout")
                       .colorScheme(.automatic)
                       .tintColor(.blue)
                       .backgroundColor(.white)

                   /// Lifecycle events
                       .onCancel {
                           isPresented = false
                       }
                       .onComplete { event in
                           //handleCompletedEvent(event)
                           print("Checkout compl: ", event)
                       }
                       .onFail { error in
                           //handleError(error)
                           print("Checkout err: " , error)
                       }
                       .onPixelEvent { event in
                           //handlePixelEvent(event)
                       }
                       .onLinkClick { url in
                           if UIApplication.shared.canOpenURL(url) {
                               UIApplication.shared.open(url)
                           }
                       }
                       .edgesIgnoringSafeArea(.all)
               }
           }`

Expected Behavior

When I open the sheet, I only see the loading template when content is loading.

Actual Behavior

When I open the sheet, I see the loading template even after content is loaded.

Screenshots/Videos/Log output

20240607_010012000_iOS.mp4

Storefront domain

d669cd-92.myshopify.com

[Feature Request] Bypass online store password

Hello,

I did not where to put this except here. According to https://github.com/Shopify/checkout-sheet-kit-swift/blob/249b80f7ecfa29b632cb8dd85b8eee284dd7165d/.github/CONTRIBUTING.md#proposing-features it's possible to propose features :)

We are heavy users of the new native checkout sheet and already using this in production. Is there any way to bypass the online stores password protection?

Our use case is simple:

  • Online Store is closed (password protected) due to mobile app-only pre-sale
  • Native Checkout should work (while store is still password protected)

Thanks!

CheckoutSheet swiftui issue

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0.1

Reproducible sample code

No response

Steps to Reproduce

Vstack{}
.sheet(isPresented: $isGuestCheckout){
            if let url = URL(string: self.myCheckoutURL){
                CheckoutSheet(checkout: url)
                    .onCancel {
                        if isCheckoutCompleted {
                            
                            UserDefaults.standard.set("", forKey: k_cartID)
                            GlobalObjects.myCartID = ""
                        }
                        isGuestCheckout = false
                    }
                    .onComplete { event in
                        print("COMPLETE:: \(event)")
                        print("ORDER SUCCESS")
                        isCheckoutCompleted = true
                    }
                    .onFail { error in
                        print("FAIL:: \(error)")
                        print("FAIL:: \(error.localizedDescription)")
                    }
                    .onPixelEvent { event in
                        print("PIXEL:: \(event)")
                    }
                    .onLinkClick { url in
                        print("URL: \(url)")
                        if UIApplication.shared.canOpenURL(url) {
                            UIApplication.shared.open(url)
                        }
                    }
                    .edgesIgnoringSafeArea(.all)
            }
        }

Expected Behavior

I want to load my checkouturl successfully in sheet and do checkout. for cart.

Actual Behavior

when i present checkout sheet by input my checkouturl from mycart page it's presented and gives fail error like bewlow beforeloading url:

FAIL:: checkoutUnavailable(message: "checkout crashed", code: ShopifyCheckoutSheetKit.CheckoutUnavailable.clientError(code: ShopifyCheckoutSheetKit.CheckoutErrorCode.unknown), recoverable: true)

Screenshots/Videos/Log output

No response

Storefront domain

digimonk-shopee.myshopify.com

How is "Throttled" handled?

One big issue with the checkoutCreate mutation that I've encountered is Throttled error when there is over 4000 checkouts in under a minute, which is a common occurrence during flash sales.

  1. Does anyone know if the cartCreate mutation is restricted by this same limitation?
  2. If it is, will it still pass back a cart URL just fine or will it return something similar to the checkout create error?
    2a) When we present the CheckoutViewController with this new mobile checkout, will the user be put in the "queue" that desktop/site users go thru when there are lots of checkouts?

Thanks

Bug Report: Dark Mode View Controller

The color scheme of the modal ViewController is based on system settings, instead of the theme passed to ShopifyCheckoutKit.configuration.colorScheme.
The light checkout can be presented in a dark ViewController.

IMG_0016

Checkout has expired

First of all, thank you so much for this great SDK! We couldn't wait to integrate it in our mobile apps and here is the first (minor) issue we've discovered:

When calling present with a checkout URL of a store that has no checkout extensibility enabled, the SDK throws an error "Checkout has expired". We tried multiple different URLs (checkout URL and cart URL) but the error was always the same.

Finally, we switched to a new development shop where the error did not appear anymore. The difference between the stores was that one has checkout extensibility enabled and the other one not.

Steps to reproduce

  1. Create checkout URL on store that has checkout.liquid modified
  2. Call ShopifyCheckout.present()

Expected behavior

Error message which explains that Mobile Checkout SDK does not work with Shops without checkout extensibility enabled

Actual behavior

Error message checkoutExpired(message: "Checkout has expired")

Logged in user information does not appear on the checkout sheet after discounts are applied.

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0

Reproducible sample code

No response

Steps to Reproduce

I have attached a video.

Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2024-05-25.at.17.28.34.mp4

Expected Behavior

It should show user info with discounts as well.

Actual Behavior

When discounts are applied to an order and a user is logged in, their information fails to appear on the checkout sheet. However, if we copy the checkout URL and paste it into any browser, all the information is visible. Yet, on the checkout sheet, it appears empty.

Screenshots/Videos/Log output

Simulator Screenshot - iPhone 15 Pro Max - 2024-05-25 at 17 26 23

Storefront domain

https://56a150-55.myshopify.com/

Receiving checkoutLiquidNotMigrated error even though I actually migrated the store

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

2.0.1

Do you have a minimum reproducible example?

I'm building a mobile app for my shopify store. In order to provide the best checkout experience I've integrated Checkout Sheet Kit for Swift. At the moment my store is using checkout.liquid, I’m planning to make the final upgrade to checkout extensibility. I made some test but even though I switch to checkout extensibility publishing my theme Checkout Sheet Kit for Swift give me this error: The checkout url provided has resulted in an error. The store is still using checkout.liquid, whereas the checkout SDK only supports checkout with extensibility.

Steps to Reproduce

Have a store currently using checkout.liquid
Publish the theme on Customize your checkout settings panel.
Open a native checkout dialog using the present function.

Expected Behavior

I want to open a native checkout dialog after I switch from checkout.liquid to checkout extensibility

Actual Behavior

When I open a native checkout dialog using the present function I'm getting back this error:
The checkout url provided has resulted in an error. The store is still using checkout.liquid, whereas the checkout SDK only supports checkout with extensibility.
even though I actually did the switch

Screenshots/Videos/Log output

No response

Storefront domain

https://casinetto.myshopify.com

Checkout sheet appears blank on initial presentation

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0.0

Reproducible sample code

No response

Steps to Reproduce

On my view .task, I create a shopifyClient and then enable:

ShopifyCheckoutSheetKit.configure {
                $0.preloading.enabled = false // defaults to true
              }

When I select a shirt size, I create a cart and then get the associated checkoutUrl.

 func updateCheckoutUrlForSize(size: String ) {
        if let productId = getProductId(forTitle: size){
            print("PrdId: ", productId, size)
            let linesInput: [Buy.Storefront.CartLineInput] = [
                .create(merchandiseId: GraphQL.ID(rawValue: productId), quantity: .value(Int32(quantity)))
            ]

            let cartInput = Storefront.CartInput.create(
                lines: .value(linesInput)
            )

            let createCartMutation = Storefront.buildMutation { $0
                .cartCreate(input: cartInput) { $0
                    .cart { $0
                        .id()
                        .checkoutUrl()
                    }
                }
            }

            let createCartMutationTask = shopifyBuyClient?.mutateGraphWith(createCartMutation) { response, error in
                if let error = error {
                    print("Error creating cart: \(error.localizedDescription)")
                    return
                }

                guard let cart = response?.cartCreate?.cart else {
                    print("No cart found in response")
                    return
                }

                print("Cart url: \(cart.checkoutUrl)")
                DispatchQueue.main.async {
                    self.checkoutURL = cart.checkoutUrl
                }
            }

            createCartMutationTask?.resume()
        }
    } 

When I select the checkout button, I only set the presented state as the url has already been determined via above.

  Button(action: {
                     if self.checkoutURL != nil {
                         print("Current checkout URL: ", checkoutURL)
                         self.isPresented = true
                     }
                 }, label: {
                     Text("Checkout")
                         .frame(width: 250)
                         .font(Font.custom("Inter", size: 20))
                         .foregroundStyle(.black)
                         .fontWeight(.semibold)
                         .padding()
                         .background(sizeSelected == nil ? Color.yellow.opacity(0.5) : Color.yellow)
                         .cornerRadius(8)
                 })
                 .disabled( sizeSelected == nil )

Expected Behavior

Given a valid checkoutUrl, the Checkout Sheet should display a proper checkout page for the customer.

Actual Behavior

With a valid checkoutUrl, the Checkout Sheet appears completely empty and prevents a customer from purchasing.

Screenshots/Videos/Log output

0610.1.mp4

When I check the URL in my logs, it's valid and opens correctly in a webpage. After selecting a different shirt size ( which will recreate the cart ), and then opening the sheet again, the page loads.

Storefront domain

d669cd-92.myshopify.com

Fetching Order ID or Checkout ID / Status upon completion

Hi, I'm looking for guidance on how to identify orders placed via this SDK.

Our use case is to provide the checkout experience to users logged into our app. Upon placing the order, we will receive a web hook event on the backend, that we want to associate with the user, but its unclear how I can reliably do this.

The completion/cancellation delegate passes no information back to the calling context. I have the original Cart object used to get the checkoutURL, but how can I match that to a Checkout ID (or something similar) to pass along to the backend, that we could use to match the incoming webhook?

Building a custom checkout with the Buy SDK makes this trivial, but so much more is abstracted away here.

Any guidance is appreciated, thanks in advance!

More theme options when using the `web` theme

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0.1

Reproducible sample code

No response

Steps to Reproduce

  1. Set device to dark mode
  2. Set ShopifyCheckoutSheetKit.configuration.colorScheme to .web
  3. Open checkout sheet

Expected Behavior

Our suggestion is to provide a way to still force the theme to use light/dark mode while using the web theme instead of using the automatic theming.

Actual Behavior

The automatic theme is used which sets the color of the background and the sheet header to dark when the device is set to dark mode.

Screenshots/Videos/Log output

Simulator Screenshot - iPhone 15 Pro Max - 2024-07-15 at 12 07 03

Storefront domain


Make CheckoutWebViewController open or public

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

3.0.1

Reproducible sample code

No response

Steps to Reproduce

N/A

Expected Behavior

We want to use our own navigation controller and not use the close button from your controller UI so that we can gain access to your bridge handling but without most of the UI. Anyway to workaround this I've nested your view controller in a container controller to fit with our platform.

Actual Behavior

Currently the initialiser is public but the class is internal.

Screenshots/Videos/Log output

No response

Storefront domain

N/A

Cart Buyer identity lost

Describe the bug

Cart attached buyer identity gets automatically reset every time checkout is opened after first time opening the checkout.

To Reproduce

  1. Attach buyer identity (customerAccessToken) to cart via storefront graphql
  2. Show CheckoutSheet -> Customer is correctly attached
  3. Cancel the checkout (does not matter if swipe down, close app, etc)
  4. Run same flow again (attach, open sheet)
  5. Buyer identity is lost

Expected behavior

Buyer identity should not be lost

Versions (please complete the following information):

Only one shipping address is being shown

When creating a cart using the cartCreate mutation and providing multiple deliveryAddressPreferences for the CartBuyerIdentityInput I can only see one address in the checkout sheet. When I copy the carts' checkoutUrl and paste it in my browser I can see multiple (those I've sent with the mutation) addresses in the dropdown.

Thank you in advance!

EDIT: Same issue on android side, but I haven't opened an issue there yet. Let's check if this is really a bug first :)

Login customer using their access token

What area is the issue related to?

Checkout Sheet Kit

What version of Checkout Sheet Kit are you using?

latest

Reproducible sample code

No response

Steps to Reproduce

Set X-Shopify-Customer-Access-Token header.
Open Checkout Web View

How do we log in the customer automatically now?

Expected Behavior

Pass header for customer access token and it should log in the customer automatically - Before we used to be able to pass X-Shopify-Customer-Access-Token

Actual Behavior

User needs to log in every time

Screenshots/Videos/Log output

No response

Storefront domain

All

Feature request: expose `modalPresentationStyle` or `isModalInPresentation`

In the checkout process, the package currently uses the present ViewController method, which presents the checkout as a card by default.
However, this card-style presentation allows users to swipe it down during checkout easily and interrupt the checkout.

It would be highly beneficial if we could have control over the modalPresentationStyle and the isModalInPresentation properties to either display it in full-screen mode or prevent it from being dismissed by swiping.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.