Checkout Screen

Checkout Screen

The Checkout screen takes a quote and outputs a booked trip or an error when booking the quote.

The quote capabilities section can be displayed by clicking on the Learn More
The pricing info section can be expanded or retracted by pressing the info button right next to the METERED | FIXED | ESTIMATED label

Colors can be fully customized by overriding the color pallet provided by the Karhoo UISDK.

886886

The Android checkout screen

Loyalty

The loyalty section is available when Loyalty is enabled for the user. In order to enable the loyalty, you need to integrate with our Loyalty API. This would allow the users to earn or burn loyalty points from their balance after completing taxi rides with Karhoo.

14401440

Android Checkout Component with all sections expanded

517517

The Checkout is implemented as an iOS ViewController / Android Activity to automatically handle the presentation, dismissal and handling of the payment flow.

let bookingDetails = KarhooBookingStatus.shared.getBookingDetails()!
let checkoutScreen = KarhooUI()
        .screens()
        .checkout()
        .buildCheckoutScreen(quote: quote,
                             bookingDetails: bookingDetails,
                             bookingMetadata: nil,
                             callback: { [weak self] result in
                                if let trip = result.completedValue() {
                                  print(trip)
                                }
                         })

checkoutScreen.modalPresentationStyle = .fullScreen
self.present(checkoutScreen, animated: true, completion: nil)
private var passengerDetails: PassengerDetails? = null
private var bookingComments: String? = ""
private var bookingMetadata: HashMap<String, String>? = null
private var bookingStatus: BookingStatus(pickupLocationInfo, destinationLocationInfo, date)
      
val builder = CheckoutActivity.Builder()
                            .quote(quote)
                            .bookingMetadata(bookingMetadata)
                            .bookingStatus(bookingStatus)

passengerDetails?.let {
  builder.passengerDetails(it)
}

bookingComments?.let {
  builder.comments(it)
}

startActivityForResult(builder.build(this), REQ_CODE_BOOKING_REQUEST_ACTIVITY)

👍

Payment flow

The payment flow is just as you expected it to be, simple and straightforward.

In the Checkout Screen you should first fill in the required fields, the main passenger details and the T&Cs checkbox if needed and then optional fields like comments, flight number or use the loyalty points.

When you are ready to pay, the highlighted button will display the words "CONFIRM AND PAY".
After tapping it, the required information regarding the payment will be required to fill in or just select an already saved card.

492492

Booking ready to be confirmed

🚧

If you use Adyen, hitting the Pay button will now proceed directly to creating the booking.

If you use Braintree, an additional tap on "CONFIRM AND PAY" is needed.

iOS Sample

📘

Payment flow handling

The checkout component requires a URL scheme to be set for the SDK to fully manage 3DSecure payment flows.

Please follow this guide for adding a URL Scheme to successfully handle app switching for the iOS Braintree implementation.

And this guide for adding a URL Scheme to successfully handle app switching for the iOS Adyen implementation.

We've added a summary below for convenience. For more details please consult the aforementioned guides.

---- BraintreeURLTypeContext.swift -----
import Foundation

struct BraintreeURLTypeContext {

    static func currentScheme() -> String {
        guard let bundleId = Bundle.main.bundleIdentifier else {
            return ""
        }

        return "\(bundleId).braintree"
    }
}
import Braintree
import Adyen

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // ...
        BTAppSwitch.setReturnURLScheme(BraintreeURLTypeContext.currentScheme())
        
        return true
    }

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
        if url.scheme?.localizedCaseInsensitiveCompare(BraintreeURLTypeContext.currentScheme()) == .orderedSame {
            return BTAppSwitch.handleOpen(url, options: options)
        }
      
        let adyenThreeDSecureUtils = AdyenThreeDSecureUtils()
        if url.scheme?.localizedCaseInsensitiveCompare(adyenThreeDSecureUtils.current3DSReturnUrlScheme) == .orderedSame {
            return RedirectComponent.applicationDidOpen(from: url)
        }
        
        return false
    }
}

