Pay Kit iOS: Advanced Operations

Analytics

If you want to monitor your integration to ensure customers are appropritately transitioning through the Cash App Pay funnel then you can track a metric in each of the CashAppPayState’s and build a funnel using the Customer Request ID.

(iOS only) Pay Kit mobile redirect limitations

iOS has a limitation that causes an interstitial page to show, requiring an extra user interaction. This happens if the time taken between the user clicking the Cash App Pay button and Pay Kit attempting to deep link exceeds 1 second.

Issue Description

Universal linking on iOS has a limitation based on an “interaction threshold”. A customer input (for example, touch, click) is required for a universal link to open an app. If the linking is done programmatically, by updating location.href for example, there is a limit of 1 second between the interaction and the actual redirection for a universal link to properly deep link into an app. If the limit is exceeded, the universal link is opened as a normal webpage instead.

When Pay Kit is managing the Cash App Pay button, the 1 second threshold will never be exceeded. However, when using the manage option to control Pay Kit manually, you must take care that the 1 second threshold is not hit.

Examples for how this may happen are: adding additional API calls after the Customer has interacted with the button but before Pay Kit is called, or running other types of form validation. Any such work should be done prior to the customer interacting with the button to avoid any extra work that might exceed the 1 second threshold which will cause the interstitial to show.

Objective-C Examples and States

General Information

SwiftObjective-C
CashAppPayObserverCAPCashAppPayObserver
CashAppPay.RedirectNotification[CAPCashAppPay RedirectNotification]
.production / .sandboxCAPEndpointProduction / CAPEndpointSandbox

Step 1

Step 1 Implement the Cash App Pay Observer Protocol

The CashAppPayObserver protocol contains only one method:

Swift code:

1func stateDidChange(to state: CashAppPayState) {
2 // handle state changes
3}

The CAPCashAppPayObserver protocol contains only one method:

Objective-C code:

1- (void)stateDidChangeTo:(CAPCashAppPayState *)state {
2 // handle state changes
3}

States

You must update your UI in response to these state changes.

SwiftObjective-C
readyToAuthorizeCAPCashAppPayStateReadyToAuthorize
approvedCAPCashAppPayStateApproved
declinedCAPCashAppPayStateDeclined

Terminal states

SwiftObjective-C
approvedCAPCashAppPayStateApproved
declinedCAPCashAppPayStateDeclined

Error States

SwiftObjective-C
integrationErrorCAPCashAppPayStateIntegrationError
apiErrorCAPCashAppPayStateApiError
unexpectedErrorCAPCashAppPayStateUnexpectedError
networkErrorCAPCashAppPayStateNetworkError

Informational states

SwiftObjective-C
notStartedCAPCashAppPayStateNotStarted
creatingCustomerRequestCAPCashAppPayStateCreatingCustomerRequest
updatingCustomerRequestCAPCashAppPayStateUpdatingCustomerRequest
redirectingCAPCashAppPayRedirecting
pollingCAPCashAppPayStatePolling
refreshingCAPCashAppPayStateRefreshing
redirectingCAPCashAppPayStateRedirecting

Step 2

Step 2 Implement URL Handling

When your app is called back by Cash App, post the CashAppPay.RedirectNotification from your AppDelegate or SceneDelegate, and the SDK will handle the rest:

Swift code:

1import UIKit
2import PayKit
3
4class SceneDelegate: UIResponder, UIWindowSceneDelegate {
5 func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
6 if let url = URLContexts.first?.url {
7 NotificationCenter.default.post(
8 name: CashAppPay.RedirectNotification,
9 object: nil,
10 userInfo: [UIApplication.LaunchOptionsKey.url : url]
11 )
12 }
13 }
14}

When your app is called back by Cash App, post the [CashAppPay.RedirectNotification] from your AppDelegate or SceneDelegate, and the SDK will handle the rest:

Objective-C code:

