BTLocalPaymentRequest.m 15 KB

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