🚧

Please replace the [BUNDLE_ID] mentioned below with your application Bundle ID. If you have a sandbox build type, you need to include add the URL types for sandbox as well.

---- info.plist -----
<key>CFBundleURLTypes</key>
    <array>
    <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string></string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>[BUNDLE_ID]</string>
        </array>
      </dict>
      <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>App-Payments-AppStore</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>[BUNDLE_ID].braintree</string>
        </array>
      </dict>
      <dict>
        <key>CFBundleTypeRole</key>
        <string>Viewer</string>
        <key>CFBundleURLName</key>
        <string>App-Payments-Adyen-AppStore</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>[BUNDLE_ID].adyen</string>
        </array>
        </dict>
    </array>
</key>

Android Sample

When an authenticated user selects a quote, they are then taken to the checkout screen which enables them to change passenger details, enable loyalty earning or burning, see the capabillities of a ride, add or update their payment details and end up by booking the ride.

The Checkout Activity can be launched independently from the Booking Activity. In order to launch it succesfully, a valid Quote is required for the component to work, the rest of the parameters are optional.

quote: QuoteThe activity will take the quote object and use this to prepopulate eta, pricing and vehicle details fields
bookingMetadata: HashMap<String, String>?If an metadata map is passed in the activity, it will be used as part of the Booking API meta data
bookingInfo: BookingInfoBy passing booking status into the Booking activity it will automatically prefill the origin, destination and date of the desired trip.
passengerDetails: PassengerDetailsIf a passenger is added, then it will be used to prefill the passenger details in the booking request component
comments: StringBy passing comments into the Checkout Component it will automatically prefill the comments of the desired trip.
validityDeadlineTimestamp: LongSets the validity timestamp of the quote
When validity of the quote is expired, a popup will be shown to the user to notify him

The Checkout Activity can be launched similar to the Booking Activity, containing a builder which can handle the above params

// launching for primary flow
val builder = CheckoutActivity.Builder()
                            .quote(actions.quote)
                            .bookingMetadata(bookingMetadata)
                            .passengerDetails(passengerDetails)
                            .comments(bookingComments)
                            .bookingInfo(BookingInfo(pickup, destination, date))
                            .currentValidityDeadlineTimestamp(ts)

// launching for callback example
startActivityForResult(builder.build(this), REQ_CODE_BOOKING_REQUEST_ACTIVITY)

When an error occurs in the Checkout Component, the activity will be finished with a result containing an error code or string
In order to catch the error result, override onActivityResult in the calling activity and listen for BOOKING_CHECKOUT_ERROR result code

/**
     * Method used for finishing up the booking request activity with an error
     * The activity which launches the BookingRequestActivity should handle the error result
     * under BOOKING_REQUEST_ERROR (result code 10) and act accordingly
     * @param error reason for closing the BookingRequestActivity
     */
    private fun finishWithError(error: String) {
        val data = Intent()
        data.putExtra(BOOKING_CHECKOUT_ERROR_KEY, error)
        setResult(BOOKING_CHECKOUT_ERROR, data)
        finish()
    }

When the Checkout Component successfully books a trip, the resulting tripId will be available on the returning's intent extras of CheckoutActivity.BOOKING_CHECKOUT_PREBOOK_TRIP_INFO_KEY

if (resultCode == Activity.RESULT_OK && requestCode == REQ_CODE_BOOKING_REQUEST_ACTIVITY) {
            if (data?.hasExtra(CheckoutActivity.BOOKING_CHECKOUT_PREBOOK_TRIP_INFO_KEY) == true) {
                //In case of prebook,the trip is instantly booked. Show a confirmation
                showPrebookConfirmationDialog(data)
            } else {
                //Send the data to the trip allocation widget
          tripAllocationWidget.onActivityResult(requestCode, resultCode, data)
            }
        }