1@import PayKit;
2
3- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
4 if ([URLContexts count] > 0) {
5 NSURL *url = ((UIOpenURLContext*)[[URLContexts allObjects] firstObject]).URL;
6 [[NSNotificationCenter defaultCenter]
7 postNotificationName:[CAPCashAppPay RedirectNotification]
8 object:NULL
9 userInfo:@{UIApplicationLaunchOptionsURLKey: url}
10 ];
11 }
12}
Do Not Skip This Step!
This step is vital to ensuring the inetgration works correctly! You may find that in testing environments this step is not required however in production environments you will see a high number of customers not being able to complete their checkout because the SDK never enters the polling state.

Step 3

Step 3 Instantiate Pay Kit iOS

For example, from your checkout view controller that implements the CashAppPayObserver protocol, you might instantiate the SDK to be:

Swift code:

1private let sandboxClientID = "YOUR_CLIENT_ID"
2private lazy var sdk: CashAppPay = {
3 let sdk = CashAppPay(clientID: sandboxClientID, endpoint: .sandbox)
4 sdk.addObserver(self)
5 return sdk
6}()

Make Sure You Retain The SDK!
You must strongly retain the SDK otherwise you will never recieve state changes.
For example, from your checkout view controller that implements the CashAppPayObserver protocol, you might instantiate the SDK to be:

Objective-C code:

1In the .h file:
2@property (nonatomic, strong) CAPCashAppPay *sdk;
3
4In the .m file:
5NSString *sandboxClientID = @"YOUR_CLIENT_ID";
6self.sdk = [[CAPCashAppPay alloc]initWithClientID:sandboxClientID endpoint:CAPEndpointSandbox];
7[_sdk addObserver:self];

Step 4

Step 4 Create a Customer Request

To charge $5.00, your createCustomerRequest call might look like this:

Swift code:

1private let sandboxBrandID = "YOUR_BRAND_ID"
2
3override func viewDidLoad() {
4 super.viewDidLoad()
5 // load view hierarchy
6 sdk.createCustomerRequest(
7 params: CreateCustomerRequestParams(
8 actions: [
9 .oneTimePayment(
10 scopeID: brandID,
11 money: Money(amount: 500, currency: .USD)
12 )
13 ],
14 channel: .IN_APP,
15 redirectURL: URL(string: "tipmycap://callback")!,
16 referenceID: nil,
17 metadata: nil
18 )
19 )
20}

To charge $5.00, your createCustomerRequestWithParams call might look like this:

Objective-C code:

1- (void)viewDidLoad {
2 [super viewDidLoad];
3 // load view hierarchy
4 NSString *sandboxBrandID = @"YOUR_BAND_ID";
5 CAPMoney *oneDollar = [[CAPMoney alloc] initWithAmount:500 currency:CAPCurrencyUSD];
6 [_sdk createCustomerRequestWithParams:[
7 [CAPCreateCustomerRequestParams alloc]
8 initWithActions:@[[CAPPaymentAction oneTimePaymentWithScopeID:sandboxBrandID money:oneDollar]]
9 redirectURL:[[NSURL alloc]initWithString:@"paykitdemo://callback"]
10 referenceID:NULL
11 metadata:NULL
12 ]
13 ];
14}

Step 5

Step 5 Authorize the Customer Request

When the customer taps the button, you can authorize the customer request.

Example

Swift code:

1@objc func cashAppPayButtonTapped() {
2 sdk.authorizeCustomerRequest(request)
3}

When the customer taps the button, you can authorize the customer request.

Example

Objective-C code:

1- (IBAction)cashAppPayButtonTapped:(id)sender {
2 [_sdk authorizeCustomerRequest:request]
3}

Unhappy Path

If the Customer does not have Cash App installed on their device then they will redirect to a webpage prompting them to download Cash App. In the event the customer does not download Cash App, then the SDK will remain in the polling state. The SDK does not handle this edge case and instead it is up to the implementor to set a reasonable timeout and treat the checkout as failed once that timeout is exceeded. It is suggested to dismiss any loading states and restart the Cash App Pay flow as to not block the customer from checking out.