| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152 |
- #import "BTPayPalDriver_Internal.h"
- #if __has_include("PayPalOneTouch.h")
- #import "PPOTRequest.h"
- #import "PPOTCore.h"
- #else
- #import <PayPalOneTouch/PPOTRequest.h>
- #import <PayPalOneTouch/PPOTCore.h>
- #endif
- #if __has_include("BraintreeCore.h")
- #import "BTAPIClient_Internal.h"
- #import "BTPayPalAccountNonce_Internal.h"
- #import "BTPostalAddress.h"
- #import "BTLogger_Internal.h"
- #else
- #import <BraintreeCore/BTAPIClient_Internal.h>
- #import <BraintreeCore/BTPostalAddress.h>
- #import <BraintreeCore/BTLogger_Internal.h>
- #endif
- #if __has_include("BTPayPalAccountNonce_Internal.h")
- #import "BTPayPalAccountNonce_Internal.h"
- #else
- #import <BraintreePayPal/BTPayPalAccountNonce_Internal.h>
- #endif
- #import <SafariServices/SafariServices.h>
- #import "BTConfiguration+PayPal.h"
- #import "BTPayPalLineItem.h"
- NSString *const BTPayPalDriverErrorDomain = @"com.braintreepayments.BTPayPalDriverErrorDomain";
- NSString *const BTSFAuthenticationSessionDisabled = @"sfAuthenticationSessionDisabled";
- static void (^appSwitchReturnBlock)(NSURL *url);
- typedef NS_ENUM(NSUInteger, BTPayPalPaymentType) {
- BTPayPalPaymentTypeUnknown = 0,
- BTPayPalPaymentTypeFuturePayments,
- BTPayPalPaymentTypeCheckout,
- BTPayPalPaymentTypeBillingAgreement,
- };
- @interface BTPayPalDriver () <SFSafariViewControllerDelegate, UIViewControllerTransitioningDelegate>
- @property (nonatomic, assign) BOOL becameActiveAfterSFAuthenticationSessionModal;
- @end
- @implementation BTPayPalDriver
- + (void)load {
- if (self == [BTPayPalDriver class]) {
- PayPalClass = [PPOTCore class];
-
- [[BTAppSwitch sharedInstance] registerAppSwitchHandler:self];
-
- [[BTTokenizationService sharedService] registerType:@"PayPal" withTokenizationBlock:^(BTAPIClient *apiClient, __unused NSDictionary *options, void (^completionBlock)(BTPaymentMethodNonce *paymentMethodNonce, NSError *error)) {
- BTPayPalDriver *driver = [[BTPayPalDriver alloc] initWithAPIClient:apiClient];
- driver.viewControllerPresentingDelegate = options[BTTokenizationServiceViewPresentingDelegateOption];
- driver.appSwitchDelegate = options[BTTokenizationServiceAppSwitchDelegateOption];
- [driver authorizeAccountWithAdditionalScopes:options[BTTokenizationServicePayPalScopesOption] completion:completionBlock];
- }];
-
- [[BTPaymentMethodNonceParser sharedParser] registerType:@"PayPalAccount" withParsingBlock:^BTPaymentMethodNonce * _Nullable(BTJSON * _Nonnull payPalAccount) {
- return [self payPalAccountFromJSON:payPalAccount];
- }];
- }
- }
- - (instancetype)initWithAPIClient:(BTAPIClient *)apiClient {
- if (self = [super init]) {
- BTClientMetadataSourceType source = [self isiOSAppAvailableForAppSwitch] ? BTClientMetadataSourcePayPalApp : BTClientMetadataSourcePayPalBrowser;
- _apiClient = [apiClient copyWithSource:source integration:apiClient.metadata.integration];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(applicationDidBecomeActive:)
- name:UIApplicationDidBecomeActiveNotification
- object:nil];
- }
- return self;
- }
- - (instancetype)init {
- return nil;
- }
- - (void)applicationDidBecomeActive:(__unused NSNotification *)notification
- {
- if (self.isSFAuthenticationSessionStarted) {
- self.becameActiveAfterSFAuthenticationSessionModal = YES;
- }
- }
- - (void)dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- #pragma mark - Authorization (Future Payments)
- - (void)authorizeAccountWithCompletion:(void (^)(BTPayPalAccountNonce *paymentMethod, NSError *error))completionBlock {
- [self authorizeAccountWithAdditionalScopes:[NSSet set] completion:completionBlock];
- }
- - (void)authorizeAccountWithAdditionalScopes:(NSSet<NSString *> *)additionalScopes completion:(void (^)(BTPayPalAccountNonce *, NSError *))completionBlock {
- [self authorizeAccountWithAdditionalScopes:additionalScopes
- forceFuturePaymentFlow:false
- completion:completionBlock];
- }
- - (void)authorizeAccountWithAdditionalScopes:(NSSet<NSString *> *)additionalScopes
- forceFuturePaymentFlow:(BOOL)forceFuturePaymentFlow
- completion:(void (^)(BTPayPalAccountNonce *, NSError *))completionBlock {
- if (!self.apiClient) {
- NSError *error = [NSError errorWithDomain:BTPayPalDriverErrorDomain
- code:BTPayPalDriverErrorTypeIntegration
- userInfo:@{NSLocalizedDescriptionKey: @"BTPayPalDriver failed because BTAPIClient is nil."}];
- completionBlock(nil, error);
- return;
- }
-
- [self setAuthorizationAppSwitchReturnBlock:completionBlock];
-
- [self.apiClient fetchOrReturnRemoteConfiguration:^(BTConfiguration *configuration, NSError *error) {
- if (error) {
- if (completionBlock) {
- completionBlock(nil, error);
- }
- return;
- }
- self.disableSFAuthenticationSession = [configuration.json[BTSFAuthenticationSessionDisabled] isTrue] || self.disableSFAuthenticationSession;
- if (configuration.isBillingAgreementsEnabled && !forceFuturePaymentFlow) {
- // Switch to Billing Agreements flow
- BTPayPalRequest *payPalRequest = [[BTPayPalRequest alloc] init]; // Drop-in only supports Vault flow, which does not use currency code or amount
- [self requestBillingAgreement:payPalRequest completion:completionBlock];
- return;
- }
-
- if (![self verifyAppSwitchWithRemoteConfiguration:configuration.json error:&error]) {
- if (completionBlock) {
- completionBlock(nil, error);
- }
- return;
- }
-
- PPOTAuthorizationRequest *request =
- [self.requestFactory requestWithScopeValues:[self.defaultOAuth2Scopes setByAddingObjectsFromSet:(additionalScopes ? additionalScopes : [NSSet set])]
- privacyURL:[configuration.json[@"paypal"][@"privacyUrl"] asURL]
- agreementURL:[configuration.json[@"paypal"][@"userAgreementUrl"] asURL]
- clientID:[self paypalClientIdWithRemoteConfiguration:configuration.json]
- environment:[self payPalEnvironmentForRemoteConfiguration:configuration.json]
- callbackURLScheme:self.returnURLScheme];
-
- if (self.apiClient.clientToken) {
- request.additionalPayloadAttributes = @{ @"client_token": self.apiClient.clientToken.originalValue };
- } else if (self.apiClient.tokenizationKey) {
- request.additionalPayloadAttributes = @{ @"client_key": self.apiClient.tokenizationKey };
- }
- if (@available(iOS 9.0, *)) {
- // will use in-app browser
- } else {
- [self informDelegateWillPerformAppSwitch];
- }
- [request performWithAdapterBlock:^(BOOL success, NSURL *url, PPOTRequestTarget target, NSString *clientMetadataId, NSError *error) {
- self.clientMetadataId = clientMetadataId;
-
- [self sendAnalyticsEventForInitiatingOneTouchForPaymentType:BTPayPalPaymentTypeFuturePayments
- withSuccess:success
- target:target];
- [self handlePayPalRequestWithSuccess:success
- error:error
- requestURL:url
- target:target
- paymentType:BTPayPalPaymentTypeFuturePayments
- completion:completionBlock];
- }];
- }];
- }
- - (void)setAuthorizationAppSwitchReturnBlock:(void (^)(BTPayPalAccountNonce *account, NSError *error))completionBlock {
- [self setAppSwitchReturnBlock:completionBlock forPaymentType:BTPayPalPaymentTypeFuturePayments];
- }
- #pragma mark - Billing Agreement
- - (void)requestBillingAgreement:(BTPayPalRequest *)request completion:(void (^)(BTPayPalAccountNonce *tokenizedCheckout, NSError *error))completionBlock {
- [self requestExpressCheckout:request
- isBillingAgreement:YES
- handler:nil
- completion:completionBlock];
- }
- - (void)requestBillingAgreement:(BTPayPalRequest *)request
- handler:(id<BTPayPalApprovalHandler>)handler
- completion:(void (^)(BTPayPalAccountNonce * _Nullable, NSError * _Nullable))completionBlock {
- [self requestExpressCheckout:request
- isBillingAgreement:YES
- handler:handler
- completion:completionBlock];
- }
- - (void)setBillingAgreementAppSwitchReturnBlock:(void (^)(BTPayPalAccountNonce *tokenizedAccount, NSError *error))completionBlock {
- [self setAppSwitchReturnBlock:completionBlock forPaymentType:BTPayPalPaymentTypeBillingAgreement];
- }
- #pragma mark - Express Checkout (One-Time Payments)
- - (void)requestOneTimePayment:(BTPayPalRequest *)request completion:(void (^)(BTPayPalAccountNonce *tokenizedCheckout, NSError *error))completionBlock {
- [self requestExpressCheckout:request
- isBillingAgreement:NO
- handler:nil
- completion:completionBlock];
- }
- - (void)requestOneTimePayment:(BTPayPalRequest *)request
- handler:(id<BTPayPalApprovalHandler>)handler
- completion:(void (^)(BTPayPalAccountNonce *tokenizedCheckout, NSError *error))completionBlock {
- [self requestExpressCheckout:request
- isBillingAgreement:NO
- handler:handler
- completion:completionBlock];
- }
- - (void)setOneTimePaymentAppSwitchReturnBlock:(void (^)(BTPayPalAccountNonce *tokenizedAccount, NSError *error))completionBlock {
- [self setAppSwitchReturnBlock:completionBlock forPaymentType:BTPayPalPaymentTypeCheckout];
- }
- #pragma mark - Helpers
- /// A "Hermes checkout" is used by both Billing Agreements and Express Checkout
- - (void)requestExpressCheckout:(BTPayPalRequest *)request
- isBillingAgreement:(BOOL)isBillingAgreement
- handler:(id<BTPayPalApprovalHandler>)handler
- completion:(void (^)(BTPayPalAccountNonce *tokenizedCheckout, NSError *error))completionBlock {
- if (!self.apiClient) {
- NSError *error = [NSError errorWithDomain:BTPayPalDriverErrorDomain
- code:BTPayPalDriverErrorTypeIntegration
- userInfo:@{NSLocalizedDescriptionKey: @"BTPayPalDriver failed because BTAPIClient is nil."}];
- completionBlock(nil, error);
- return;
- }
- if (!request || (!isBillingAgreement && !request.amount)) {
- completionBlock(nil, [NSError errorWithDomain:BTPayPalDriverErrorDomain code:BTPayPalDriverErrorTypeInvalidRequest userInfo:nil]);
- return;
- }
- [self.apiClient fetchOrReturnRemoteConfiguration:^(BTConfiguration *configuration, NSError *error) {
- if (error) {
- if (completionBlock) completionBlock(nil, error);
- return;
- }
- if (![self verifyAppSwitchWithRemoteConfiguration:configuration.json error:&error]) {
- if (completionBlock) completionBlock(nil, error);
- return;
- }
- self.disableSFAuthenticationSession = [configuration.json[BTSFAuthenticationSessionDisabled] isTrue] || self.disableSFAuthenticationSession;
- NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
- NSMutableDictionary *experienceProfile = [NSMutableDictionary dictionary];
- if (!isBillingAgreement) {
- parameters[@"intent"] = [self.class intentTypeToString:request.intent];
- if (request.amount != nil) {
- parameters[@"amount"] = request.amount;
- }
- } else {
- if (request.billingAgreementDescription.length > 0) {
- parameters[@"description"] = request.billingAgreementDescription;
- }
- }
- parameters[@"offer_paypal_credit"] = @(request.offerCredit);
- experienceProfile[@"no_shipping"] = @(!request.isShippingAddressRequired);
- experienceProfile[@"brand_name"] = request.displayName ?: [configuration.json[@"paypal"][@"displayName"] asString];
- NSString *landingPageTypeValue = [self.class landingPageTypeToString:request.landingPageType];
- if (landingPageTypeValue != nil) {
- experienceProfile[@"landing_page_type"] = landingPageTypeValue;
- }
- if (request.localeCode != nil) {
- experienceProfile[@"locale_code"] = request.localeCode;
- }
- if (request.merchantAccountId != nil) {
- parameters[@"merchant_account_id"] = request.merchantAccountId;
- }
- // Currency code should only be used for Hermes Checkout (one-time payment).
- // For BA, currency should not be used.
- NSString *currencyCode = request.currencyCode ?: [configuration.json[@"paypal"][@"currencyIsoCode"] asString];
- if (!isBillingAgreement && currencyCode) {
- parameters[@"currency_iso_code"] = currencyCode;
- }
- if (request.shippingAddressOverride != nil) {
- experienceProfile[@"address_override"] = @(!request.isShippingAddressEditable);
- BTPostalAddress *shippingAddress = request.shippingAddressOverride;
- if (isBillingAgreement) {
- NSMutableDictionary *shippingAddressParams = [NSMutableDictionary dictionary];
- shippingAddressParams[@"line1"] = shippingAddress.streetAddress;
- shippingAddressParams[@"line2"] = shippingAddress.extendedAddress;
- shippingAddressParams[@"city"] = shippingAddress.locality;
- shippingAddressParams[@"state"] = shippingAddress.region;
- shippingAddressParams[@"postal_code"] = shippingAddress.postalCode;
- shippingAddressParams[@"country_code"] = shippingAddress.countryCodeAlpha2;
- shippingAddressParams[@"recipient_name"] = shippingAddress.recipientName;
- parameters[@"shipping_address"] = shippingAddressParams;
- } else {
- parameters[@"line1"] = shippingAddress.streetAddress;
- parameters[@"line2"] = shippingAddress.extendedAddress;
- parameters[@"city"] = shippingAddress.locality;
- parameters[@"state"] = shippingAddress.region;
- parameters[@"postal_code"] = shippingAddress.postalCode;
- parameters[@"country_code"] = shippingAddress.countryCodeAlpha2;
- parameters[@"recipient_name"] = shippingAddress.recipientName;
- }
- } else {
- experienceProfile[@"address_override"] = @NO;
- }
- if (request.lineItems.count > 0) {
- NSMutableArray *lineItemsArray = [NSMutableArray arrayWithCapacity:request.lineItems.count];
- for (BTPayPalLineItem *lineItem in request.lineItems) {
- [lineItemsArray addObject:[lineItem requestParameters]];
- }
- parameters[@"line_items"] = lineItemsArray;
- }
- NSString *returnURI;
- NSString *cancelURI;
- [[self.class payPalClass] redirectURLsForCallbackURLScheme:self.returnURLScheme
- withReturnURL:&returnURI
- withCancelURL:&cancelURI];
- if (!returnURI || !cancelURI) {
- completionBlock(nil, [NSError errorWithDomain:BTPayPalDriverErrorDomain
- code:BTPayPalDriverErrorTypeIntegrationReturnURLScheme
- userInfo:@{NSLocalizedFailureReasonErrorKey: @"Application may not support One Touch callback URL scheme.",
- NSLocalizedRecoverySuggestionErrorKey: @"Check the return URL scheme" }]);
- return;
- }
- if (returnURI) {
- parameters[@"return_url"] = returnURI;
- }
- if (cancelURI) {
- parameters[@"cancel_url"] = cancelURI;
- }
- parameters[@"experience_profile"] = experienceProfile;
- self.payPalRequest = request;
- NSString *url = isBillingAgreement ? @"setup_billing_agreement" : @"create_payment_resource";
- [self.apiClient POST:[NSString stringWithFormat:@"v1/paypal_hermes/%@",url]
- parameters:parameters
- completion:^(BTJSON *body, __unused NSHTTPURLResponse *response, NSError *error) {
- if (error) {
- NSString *errorDetailsIssue = ((BTJSON *)error.userInfo[BTHTTPJSONResponseBodyKey][@"paymentResource"][@"errorDetails"][0][@"issue"]).asString;
- if (error.userInfo[NSLocalizedDescriptionKey] == nil && errorDetailsIssue != nil) {
- NSMutableDictionary *dictionary = [error.userInfo mutableCopy];
- dictionary[NSLocalizedDescriptionKey] = errorDetailsIssue;
- error = [NSError errorWithDomain:error.domain code:error.code userInfo:dictionary];
- }
- if (completionBlock) {
- completionBlock(nil, error);
- }
- return;
- }
- if (isBillingAgreement) {
- [self setBillingAgreementAppSwitchReturnBlock:completionBlock];
- } else {
- [self setOneTimePaymentAppSwitchReturnBlock:completionBlock];
- }
- NSString *payPalClientID = [configuration.json[@"paypal"][@"clientId"] asString];
- if (!payPalClientID && [self payPalEnvironmentForRemoteConfiguration:configuration.json] == PayPalEnvironmentMock) {
- payPalClientID = @"FAKE-PAYPAL-CLIENT-ID";
- }
- NSURL *approvalUrl = [body[@"paymentResource"][@"redirectUrl"] asURL];
- if (approvalUrl == nil) {
- approvalUrl = [body[@"agreementSetup"][@"approvalUrl"] asURL];
- }
- approvalUrl = [self decorateApprovalURL:approvalUrl forRequest:request];
- PPOTCheckoutRequest *request = nil;
- if (isBillingAgreement) {
- request = [self.requestFactory billingAgreementRequestWithApprovalURL:approvalUrl
- clientID:payPalClientID
- environment:[self payPalEnvironmentForRemoteConfiguration:configuration.json]
- callbackURLScheme:self.returnURLScheme];
- } else {
- request = [self.requestFactory checkoutRequestWithApprovalURL:approvalUrl
- clientID:payPalClientID
- environment:[self payPalEnvironmentForRemoteConfiguration:configuration.json]
- callbackURLScheme:self.returnURLScheme];
- }
- // Call custom handler and return before beginning the default approval process
- if (handler != nil) {
- [handler handleApproval:request paypalApprovalDelegate:self];
- return;
- }
- if (@available(iOS 9.0, *)) {
- // will use in-app browser
- } else {
- [self informDelegateWillPerformAppSwitch];
- }
- [request performWithAdapterBlock:^(BOOL success, NSURL *url, PPOTRequestTarget target, NSString *clientMetadataId, NSError *error) {
- self.clientMetadataId = clientMetadataId;
-
- if (isBillingAgreement) {
- [self sendAnalyticsEventForInitiatingOneTouchForPaymentType:BTPayPalPaymentTypeBillingAgreement
- withSuccess:success
- target:target];
- } else {
- [self sendAnalyticsEventForInitiatingOneTouchForPaymentType:BTPayPalPaymentTypeCheckout
- withSuccess:success
- target:target];
- }
- [self handlePayPalRequestWithSuccess:success
- error:error
- requestURL:url
- target:target
- paymentType:isBillingAgreement ? BTPayPalPaymentTypeBillingAgreement : BTPayPalPaymentTypeCheckout
- completion:completionBlock];
- }];
- }];
- }];
- }
- - (void)setAppSwitchReturnBlock:(void (^)(BTPayPalAccountNonce *tokenizedAccount, NSError *error))completionBlock
- forPaymentType:(BTPayPalPaymentType)paymentType {
- appSwitchReturnBlock = ^(NSURL *url) {
- [self informDelegateAppContextDidReturn];
- if (@available(iOS 11.0, *)) {
- if (self.safariAuthenticationSession) {
- // do nothing
- } else if (self.safariViewController) {
- [self informDelegatePresentingViewControllerNeedsDismissal];
- } else {
- [self informDelegateWillProcessAppSwitchReturn];
- }
- } else if (@available(iOS 9.0, *)) {
- [self informDelegatePresentingViewControllerNeedsDismissal];
- } else {
- [self informDelegateWillProcessAppSwitchReturn];
- }
-
- // Before parsing the return URL, check whether the user cancelled by breaking
- // out of the PayPal app switch flow (e.g. "Done" button in SFSafariViewController)
- if ([url.absoluteString isEqualToString:SFSafariViewControllerFinishedURL]) {
- if (completionBlock) completionBlock(nil, nil);
- appSwitchReturnBlock = nil;
- return;
- }
-
- [[self.class payPalClass] parseResponseURL:url completionBlock:^(PPOTResult *result) {
- [self sendAnalyticsEventForHandlingOneTouchResult:result forPaymentType:paymentType];
-
- switch (result.type) {
- case PPOTResultTypeError:
- if (completionBlock) completionBlock(nil, result.error);
- break;
- case PPOTResultTypeCancel:
- if (result.error) {
- [[BTLogger sharedLogger] error:@"PayPal error: %@", result.error];
- }
- if (completionBlock) completionBlock(nil, nil);
- break;
- case PPOTResultTypeSuccess: {
- NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
- parameters[@"paypal_account"] = [result.response mutableCopy];
-
- if (paymentType == BTPayPalPaymentTypeCheckout) {
- parameters[@"paypal_account"][@"options"] = @{ @"validate": @NO };
- if (self.payPalRequest) {
- parameters[@"paypal_account"][@"intent"] = [self.class intentTypeToString:self.payPalRequest.intent];
- }
- }
- if (self.clientMetadataId) {
- parameters[@"paypal_account"][@"correlation_id"] = self.clientMetadataId;
- }
- if (self.payPalRequest != nil && self.payPalRequest.merchantAccountId != nil) {
- parameters[@"merchant_account_id"] = self.payPalRequest.merchantAccountId;
- }
-
- BTClientMetadata *metadata = [self clientMetadata];
- parameters[@"_meta"] = @{
- @"source" : metadata.sourceString,
- @"integration" : metadata.integrationString,
- @"sessionId" : metadata.sessionId,
- };
-
- [self.apiClient POST:@"/v1/payment_methods/paypal_accounts"
- parameters:parameters
- completion:^(BTJSON *body, __unused NSHTTPURLResponse *response, NSError *error)
- {
- if (error) {
- [self sendAnalyticsEventForTokenizationFailureForPaymentType:paymentType];
- if (completionBlock) completionBlock(nil, error);
- return;
- }
- [self sendAnalyticsEventForTokenizationSuccessForPaymentType:paymentType];
- BTJSON *payPalAccount = body[@"paypalAccounts"][0];
- BTPayPalAccountNonce *tokenizedAccount = [self.class payPalAccountFromJSON:payPalAccount];
- [self sendAnalyticsEventIfCreditFinancingInNonce:tokenizedAccount forPaymentType:paymentType];
- if (completionBlock) {
- completionBlock(tokenizedAccount, nil);
- }
- }];
-
- break;
- }
- }
- appSwitchReturnBlock = nil;
- }];
- };
- }
- - (void)handlePayPalRequestWithSuccess:(BOOL)success
- error:(NSError *)error
- requestURL:(NSURL *)url
- target:(PPOTRequestTarget)target
- paymentType:(BTPayPalPaymentType)paymentType
- completion:(void (^)(BTPayPalAccountNonce *, NSError *))completionBlock {
- if (success) {
- // Defensive programming in case PayPal One Touch returns a non-HTTP URL so that SFSafariViewController doesn't crash
- if (@available(iOS 9.0, *)) {
- if (![url.scheme.lowercaseString hasPrefix:@"http"]) {
- NSError *urlError = [NSError errorWithDomain:BTPayPalDriverErrorDomain
- code:BTPayPalDriverErrorTypeUnknown
- userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Attempted to open an invalid URL in SFSafariViewController: %@://", url.scheme],
- NSLocalizedRecoverySuggestionErrorKey: @"Try again or contact Braintree Support." }];
- if (completionBlock) {
- completionBlock(nil, urlError);
- }
- NSString *eventName = [NSString stringWithFormat:@"ios.%@.%@.error.safariviewcontrollerbadscheme.%@", [self.class eventStringForPaymentType:paymentType], [self.class eventStringForRequestTarget:target], url.scheme];
- [self.apiClient sendAnalyticsEvent:eventName];
- return;
- }
- }
- [self performSwitchRequest:url];
- if (@available(iOS 9.0, *)) {
- // use in-app browser
- } else {
- [self informDelegateDidPerformAppSwitchToTarget:target];
- }
- } else {
- if (completionBlock) {
- completionBlock(nil, error);
- }
- }
- }
- - (void)performSwitchRequest:(NSURL *)appSwitchURL {
- [self informDelegateAppContextWillSwitch];
- if (@available(iOS 11.0, *)) {
- NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:appSwitchURL resolvingAgainstBaseURL:NO];
- if (self.disableSFAuthenticationSession) {
- // Append "force-one-touch" query param when One Touch functions correctly
- NSString *queryForAuthSession = [urlComponents.query stringByAppendingString:@"&bt_int_type=1"];
- urlComponents.query = queryForAuthSession;
- [self informDelegatePresentingViewControllerRequestPresent:urlComponents.URL];
- } else {
- NSString *queryForAuthSession = [urlComponents.query stringByAppendingString:@"&bt_int_type=2"];
- urlComponents.query = queryForAuthSession;
- self.safariAuthenticationSession = [[SFAuthenticationSession alloc] initWithURL:urlComponents.URL
- callbackURLScheme:self.returnURLScheme
- completionHandler:^(NSURL * _Nullable callbackURL, NSError * _Nullable error)
- {
- if (error) {
- if (error.domain == SFAuthenticationErrorDomain && error.code == SFAuthenticationErrorCanceledLogin) {
- if (self.becameActiveAfterSFAuthenticationSessionModal) {
- [self.apiClient sendAnalyticsEvent:@"ios.sfauthsession.cancel.web"];
- } else {
- [self.apiClient sendAnalyticsEvent:@"ios.sfauthsession.cancel.modal"];
- }
- }
- [self.class handleAppSwitchReturnURL:[NSURL URLWithString:SFSafariViewControllerFinishedURL]];
- return;
- }
- [BTAppSwitch handleOpenURL:callbackURL sourceApplication:@"com.apple.safariviewservice"];
- self.safariAuthenticationSession = nil;
- }];
- if (self.safariAuthenticationSession != nil) {
- self.becameActiveAfterSFAuthenticationSessionModal = NO;
- self.isSFAuthenticationSessionStarted = [self.safariAuthenticationSession start];
- if (self.isSFAuthenticationSessionStarted) {
- [self.apiClient sendAnalyticsEvent:@"ios.sfauthsession.start.succeeded"];
- } else {
- [self.apiClient sendAnalyticsEvent:@"ios.sfauthsession.start.failed"];
- }
- }
- }
- } else if (@available(iOS 9.0, *)) {
- NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:appSwitchURL resolvingAgainstBaseURL:NO];
- NSString *queryForAuthSession = [urlComponents.query stringByAppendingString:@"&bt_int_type=1"];
- urlComponents.query = queryForAuthSession;
- [self informDelegatePresentingViewControllerRequestPresent:urlComponents.URL];
- } else {
- UIApplication *application = [UIApplication sharedApplication];
- if (@available(iOS 10.0, *)) {
- [application openURL:appSwitchURL options:[NSDictionary dictionary] completionHandler:nil];
- } else {
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- [application openURL:appSwitchURL];
- #pragma clang diagnostic pop
- }
- }
- }
- - (NSString *)payPalEnvironmentForRemoteConfiguration:(BTJSON *)configuration {
- NSString *btPayPalEnvironmentName = [configuration[@"paypal"][@"environment"] asString];
- if ([btPayPalEnvironmentName isEqualToString:@"offline"]) {
- return PayPalEnvironmentMock;
- } else if ([btPayPalEnvironmentName isEqualToString:@"live"]) {
- return PayPalEnvironmentProduction;
- } else {
- // Fall back to mock when configuration has an unsupported value for environment, e.g. "custom"
- // Instead of returning btPayPalEnvironmentName
- return PayPalEnvironmentMock;
- }
- }
- - (NSString *)paypalClientIdWithRemoteConfiguration:(BTJSON *)configuration {
- if ([[configuration[@"paypal"][@"environment"] asString] isEqualToString:@"offline"] && ![configuration[@"paypal"][@"clientId"] isString]) {
- return @"mock-paypal-client-id";
- } else {
- return [configuration[@"paypal"][@"clientId"] asString];
- }
- }
- - (BTClientMetadata *)clientMetadata {
- BTMutableClientMetadata *metadata = [self.apiClient.metadata mutableCopy];
-
- if ([self isiOSAppAvailableForAppSwitch]) {
- metadata.source = BTClientMetadataSourcePayPalApp;
- } else {
- metadata.source = BTClientMetadataSourcePayPalBrowser;
- }
-
- return [metadata copy];
- }
- - (NSSet *)defaultOAuth2Scopes {
- return [NSSet setWithObjects:@"https://uri.paypal.com/services/payments/futurepayments", @"email", nil];
- }
- + (BTPostalAddress *)accountAddressFromJSON:(BTJSON *)addressJSON {
- if (!addressJSON.isObject) {
- return nil;
- }
-
- BTPostalAddress *address = [[BTPostalAddress alloc] init];
- address.recipientName = [addressJSON[@"recipientName"] asString]; // Likely to be nil
- address.streetAddress = [addressJSON[@"street1"] asString];
- address.extendedAddress = [addressJSON[@"street2"] asString];
- address.locality = [addressJSON[@"city"] asString];
- address.region = [addressJSON[@"state"] asString];
- address.postalCode = [addressJSON[@"postalCode"] asString];
- address.countryCodeAlpha2 = [addressJSON[@"country"] asString];
-
- return address;
- }
- + (BTPostalAddress *)shippingOrBillingAddressFromJSON:(BTJSON *)addressJSON {
- if (!addressJSON.isObject) {
- return nil;
- }
-
- BTPostalAddress *address = [[BTPostalAddress alloc] init];
- address.recipientName = [addressJSON[@"recipientName"] asString]; // Likely to be nil
- address.streetAddress = [addressJSON[@"line1"] asString];
- address.extendedAddress = [addressJSON[@"line2"] asString];
- address.locality = [addressJSON[@"city"] asString];
- address.region = [addressJSON[@"state"] asString];
- address.postalCode = [addressJSON[@"postalCode"] asString];
- address.countryCodeAlpha2 = [addressJSON[@"countryCode"] asString];
-
- return address;
- }
- + (BTPayPalCreditFinancingAmount *)creditFinancingAmountFromJSON:(BTJSON *)amountJSON {
- if (!amountJSON.isObject) {
- return nil;
- }
- NSString *currency = [amountJSON[@"currency"] asString];
- NSString *value = [amountJSON[@"value"] asString];
- return [[BTPayPalCreditFinancingAmount alloc] initWithCurrency:currency value:value];
- }
- + (BTPayPalCreditFinancing *)creditFinancingFromJSON:(BTJSON *)creditFinancingOfferedJSON {
- if (!creditFinancingOfferedJSON.isObject) {
- return nil;
- }
- BOOL isCardAmountImmutable = [creditFinancingOfferedJSON[@"cardAmountImmutable"] isTrue];
- BTPayPalCreditFinancingAmount *monthlyPayment = [self.class creditFinancingAmountFromJSON:creditFinancingOfferedJSON[@"monthlyPayment"]];
- BOOL payerAcceptance = [creditFinancingOfferedJSON[@"payerAcceptance"] isTrue];
- NSInteger term = [creditFinancingOfferedJSON[@"term"] asIntegerOrZero];
- BTPayPalCreditFinancingAmount *totalCost = [self.class creditFinancingAmountFromJSON:creditFinancingOfferedJSON[@"totalCost"]];
- BTPayPalCreditFinancingAmount *totalInterest = [self.class creditFinancingAmountFromJSON:creditFinancingOfferedJSON[@"totalInterest"]];
- return [[BTPayPalCreditFinancing alloc] initWithCardAmountImmutable:isCardAmountImmutable
- monthlyPayment:monthlyPayment
- payerAcceptance:payerAcceptance
- term:term
- totalCost:totalCost
- totalInterest:totalInterest];
- }
- + (BTPayPalAccountNonce *)payPalAccountFromJSON:(BTJSON *)payPalAccount {
- NSString *nonce = [payPalAccount[@"nonce"] asString];
- NSString *description = [payPalAccount[@"description"] asString];
-
- BTJSON *details = payPalAccount[@"details"];
-
- NSString *email = [details[@"email"] asString];
- NSString *clientMetadataId = [details[@"correlationId"] asString];
- // Allow email to be under payerInfo
- if ([details[@"payerInfo"][@"email"] isString]) {
- email = [details[@"payerInfo"][@"email"] asString];
- }
-
- NSString *firstName = [details[@"payerInfo"][@"firstName"] asString];
- NSString *lastName = [details[@"payerInfo"][@"lastName"] asString];
- NSString *phone = [details[@"payerInfo"][@"phone"] asString];
- NSString *payerId = [details[@"payerInfo"][@"payerId"] asString];
- BOOL isDefault = [payPalAccount[@"default"] isTrue];
-
- BTPostalAddress *shippingAddress = [self.class shippingOrBillingAddressFromJSON:details[@"payerInfo"][@"shippingAddress"]];
- BTPostalAddress *billingAddress = [self.class shippingOrBillingAddressFromJSON:details[@"payerInfo"][@"billingAddress"]];
- if (!shippingAddress) {
- shippingAddress = [self.class accountAddressFromJSON:details[@"payerInfo"][@"accountAddress"]];
- }
-
- // Braintree gateway has some inconsistent behavior depending on
- // the type of nonce, and sometimes returns "PayPal" for description,
- // and sometimes returns a real identifying string. The former is not
- // desirable for display. The latter is.
- // As a workaround, we ignore descriptions that look like "PayPal".
- if ([description caseInsensitiveCompare:@"PayPal"] == NSOrderedSame) {
- description = email;
- }
- BTPayPalCreditFinancing *creditFinancing = [self.class creditFinancingFromJSON:details[@"creditFinancingOffered"]];
- BTPayPalAccountNonce *tokenizedPayPalAccount = [[BTPayPalAccountNonce alloc] initWithNonce:nonce
- description:description
- email:email
- firstName:firstName
- lastName:lastName
- phone:phone
- billingAddress:billingAddress
- shippingAddress:shippingAddress
- clientMetadataId:clientMetadataId
- payerId:payerId
- isDefault:isDefault
- creditFinancing:creditFinancing];
-
- return tokenizedPayPalAccount;
- }
- + (NSString *)intentTypeToString:(BTPayPalRequestIntent)intentType {
- NSString *result = nil;
- switch(intentType) {
- case BTPayPalRequestIntentAuthorize:
- result = @"authorize";
- break;
- case BTPayPalRequestIntentSale:
- result = @"sale";
- break;
- case BTPayPalRequestIntentOrder:
- result = @"order";
- break;
- default:
- result = @"authorize";
- break;
- }
- return result;
- }
- + (NSString *)landingPageTypeToString:(BTPayPalRequestLandingPageType)landingPageType {
- switch(landingPageType) {
- case BTPayPalRequestLandingPageTypeLogin:
- return @"login";
- case BTPayPalRequestLandingPageTypeBilling:
- return @"billing";
- default:
- return nil;
- }
- }
- #pragma mark - Delegate Informers
- - (void)informDelegateWillPerformAppSwitch {
- NSNotification *notification = [[NSNotification alloc] initWithName:BTAppSwitchWillSwitchNotification
- object:self
- userInfo:nil];
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- if ([self.appSwitchDelegate respondsToSelector:@selector(appSwitcherWillPerformAppSwitch:)]) {
- [self.appSwitchDelegate appSwitcherWillPerformAppSwitch:self];
- }
- }
- - (void)informDelegateDidPerformAppSwitchToTarget:(PPOTRequestTarget)target {
- BTAppSwitchTarget appSwitchTarget;
- switch (target) {
- case PPOTRequestTargetBrowser:
- appSwitchTarget = BTAppSwitchTargetWebBrowser;
- break;
- case PPOTRequestTargetOnDeviceApplication:
- appSwitchTarget = BTAppSwitchTargetNativeApp;
- break;
- case PPOTRequestTargetNone:
- case PPOTRequestTargetUnknown:
- appSwitchTarget = BTAppSwitchTargetUnknown;
- // Should never happen
- break;
- }
-
- NSNotification *notification = [[NSNotification alloc] initWithName:BTAppSwitchDidSwitchNotification
- object:self
- userInfo:@{ BTAppSwitchNotificationTargetKey : @(appSwitchTarget) } ];
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- if ([self.appSwitchDelegate respondsToSelector:@selector(appSwitcher:didPerformSwitchToTarget:)]) {
- [self.appSwitchDelegate appSwitcher:self didPerformSwitchToTarget:appSwitchTarget];
- }
- }
- - (void)informDelegateWillProcessAppSwitchReturn {
- NSNotification *notification = [[NSNotification alloc] initWithName:BTAppSwitchWillProcessPaymentInfoNotification
- object:self
- userInfo:nil];
- [[NSNotificationCenter defaultCenter] postNotification:notification];
-
- if ([self.appSwitchDelegate respondsToSelector:@selector(appSwitcherWillProcessPaymentInfo:)]) {
- [self.appSwitchDelegate appSwitcherWillProcessPaymentInfo:self];
- }
- }
- - (void)informDelegateAppContextWillSwitch {
- NSNotification *notification = [[NSNotification alloc] initWithName:BTAppContextWillSwitchNotification
- object:self
- userInfo:nil];
- [[NSNotificationCenter defaultCenter] postNotification:notification];
- if ([self.appSwitchDelegate respondsToSelector:@selector(appContextWillSwitch:)]) {
- [self.appSwitchDelegate appContextWillSwitch:self];
- }
- }
- - (void)informDelegateAppContextDidReturn {
- NSNotification *notification = [[NSNotification alloc] initWithName:BTAppContextDidReturnNotification
- object:self
- userInfo:nil];
- [[NSNotificationCenter defaultCenter] postNotification:notification];
- if ([self.appSwitchDelegate respondsToSelector:@selector(appContextDidReturn:)]) {
- [self.appSwitchDelegate appContextDidReturn:self];
- }
- }
- - (void)informDelegatePresentingViewControllerRequestPresent:(NSURL*)appSwitchURL {
- if (self.viewControllerPresentingDelegate != nil && [self.viewControllerPresentingDelegate respondsToSelector:@selector(paymentDriver:requestsPresentationOfViewController:)]) {
- if (@available(iOS 9.0, *)) {
- self.safariViewController = [[SFSafariViewController alloc] initWithURL:appSwitchURL];
- self.safariViewController.delegate = self;
- self.safariViewController.transitioningDelegate = self;
- [self.viewControllerPresentingDelegate paymentDriver:self requestsPresentationOfViewController:self.safariViewController];
- }
- } else {
- [[BTLogger sharedLogger] critical:@"Unable to display View Controller to continue PayPal flow. BTPayPalDriver needs a viewControllerPresentingDelegate<BTViewControllerPresentingDelegate> to be set."];
- }
- }
- - (void)informDelegatePresentingViewControllerNeedsDismissal {
- if (self.viewControllerPresentingDelegate != nil && [self.viewControllerPresentingDelegate respondsToSelector:@selector(paymentDriver:requestsDismissalOfViewController:)]) {
- if (@available(iOS 9.0, *)) {
- [self.viewControllerPresentingDelegate paymentDriver:self requestsDismissalOfViewController:self.safariViewController];
- self.safariViewController = nil;
- }
- } else {
- [[BTLogger sharedLogger] critical:@"Unable to dismiss View Controller to end PayPal flow. BTPayPalDriver needs a viewControllerPresentingDelegate<BTViewControllerPresentingDelegate> to be set."];
- }
- }
- #pragma mark - SFSafariViewControllerDelegate
- static NSString * const SFSafariViewControllerFinishedURL = @"sfsafariviewcontroller://finished";
- - (void)safariViewControllerDidFinish:(__unused SFSafariViewController *)controller API_AVAILABLE(ios(9.0)) {
- [self.class handleAppSwitchReturnURL:[NSURL URLWithString:SFSafariViewControllerFinishedURL]];
- }
- #pragma mark - Preflight check
- - (BOOL)verifyAppSwitchWithRemoteConfiguration:(BTJSON *)configuration error:(NSError * __autoreleasing *)error {
- if (![configuration[@"paypalEnabled"] isTrue]) {
- [self.apiClient sendAnalyticsEvent:@"ios.paypal-otc.preflight.disabled"];
- if (error != NULL) {
- *error = [NSError errorWithDomain:BTPayPalDriverErrorDomain
- code:BTPayPalDriverErrorTypeDisabled
- userInfo:@{ NSLocalizedDescriptionKey: @"PayPal is not enabled for this merchant",
- NSLocalizedRecoverySuggestionErrorKey: @"Enable PayPal for this merchant in the Braintree Control Panel" }];
- }
- return NO;
- }
-
- if (self.returnURLScheme == nil || [self.returnURLScheme isEqualToString:@""]) {
- NSString *recoverySuggestion = @"PayPal requires a return URL scheme to be configured via [BTAppSwitch setReturnURLScheme:]. This custom URL scheme must also be registered with your app.";
- [[BTLogger sharedLogger] critical:recoverySuggestion];
- [self.apiClient sendAnalyticsEvent:@"ios.paypal-otc.preflight.nil-return-url-scheme"];
- if (error != NULL) {
- *error = [NSError errorWithDomain:BTPayPalDriverErrorDomain
- code:BTPayPalDriverErrorTypeIntegrationReturnURLScheme
- userInfo:@{ NSLocalizedDescriptionKey: @"PayPal app switch is missing a returnURLScheme",
- NSLocalizedRecoverySuggestionErrorKey: recoverySuggestion }];
- }
- return NO;
- }
-
- if (![[self.class payPalClass] doesApplicationSupportOneTouchCallbackURLScheme:self.returnURLScheme]) {
- NSString *recoverySuggestion = [NSString stringWithFormat:@"PayPal requires [BTAppSwitch setReturnURLScheme:] to be configured to begin with your app's bundle ID (%@). Currently, it is set to (%@).", [NSBundle mainBundle].bundleIdentifier, self.returnURLScheme];
- [[BTLogger sharedLogger] critical:recoverySuggestion];
-
- [self.apiClient sendAnalyticsEvent:@"ios.paypal-otc.preflight.invalid-return-url-scheme"];
- if (error != NULL) {
- *error = [NSError errorWithDomain:BTPayPalDriverErrorDomain
- code:BTPayPalDriverErrorTypeIntegrationReturnURLScheme
- userInfo:@{NSLocalizedFailureReasonErrorKey: @"Application does not support One Touch callback URL scheme",
- NSLocalizedRecoverySuggestionErrorKey: recoverySuggestion }];
- }
- return NO;
- }
-
- return YES;
- }
- #pragma mark - Analytics Helpers
- + (NSString *)eventStringForPaymentType:(BTPayPalPaymentType)paymentType {
- switch (paymentType) {
- case BTPayPalPaymentTypeBillingAgreement:
- return @"paypal-ba";
- case BTPayPalPaymentTypeFuturePayments:
- return @"paypal-future-payments";
- case BTPayPalPaymentTypeCheckout:
- return @"paypal-single-payment";
- case BTPayPalPaymentTypeUnknown:
- return nil;
- }
- }
- + (NSString *)eventStringForRequestTarget:(PPOTRequestTarget)requestTarget {
- switch (requestTarget) {
- case PPOTRequestTargetNone:
- return @"none";
- case PPOTRequestTargetUnknown:
- return @"unknown";
- case PPOTRequestTargetOnDeviceApplication:
- return @"appswitch";
- case PPOTRequestTargetBrowser:
- return @"webswitch";
- }
- }
- - (void)sendAnalyticsEventForInitiatingOneTouchForPaymentType:(BTPayPalPaymentType)paymentType
- withSuccess:(BOOL)success
- target:(PPOTRequestTarget)target {
- if (paymentType == BTPayPalPaymentTypeUnknown) return;
-
- NSString *eventName = [NSString stringWithFormat:@"ios.%@.%@.initiate.%@", [self.class eventStringForPaymentType:paymentType], [self.class eventStringForRequestTarget:target], success ? @"started" : @"failed"];
- [self.apiClient sendAnalyticsEvent:eventName];
- if ((paymentType == BTPayPalPaymentTypeCheckout || paymentType == BTPayPalPaymentTypeBillingAgreement) && self.payPalRequest.offerCredit) {
- NSString *eventName = [NSString stringWithFormat:@"ios.%@.%@.credit.offered.%@", [self.class eventStringForPaymentType:paymentType], [self.class eventStringForRequestTarget:target], success ? @"started" : @"failed"];
- [self.apiClient sendAnalyticsEvent:eventName];
- }
- }
- - (void)sendAnalyticsEventForHandlingOneTouchResult:(PPOTResult *)result forPaymentType:(BTPayPalPaymentType)paymentType {
- if (paymentType == BTPayPalPaymentTypeUnknown) return;
-
- NSString *eventName = [NSString stringWithFormat:@"ios.%@.%@", [self.class eventStringForPaymentType:paymentType], [self.class eventStringForRequestTarget:result.target]];
-
- switch (result.type) {
- case PPOTResultTypeError:
- if (result.error.code == PPOTErrorCodePersistedDataFetchFailed) {
- return [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"%@.failed-keychain", eventName]];
- }
- return [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"%@.failed", eventName]];
- case PPOTResultTypeCancel:
- if (result.error) {
- return [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"%@.canceled-with-error", eventName]];
- } else {
- return [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"%@.canceled", eventName]];
- }
- case PPOTResultTypeSuccess:
- return [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"%@.succeeded", eventName]];
- }
- }
- - (void)sendAnalyticsEventIfCreditFinancingInNonce:(BTPayPalAccountNonce *)payPalAccountNonce forPaymentType:(BTPayPalPaymentType)paymentType {
- if ([payPalAccountNonce creditFinancing]) {
- NSString *eventName = [NSString stringWithFormat:@"ios.%@.credit.accepted", [self.class eventStringForPaymentType:paymentType]];
- [self.apiClient sendAnalyticsEvent:eventName];
- }
- }
- - (void)sendAnalyticsEventForTokenizationSuccessForPaymentType:(BTPayPalPaymentType)paymentType {
- if (paymentType == BTPayPalPaymentTypeUnknown) return;
-
- NSString *eventName = [NSString stringWithFormat:@"ios.%@.tokenize.succeeded", [self.class eventStringForPaymentType:paymentType]];
- [self.apiClient sendAnalyticsEvent:eventName];
- }
- - (void)sendAnalyticsEventForTokenizationFailureForPaymentType:(BTPayPalPaymentType)paymentType {
- if (paymentType == BTPayPalPaymentTypeUnknown) return;
-
- NSString *eventName = [NSString stringWithFormat:@"ios.%@.tokenize.failed", [self.class eventStringForPaymentType:paymentType]];
- [self.apiClient sendAnalyticsEvent:eventName];
- }
- #pragma mark - App Switch handling
- - (BOOL)isiOSAppAvailableForAppSwitch {
- return [[self.class payPalClass] isWalletAppInstalled];
- }
- + (BOOL)canHandleAppSwitchReturnURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication {
- return appSwitchReturnBlock != nil && [PPOTCore canParseURL:url sourceApplication:sourceApplication];
- }
- + (void)handleAppSwitchReturnURL:(NSURL *)url {
- if (appSwitchReturnBlock) {
- appSwitchReturnBlock(url);
- }
- }
- - (NSString *)returnURLScheme {
- if (!_returnURLScheme) {
- _returnURLScheme = [[BTAppSwitch sharedInstance] returnURLScheme];
- }
- return _returnURLScheme;
- }
- #pragma mark - BTPayPalApprovalHandler delegate methods
- - (void)onApprovalComplete:(NSURL *)url {
- [self.class handleAppSwitchReturnURL:url];
- }
- - (void)onApprovalCancel {
- [self.class handleAppSwitchReturnURL:[NSURL URLWithString:SFSafariViewControllerFinishedURL]];
- }
- #pragma mark - Internal
- - (NSURL *)decorateApprovalURL:(NSURL*)approvalURL forRequest:(BTPayPalRequest *)paypalRequest {
- if (approvalURL != nil && paypalRequest.userAction != BTPayPalRequestUserActionDefault) {
- NSURLComponents* approvalURLComponents = [[NSURLComponents alloc] initWithURL:approvalURL resolvingAgainstBaseURL:NO];
- if (approvalURLComponents != nil) {
- NSString *userActionValue = [BTPayPalDriver userActionTypeToString:paypalRequest.userAction];
- if ([userActionValue length] > 0) {
- NSString *query = [approvalURLComponents query];
- NSString *delimiter = [query length] == 0 ? @"" : @"&";
- query = [NSString stringWithFormat:@"%@%@useraction=%@", query, delimiter, userActionValue];
- approvalURLComponents.query = query;
- }
- return [approvalURLComponents URL];
- }
- }
- return approvalURL;
- }
- + (NSString *)userActionTypeToString:(BTPayPalRequestUserAction)userActionType {
- NSString *result = nil;
- switch(userActionType) {
- case BTPayPalRequestUserActionCommit:
- result = @"commit";
- break;
- default:
- result = @"";
- break;
- }
- return result;
- }
- - (BTPayPalRequestFactory *)requestFactory {
- if (!_requestFactory) {
- _requestFactory = [[BTPayPalRequestFactory alloc] init];
- }
- return _requestFactory;
- }
- static Class PayPalClass;
- + (void)setPayPalClass:(Class)payPalClass {
- if ([payPalClass isSubclassOfClass:[PPOTCore class]]) {
- PayPalClass = payPalClass;
- }
- }
- + (Class)payPalClass {
- return PayPalClass;
- }
- @end
|