BTLocalPaymentRequest.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. #import "BTPaymentFlowDriver_Internal.h"
  2. #import "BTPaymentFlowDriver+LocalPayment_Internal.h"
  3. #import <SafariServices/SafariServices.h>
  4. #if __has_include(<Braintree/BraintreePaymentFlow.h>) // CocoaPods
  5. #import <Braintree/BTLocalPaymentRequest.h>
  6. #import <Braintree/BTConfiguration+LocalPayment.h>
  7. #import <Braintree/BTLocalPaymentResult.h>
  8. #import <Braintree/BTLogger_Internal.h>
  9. #import <Braintree/BTAPIClient_Internal.h>
  10. #import <Braintree/BraintreeCore.h>
  11. #elif SWIFT_PACKAGE // SPM
  12. #import <BraintreePaymentFlow/BTLocalPaymentRequest.h>
  13. #import <BraintreePaymentFlow/BTConfiguration+LocalPayment.h>
  14. #import <BraintreePaymentFlow/BTLocalPaymentResult.h>
  15. #import "../BraintreeCore/BTLogger_Internal.h"
  16. #import "../BraintreeCore/BTAPIClient_Internal.h"
  17. #import <BraintreeCore/BraintreeCore.h>
  18. #else // Carthage
  19. #import <BraintreePaymentFlow/BTLocalPaymentRequest.h>
  20. #import <BraintreePaymentFlow/BTConfiguration+LocalPayment.h>
  21. #import <BraintreePaymentFlow/BTLocalPaymentResult.h>
  22. #import <BraintreeCore/BTLogger_Internal.h>
  23. #import <BraintreeCore/BTAPIClient_Internal.h>
  24. #import <BraintreeCore/BraintreeCore.h>
  25. #endif
  26. #if __has_include(<Braintree/Braintree-Swift.h>) // CocoaPods
  27. #import <Braintree/Braintree-Swift.h>
  28. #elif SWIFT_PACKAGE // SPM
  29. /* Use @import for SPM support
  30. * See https://forums.swift.org/t/using-a-swift-package-in-a-mixed-swift-and-objective-c-project/27348
  31. */
  32. @import PayPalDataCollector;
  33. #elif __has_include("Braintree-Swift.h") // CocoaPods for ReactNative
  34. /* Use quoted style when importing Swift headers for ReactNative support
  35. * See https://github.com/braintree/braintree_ios/issues/671
  36. */
  37. #import "Braintree-Swift.h"
  38. #else // Carthage
  39. #import <PayPalDataCollector/PayPalDataCollector-Swift.h>
  40. #endif
  41. @interface BTLocalPaymentRequest ()
  42. @property (nonatomic, copy, nullable) NSString *paymentID;
  43. @property (nonatomic, weak) id<BTPaymentFlowDriverDelegate> paymentFlowDriverDelegate;
  44. @property (nonatomic, strong) NSString *correlationID;
  45. @end
  46. @implementation BTLocalPaymentRequest
  47. - (void)handleRequest:(BTPaymentFlowRequest *)request client:(BTAPIClient *)apiClient paymentDriverDelegate:(id<BTPaymentFlowDriverDelegate>)delegate {
  48. self.paymentFlowDriverDelegate = delegate;
  49. BTLocalPaymentRequest *localPaymentRequest = (BTLocalPaymentRequest *)request;
  50. [apiClient fetchOrReturnRemoteConfiguration:^(__unused BTConfiguration *configuration, NSError *configurationError) {
  51. if (configurationError) {
  52. [delegate onPaymentComplete:nil error:configurationError];
  53. return;
  54. }
  55. self.correlationID = [PPDataCollector clientMetadataID:nil isSandbox:[configuration.environment isEqualToString:@"sandbox"]];
  56. NSError *integrationError;
  57. if ([self.paymentFlowDriverDelegate returnURLScheme] == nil || [[self.paymentFlowDriverDelegate returnURLScheme] isEqualToString:@""]) {
  58. [[BTLogger sharedLogger] critical:@"Local Payment requires a return URL scheme to be configured via [BTAppContextSwitcher setReturnURLScheme:]"];
  59. integrationError = [NSError errorWithDomain:BTPaymentFlowDriverErrorDomain
  60. code:BTPaymentFlowDriverErrorTypeInvalidReturnURL
  61. userInfo:@{NSLocalizedDescriptionKey: @"UIApplication failed to perform app or browser switch."}];
  62. } else if (![configuration isLocalPaymentEnabled]) {
  63. [[BTLogger sharedLogger] critical:@"Enable PayPal for this merchant in the Braintree Control Panel to use Local Payments."];
  64. integrationError = [NSError errorWithDomain:BTPaymentFlowDriverErrorDomain
  65. code:BTPaymentFlowDriverErrorTypeDisabled
  66. userInfo:@{NSLocalizedDescriptionKey: @"Enable PayPal for this merchant in the Braintree Control Panel to use Local Payments."}];
  67. } else if (localPaymentRequest.localPaymentFlowDelegate == nil) {
  68. [[BTLogger sharedLogger] critical:@"BTLocalPaymentRequest localPaymentFlowDelegate can not be nil."];
  69. integrationError = [NSError errorWithDomain:BTPaymentFlowDriverErrorDomain
  70. code:BTPaymentFlowDriverErrorTypeIntegration
  71. userInfo:@{NSLocalizedDescriptionKey: @"Failed to begin payment flow: BTLocalPaymentRequest localPaymentFlowDelegate can not be nil."}];
  72. } else if (localPaymentRequest.amount == nil || localPaymentRequest.paymentType == nil) {
  73. [[BTLogger sharedLogger] critical:@"BTLocalPaymentRequest amount and paymentType can not be nil."];
  74. integrationError = [NSError errorWithDomain:BTPaymentFlowDriverErrorDomain
  75. code:BTPaymentFlowDriverErrorTypeIntegration
  76. userInfo:@{NSLocalizedDescriptionKey: @"Failed to begin payment flow: BTLocalPaymentRequest amount and paymentType can not be nil."}];
  77. }
  78. if (integrationError != nil) {
  79. [delegate onPaymentComplete:nil error:integrationError];
  80. return;
  81. }
  82. NSMutableDictionary *params = [@{
  83. @"amount": localPaymentRequest.amount,
  84. @"funding_source": localPaymentRequest.paymentType,
  85. @"intent": @"sale"
  86. } mutableCopy];
  87. params[@"return_url"] = [NSString stringWithFormat:@"%@%@", [delegate returnURLScheme], @"://x-callback-url/braintree/local-payment/success"];
  88. params[@"cancel_url"] = [NSString stringWithFormat:@"%@%@", [delegate returnURLScheme], @"://x-callback-url/braintree/local-payment/cancel"];
  89. if (localPaymentRequest.paymentTypeCountryCode) {
  90. params[@"payment_type_country_code"] = localPaymentRequest.paymentTypeCountryCode;
  91. }
  92. if (localPaymentRequest.address) {
  93. params[@"line1"] = localPaymentRequest.address.streetAddress;
  94. params[@"line2"] = localPaymentRequest.address.extendedAddress;
  95. params[@"city"] = localPaymentRequest.address.locality;
  96. params[@"state"] = localPaymentRequest.address.region;
  97. params[@"postal_code"] = localPaymentRequest.address.postalCode;
  98. params[@"country_code"] = localPaymentRequest.address.countryCodeAlpha2;
  99. }
  100. if (localPaymentRequest.currencyCode) {
  101. params[@"currency_iso_code"] = localPaymentRequest.currencyCode;
  102. }
  103. if (localPaymentRequest.givenName) {
  104. params[@"first_name"] = localPaymentRequest.givenName;
  105. }
  106. if (localPaymentRequest.surname) {
  107. params[@"last_name"] = localPaymentRequest.surname;
  108. }
  109. if (localPaymentRequest.email) {
  110. params[@"payer_email"] = localPaymentRequest.email;
  111. }
  112. if (localPaymentRequest.phone) {
  113. params[@"phone"] = localPaymentRequest.phone;
  114. }
  115. if (localPaymentRequest.merchantAccountID) {
  116. params[@"merchant_account_id"] = localPaymentRequest.merchantAccountID;
  117. }
  118. if (localPaymentRequest.bic) {
  119. params[@"bic"] = localPaymentRequest.bic;
  120. }
  121. NSMutableDictionary *experienceProfile = [@{
  122. @"no_shipping": @(!localPaymentRequest.isShippingAddressRequired)
  123. } mutableCopy];
  124. if (localPaymentRequest.displayName) {
  125. experienceProfile[@"brand_name"] = localPaymentRequest.displayName;
  126. }
  127. params[@"experience_profile"] = [experienceProfile copy];
  128. [apiClient POST:@"v1/local_payments/create"
  129. parameters:params
  130. completion:^(BTJSON *body, __unused NSHTTPURLResponse *response, NSError *error) {
  131. if (!error) {
  132. self.paymentID = [body[@"paymentResource"][@"paymentToken"] asString];
  133. NSString *approvalUrl = [body[@"paymentResource"][@"redirectUrl"] asString];
  134. NSURL *url = [NSURL URLWithString:approvalUrl];
  135. if (self.paymentID && url) {
  136. [self.localPaymentFlowDelegate localPaymentStarted:self paymentID:self.paymentID start:^{
  137. [delegate onPaymentWithURL:url error:error];
  138. }];
  139. } else {
  140. [[BTLogger sharedLogger] critical:@"Payment cannot be processed: the redirectUrl or paymentToken is nil. Contact Braintree support if the error persists."];
  141. NSError *error = [NSError errorWithDomain:BTPaymentFlowDriverErrorDomain
  142. code:BTPaymentFlowDriverErrorTypeAppSwitchFailed
  143. userInfo:@{NSLocalizedDescriptionKey: @"Payment cannot be processed: the redirectUrl or paymentToken is nil. Contact Braintree support if the error persists."}];
  144. [delegate onPaymentComplete:nil error:error];
  145. return;
  146. }
  147. } else {
  148. if (error.code == NETWORK_CONNECTION_LOST_CODE) {
  149. [apiClient sendAnalyticsEvent:@"ios.local-payment-methods.network-connection.failure"];
  150. }
  151. [delegate onPaymentWithURL:nil error:error];
  152. }
  153. }];
  154. }];
  155. }
  156. - (void)handleOpenURL:(__unused NSURL *)url {
  157. if ([url.host isEqualToString:@"x-callback-url"] && [url.path hasPrefix:@"/braintree/local-payment/cancel"]) {
  158. // canceled
  159. NSError *error = [NSError errorWithDomain:BTPaymentFlowDriverErrorDomain
  160. code:BTPaymentFlowDriverErrorTypeCanceled
  161. userInfo:@{}];
  162. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:error];
  163. } else {
  164. // success
  165. NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
  166. parameters[@"paypal_account"] = [@{} mutableCopy];
  167. parameters[@"paypal_account"][@"response"] = @{ @"webURL": url.absoluteString };
  168. parameters[@"paypal_account"][@"response_type"] = @"web";
  169. parameters[@"paypal_account"][@"options"] = @{ @"validate": @NO };
  170. parameters[@"paypal_account"][@"intent"] = @"sale";
  171. if (self.correlationID) {
  172. parameters[@"paypal_account"][@"correlation_id"] = self.correlationID;
  173. }
  174. if (self.merchantAccountID) {
  175. parameters[@"merchant_account_id"] = self.merchantAccountID;
  176. }
  177. BTClientMetadata *metadata = self.paymentFlowDriverDelegate.apiClient.metadata;
  178. parameters[@"_meta"] = @{
  179. @"source" : metadata.sourceString,
  180. @"integration" : metadata.integrationString,
  181. @"sessionId" : metadata.sessionID,
  182. };
  183. [self.paymentFlowDriverDelegate.apiClient POST:@"/v1/payment_methods/paypal_accounts"
  184. parameters:parameters
  185. completion:^(BTJSON *body, __unused NSHTTPURLResponse *response, NSError *error)
  186. {
  187. if (error) {
  188. if (error.code == NETWORK_CONNECTION_LOST_CODE) {
  189. [self.paymentFlowDriverDelegate.apiClient sendAnalyticsEvent:@"ios.local-payment-methods.network-connection.failure"];
  190. }
  191. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:error];
  192. return;
  193. } else {
  194. BTJSON *payPalAccount = body[@"paypalAccounts"][0];
  195. NSString *nonce = [payPalAccount[@"nonce"] asString];
  196. NSString *type = [payPalAccount[@"type"] asString];
  197. BTJSON *details = payPalAccount[@"details"];
  198. NSString *email = [details[@"email"] asString];
  199. NSString *clientMetadataID = [details[@"correlationId"] asString];
  200. // Allow email to be under payerInfo
  201. if ([details[@"payerInfo"][@"email"] isString]) {
  202. email = [details[@"payerInfo"][@"email"] asString];
  203. }
  204. NSString *firstName = [details[@"payerInfo"][@"firstName"] asString];
  205. NSString *lastName = [details[@"payerInfo"][@"lastName"] asString];
  206. NSString *phone = [details[@"payerInfo"][@"phone"] asString];
  207. NSString *payerID = [details[@"payerInfo"][@"payerId"] asString];
  208. BTPostalAddress *shippingAddress = [self.class shippingOrBillingAddressFromJSON:details[@"payerInfo"][@"shippingAddress"]];
  209. BTPostalAddress *billingAddress = [self.class shippingOrBillingAddressFromJSON:details[@"payerInfo"][@"billingAddress"]];
  210. if (!shippingAddress) {
  211. shippingAddress = [self.class accountAddressFromJSON:details[@"payerInfo"][@"accountAddress"]];
  212. }
  213. BTLocalPaymentResult *tokenizedLocalPayment = [[BTLocalPaymentResult alloc] initWithNonce:nonce
  214. type:type
  215. email:email
  216. firstName:firstName
  217. lastName:lastName
  218. phone:phone
  219. billingAddress:billingAddress
  220. shippingAddress:shippingAddress
  221. clientMetadataID:clientMetadataID
  222. payerID:payerID];
  223. [self.paymentFlowDriverDelegate onPaymentComplete:tokenizedLocalPayment error:nil];
  224. }
  225. }];
  226. }
  227. }
  228. + (BTPostalAddress *)accountAddressFromJSON:(BTJSON *)addressJSON {
  229. if (!addressJSON.isObject) {
  230. return nil;
  231. }
  232. BTPostalAddress *address = [[BTPostalAddress alloc] init];
  233. address.recipientName = [addressJSON[@"recipientName"] asString]; // Likely to be nil
  234. address.streetAddress = [addressJSON[@"street1"] asString];
  235. address.extendedAddress = [addressJSON[@"street2"] asString];
  236. address.locality = [addressJSON[@"city"] asString];
  237. address.region = [addressJSON[@"state"] asString];
  238. address.postalCode = [addressJSON[@"postalCode"] asString];
  239. address.countryCodeAlpha2 = [addressJSON[@"country"] asString];
  240. return address;
  241. }
  242. + (BTPostalAddress *)shippingOrBillingAddressFromJSON:(BTJSON *)addressJSON {
  243. if (!addressJSON.isObject) {
  244. return nil;
  245. }
  246. BTPostalAddress *address = [[BTPostalAddress alloc] init];
  247. address.recipientName = [addressJSON[@"recipientName"] asString]; // Likely to be nil
  248. address.streetAddress = [addressJSON[@"line1"] asString];
  249. address.extendedAddress = [addressJSON[@"line2"] asString];
  250. address.locality = [addressJSON[@"city"] asString];
  251. address.region = [addressJSON[@"state"] asString];
  252. address.postalCode = [addressJSON[@"postalCode"] asString];
  253. address.countryCodeAlpha2 = [addressJSON[@"countryCode"] asString];
  254. return address;
  255. }
  256. - (BOOL)canHandleAppSwitchReturnURL:(NSURL *)url {
  257. return [url.host isEqualToString:@"x-callback-url"] && [url.path hasPrefix:@"/braintree/local-payment"];
  258. }
  259. - (NSString *)paymentFlowName {
  260. NSString *paymentType = self.paymentType != nil ? [self.paymentType lowercaseString] : @"unknown";
  261. return [NSString stringWithFormat:@"%@.local-payment", paymentType];
  262. }
  263. @end