# Consolidated Checkout API # 1/14/21 Config Meeting ``` Checkout { onAuthenticate: () -> () = { } onAuthStepUp: () -> () = { } OnShippingChange? = nil (pending implementation?) OnLoggedOut? = nil CreateOrder? = nil, OnApprove? = nil, // automatically make capture/authorize OnCancel? = nil, OnError? = nil, function set(CheckoutConfig) function start( CreateOrder? = nil, OnApprove? = nil, // automatically make capture/authorize OnCancel? = nil, OnError? = nil, ) // Future state, post-MVP. Billing Agreements are likely separate experiences than one-off purchases. function start( CreateBillingAgreement, OnApprove? = nil, OnCancel? = nil, OnError? = nil ) function start( CreateSubscription, OnApprove? = nil, OnCancel? = nil, OnError? = nil ) } ``` ## Consolidated Checkout API (Future State) ### Checkout Config ``` CheckoutConfig { String clientId Environment environment // iOS: rename from `payMode` to `userAction` // Android: would replace `merchantURLQueryParams` UserAction? userAction returnUrl: String // com.paypal.checkoutsamples://paypalpay // uriScheme // universalLink SettingsConfig( EnableLogging ShouldFailEligibility // Android ChecksEligibility // iOS ) } enum UserAction { Commit Continue } ``` ### Checkout -> CheckoutSDK ``` Checkout { function set(CheckoutConfig) // Android - when mapping to DebugConfigManager, ensure that // "://paypalpay" is stripped off before setting `merchantRedirectUrl` // possible rename, maybe launch paysheet, maybe something else function start( CreateOrder, // for GA OnApprove? = nil, // automatically make capture/authorize - for GA OnCancel? = nil, // for GA OnError? = nil, // for GA OnShippingChange? = nil, // pending implementation OnLoggedOut? = nil // pending implementation ) } ``` ### Checkout Flow ``` Application { function onStart { Checkout.set(CheckoutConfig) } } CheckoutScreen { function payWithPayPal { Checkout.start( createOrder { createOrderActions -> // Server-side Call by Merchant createOrderActions.setOrderId(orderId) // or // Client-side Call actions.create(order) { orderId -> // Merchant can log orderId } // Success callback is optional actions.create(order) } ) } } CreateOrderActions { function create(orderId: String) function create(order: Order, successCallback: ((String) -> Unit)?) } ``` ### OnApprove iOS ```swift class ApprovalData { let payerID: String let ecToken: String let intent: String let returnURL: URL? } ``` Android ```kotlin data class Approval( val data: ApprovalData, val orderActions: OrderActions ) data class ApprovalData( val payerId: String, val orderId: String, val paymentId: String? = null ) ``` #### Questions 1. Should we return the above fields? Are there additional fields we should return? 2. Return enums instead of strings for certain fields? (intent, userAction) ### OnError ```kotlin= class ErrorInfo( // The actual error. val error: Error (iOS) / Throwable (Android), // The error reason. val reason: String, // Correlation IDs associated with debugging issues. val correlationIds: CorrelationIDs, // Pay token/EC-Token/OrderID val payToken: String, // SDK version val nativeSdkVersion: String ) class CorrelationIDs( // Our correlation id associated when checking eligibility. // GraphQL Query: mobileSDKEligibility val eligibilityDebugID: String?, // Our correlation id associated when checking eligibility for payment buttons. // GraphQL Query: TBD val fundingEligibilityDebugID: String?, // Our correlation id associated when updating client config. // GraphQL Query: updateClientConfig val updateClientConfigDebugID: String?, // Our correlation id associated when upgrading LSAT. // GraphQL Query: upgradeLowScopeAccessToken val lsatUpgradeDebugID: String?, // Our correlation id associated when fetching user info. // GraphQL Query: checkoutSession val fetchPayloadDebugID: String?, // Our correlation id associated when converting currency info. // GraphQL Query: updateCurrencyConversionType val currencyConversionDebugID: String?, // Our correlation id associated when finishing the checkout flow. // GraphQL Query: approvePayment val finishCheckoutDebugID: String? ) ``` ## Future Notes 1. We may want to consider offering debug hooks into the SDK's. Currently we allow failing eligibility on both platforms but it's handled a little differently. Might be nice to stremline this. ``` // Android within CheckoutConfig shouldFailEligibilityCall: Boolean // iOS within EnvironmentConfig.swift EnvironmentConfig( public var checksEligibility: Bool = true ) ``` 2. Sync up on CheckoutConfig as source of truth over DebugConfigManager (mostly Android). 3. To investigate: can we create a billing agreement from the orders api 4. EC Token -> Renamed to Order ID --- ## Working Config ``` CheckoutConfig { String clientId // Android: migrate from "RunTimeEnvironment" to "Environment" Environment environment // iOS: remove `payToken` from CheckoutConfig initializer // iOS: rename from `payMode` to `userAction` // Android: would replace `merchantURLQueryParams` PayMode? userAction // Talk about url schemes & universal link iOS -> URIScheme (Redirect URL) & Unverisal Link used for Analtyics Android -> merchantUrlScheme & merchantRedirectUrl // Return URL -> Used for authentication & fallback with Android. returnUrl: String // com.paypal.checkoutsamples://paypalpay } ``` --- # Working Notes ## iOS Signatures (Current State) ## Callback Signatures: ```swift public typealias AuthenticationCallback = (@escaping (AuthToken?, Error?) -> Void) -> Void public typealias AuthStepUpCallback = (URI?, (@escaping (RedirectURI?, Error?) -> Void)) -> Void public typealias ApprovalCallback = (Approval) -> Void public typealias CancelCallback = () -> Void public typealias ErrorCallback = (ErrorInfo) -> Void public typealias LogoutCallback = () -> Void public typealias ShippingChangeCallback = (ShippingChange, (@escaping (ShippingChangeState, ShippingChangeError?) -> Void)) -> Void ``` ### Checkout Config ```swift CheckoutConfig { public var clientID: String public var payToken: String? public var payMode: PayMode? public var environment: Environment public var environmentConfig = EnvironmentConfig() public var uriScheme: String public var universalLink: String public var onAuthenticate: AuthenticationCallback? public var onAuthStepUp: AuthStepUpCallback? public var onApprove: ApprovalCallback? public var onCancel: CancelCallback? public var onError: ErrorCallback? public var onLogout: LogoutCallback? public var onShippingChange: ShippingChangeCallback? public var presentingViewController: UIViewController? public init( clientID: String, payToken: String, universalLink: String, uriScheme: String, onApprove: @escaping CheckoutConfig.ApprovalCallback, onCancel: @escaping CheckoutConfig.CancelCallback, onError: @escaping CheckoutConfig.ErrorCallback, environment: Environment = .live ) } ``` ### Checkout Definition ```swift // Checkout.swift @objc(PPCheckout) @objcMembers public final class Checkout: NSObject, StaticIdentifiable { @objc(setConfig:) public static func set(config: CheckoutConfig) { ... } @objc(start:) public static func start(experience: Experience = .native) { ... } } ``` ### Checkout Flow ```swift let config = CheckoutConfig( ... ) Checkout.set(config: config) // Must be called before `start` Checkout.start(experience: Experience = .native) ``` ## Android Signatures (Current State) ```kotlin // Currently implemented in Java CheckoutConfig { val runTimeEnvironment: RunTimeEnvironment (maps to CheckoutEnvironment) val environment: CheckoutEnvironment val clientId: String val merchantUrlScheme: String val merchantRedirectUrl: String val merchantQueryParams: String[] val payPalCheckoutCompleteListener: PayPalCheckoutCompleteListener val shippingCallbacks: ShippingCallbacks val isDebug: Boolean val shouldFailEligibilityCall: Boolean } ``` ```kotlin PayPalCheckoutSdk { fun startCheckoutWithToken( context: Context, token: String, checkoutConfig: CheckoutConfig ) fun startCheckoutWithOrders( context: Context, order: Order, orderCallbacks: OrderCallbacks, checkoutConfig: CheckoutConfig ) fun startCheckoutWithIntentUri( context: Context, originUri: Uri, checkoutConfig: CheckoutConfig ) } ``` ```kotlin CheckoutEnvironemt { // Handles URLs for LIVE, SANDBOX, and STAGING } PayPalCheckoutCompleteListener { fun onCheckoutComplete(params: HashMap<String, String>) fun onCheckoutCancelled(cancelReason: CheckoutCancelReason, reason: String) } ``` ```kotlin ShippingCallbacks { fun onShippingChange(data: ShippingData, listener: ShippingCallbackListener) } ``` ```kotlin ShippingCallbackListener { fun onSuccess(refreshData: Boolean) fun onSuccess(refreshData: Boolean, upgradedAccessToken: String, purchaseUnit: List<PurchaseUnit>) fun onFailure(reason: String, shippingCallbackRequestType: ShippingCallbackRequestType) } ``` ### Checkout Flow Option 1 ```kotlin val checkoutConfig = CheckoutConfig.getInstance().apply { /* set configuration values */ } PayPalCheckoutSdk.getInstance().startCheckoutWithToken(this@MainActivity, "EC-TOKEN", checkoutConfig) ``` ### Checkout Flow Option 2 ```kotlin val checkoutConfig = CheckoutConfig.getInstance().apply { /* set configuration values */ } PayPalCheckoutSdk.getInstance().initialize(checkoutConfig) PayPalCheckoutSdk.getInstance().startCheckoutWithToken(this@MainActivity, "EC-TOKEN") ```