73ed51ae1a31e8cb89245b517175dbb64bb0a04a
[WebKit-https.git] / Source / WebKit / Shared / ApplePay / cocoa / WebPaymentCoordinatorProxyCocoa.mm
1 /*
2  * Copyright (C) 2016-2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WebPaymentCoordinatorProxyCocoa.h"
28
29 #if ENABLE(APPLE_PAY)
30
31 #import "WKPaymentAuthorizationDelegate.h"
32 #import "WebPaymentCoordinatorProxy.h"
33 #import <WebCore/PaymentAuthorizationStatus.h>
34 #import <WebCore/PaymentHeaders.h>
35 #import <pal/cocoa/PassKitSoftLink.h>
36 #import <wtf/BlockPtr.h>
37 #import <wtf/RunLoop.h>
38 #import <wtf/URL.h>
39
40 // FIXME: Once rdar://problem/24420024 has been fixed, import PKPaymentRequest_Private.h instead.
41 @interface PKPaymentRequest ()
42 @property (nonatomic, retain) NSURL *originatingURL;
43 @end
44
45 @interface PKPaymentRequest ()
46 // FIXME: Remove this once it's in an SDK.
47 @property (nonatomic, strong) NSArray *thumbnailURLs;
48 @property (nonatomic, strong) NSURL *thumbnailURL;
49
50 @property (nonatomic, assign) BOOL expectsMerchantSession;
51 @end
52
53 namespace WebKit {
54
55 bool WebPaymentCoordinatorProxy::platformCanMakePayments()
56 {
57     return [PAL::getPKPaymentAuthorizationViewControllerClass() canMakePayments];
58 }
59
60 void WebPaymentCoordinatorProxy::platformCanMakePaymentsWithActiveCard(const String& merchantIdentifier, const String& domainName, PAL::SessionID sessionID, WTF::Function<void(bool)>&& completionHandler)
61 {
62 #if HAVE(PASSKIT_GRANULAR_ERRORS)
63     PKCanMakePaymentsWithMerchantIdentifierDomainAndSourceApplication(merchantIdentifier, domainName, m_client.paymentCoordinatorSourceApplicationSecondaryIdentifier(*this, sessionID), makeBlockPtr([completionHandler = WTFMove(completionHandler)](BOOL canMakePayments, NSError *error) mutable {
64         if (error)
65             LOG_ERROR("PKCanMakePaymentsWithMerchantIdentifierAndDomain error %@", error);
66
67         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), canMakePayments] {
68             completionHandler(canMakePayments);
69         });
70     }).get());
71 #else
72     PKCanMakePaymentsWithMerchantIdentifierAndDomain(merchantIdentifier, domainName, makeBlockPtr([completionHandler = WTFMove(completionHandler)](BOOL canMakePayments, NSError *error) mutable {
73         if (error)
74             LOG_ERROR("PKCanMakePaymentsWithMerchantIdentifierAndDomain error %@", error);
75
76         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), canMakePayments] {
77             completionHandler(canMakePayments);
78         });
79     }).get());
80 #endif
81 }
82
83 void WebPaymentCoordinatorProxy::platformOpenPaymentSetup(const String& merchantIdentifier, const String& domainName, WTF::Function<void(bool)>&& completionHandler)
84 {
85     auto passLibrary = adoptNS([PAL::allocPKPassLibraryInstance() init]);
86     [passLibrary openPaymentSetupForMerchantIdentifier:merchantIdentifier domain:domainName completion:makeBlockPtr([completionHandler = WTFMove(completionHandler)](BOOL result) mutable {
87         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), result] {
88             completionHandler(result);
89         });
90     }).get()];
91 }
92
93 #if HAVE(PASSKIT_GRANULAR_ERRORS)
94 static RetainPtr<NSSet> toPKContactFields(const WebCore::ApplePaySessionPaymentRequest::ContactFields& contactFields)
95 {
96     Vector<NSString *> result;
97
98     if (contactFields.postalAddress)
99         result.append(PAL::get_PassKit_PKContactFieldPostalAddress());
100     if (contactFields.phone)
101         result.append(PAL::get_PassKit_PKContactFieldPhoneNumber());
102     if (contactFields.email)
103         result.append(PAL::get_PassKit_PKContactFieldEmailAddress());
104     if (contactFields.name)
105         result.append(PAL::get_PassKit_PKContactFieldName());
106     if (contactFields.phoneticName)
107         result.append(PAL::get_PassKit_PKContactFieldPhoneticName());
108
109     return adoptNS([[NSSet alloc] initWithObjects:result.data() count:result.size()]);
110 }
111 #else
112 static PKAddressField toPKAddressField(const WebCore::ApplePaySessionPaymentRequest::ContactFields& contactFields)
113 {
114     PKAddressField result = 0;
115
116     if (contactFields.postalAddress)
117         result |= PKAddressFieldPostalAddress;
118     if (contactFields.phone)
119         result |= PKAddressFieldPhone;
120     if (contactFields.email)
121         result |= PKAddressFieldEmail;
122     if (contactFields.name)
123         result |= PKAddressFieldName;
124
125     return result;
126 }
127 #endif
128
129 PKPaymentSummaryItemType toPKPaymentSummaryItemType(WebCore::ApplePaySessionPaymentRequest::LineItem::Type type)
130 {
131     switch (type) {
132     case WebCore::ApplePaySessionPaymentRequest::LineItem::Type::Final:
133         return PKPaymentSummaryItemTypeFinal;
134
135     case WebCore::ApplePaySessionPaymentRequest::LineItem::Type::Pending:
136         return PKPaymentSummaryItemTypePending;
137     }
138 }
139
140 NSDecimalNumber *toDecimalNumber(const String& amount)
141 {
142     if (!amount)
143         return [NSDecimalNumber zero];
144     return [NSDecimalNumber decimalNumberWithString:amount locale:@{ NSLocaleDecimalSeparator : @"." }];
145 }
146
147 PKPaymentSummaryItem *toPKPaymentSummaryItem(const WebCore::ApplePaySessionPaymentRequest::LineItem& lineItem)
148 {
149     return [PAL::getPKPaymentSummaryItemClass() summaryItemWithLabel:lineItem.label amount:toDecimalNumber(lineItem.amount) type:toPKPaymentSummaryItemType(lineItem.type)];
150 }
151
152 NSArray *toPKPaymentSummaryItems(const WebCore::ApplePaySessionPaymentRequest::TotalAndLineItems& totalAndLineItems)
153 {
154     NSMutableArray *paymentSummaryItems = [NSMutableArray arrayWithCapacity:totalAndLineItems.lineItems.size() + 1];
155     for (auto& lineItem : totalAndLineItems.lineItems) {
156         if (PKPaymentSummaryItem *summaryItem = toPKPaymentSummaryItem(lineItem))
157             [paymentSummaryItems addObject:summaryItem];
158     }
159
160     if (PKPaymentSummaryItem *totalItem = toPKPaymentSummaryItem(totalAndLineItems.total))
161         [paymentSummaryItems addObject:totalItem];
162
163     return paymentSummaryItems;
164 }
165
166 static PKMerchantCapability toPKMerchantCapabilities(const WebCore::ApplePaySessionPaymentRequest::MerchantCapabilities& merchantCapabilities)
167 {
168     PKMerchantCapability result = 0;
169     if (merchantCapabilities.supports3DS)
170         result |= PKMerchantCapability3DS;
171     if (merchantCapabilities.supportsEMV)
172         result |= PKMerchantCapabilityEMV;
173     if (merchantCapabilities.supportsCredit)
174         result |= PKMerchantCapabilityCredit;
175     if (merchantCapabilities.supportsDebit)
176         result |= PKMerchantCapabilityDebit;
177
178     return result;
179 }
180
181 static RetainPtr<NSArray> toSupportedNetworks(const Vector<String>& supportedNetworks)
182 {
183     auto result = adoptNS([[NSMutableArray alloc] initWithCapacity:supportedNetworks.size()]);
184     for (auto& supportedNetwork : supportedNetworks)
185         [result addObject:supportedNetwork];
186     return result;
187 }
188
189 static PKShippingType toPKShippingType(WebCore::ApplePaySessionPaymentRequest::ShippingType shippingType)
190 {
191     switch (shippingType) {
192     case WebCore::ApplePaySessionPaymentRequest::ShippingType::Shipping:
193         return PKShippingTypeShipping;
194
195     case WebCore::ApplePaySessionPaymentRequest::ShippingType::Delivery:
196         return PKShippingTypeDelivery;
197
198     case WebCore::ApplePaySessionPaymentRequest::ShippingType::StorePickup:
199         return PKShippingTypeStorePickup;
200
201     case WebCore::ApplePaySessionPaymentRequest::ShippingType::ServicePickup:
202         return PKShippingTypeServicePickup;
203     }
204 }
205
206 PKShippingMethod *toPKShippingMethod(const WebCore::ApplePaySessionPaymentRequest::ShippingMethod& shippingMethod)
207 {
208     PKShippingMethod *result = [PAL::getPKShippingMethodClass() summaryItemWithLabel:shippingMethod.label amount:toDecimalNumber(shippingMethod.amount)];
209     [result setIdentifier:shippingMethod.identifier];
210     [result setDetail:shippingMethod.detail];
211
212     return result;
213 }
214     
215 #if HAVE(PASSKIT_GRANULAR_ERRORS)
216 static RetainPtr<NSSet> toNSSet(const Vector<String>& strings)
217 {
218     if (strings.isEmpty())
219         return nil;
220
221     auto mutableSet = adoptNS([[NSMutableSet alloc] initWithCapacity:strings.size()]);
222     for (auto& string : strings)
223         [mutableSet addObject:string];
224
225     return WTFMove(mutableSet);
226 }
227
228 static PKPaymentRequestAPIType toAPIType(WebCore::ApplePaySessionPaymentRequest::Requester requester)
229 {
230     switch (requester) {
231     case WebCore::ApplePaySessionPaymentRequest::Requester::ApplePayJS:
232         return PKPaymentRequestAPITypeWebJS;
233     case WebCore::ApplePaySessionPaymentRequest::Requester::PaymentRequest:
234         return PKPaymentRequestAPITypeWebPaymentRequest;
235     }
236 }
237 #endif
238
239 RetainPtr<PKPaymentRequest> WebPaymentCoordinatorProxy::platformPaymentRequest(const URL& originatingURL, const Vector<URL>& linkIconURLs, PAL::SessionID sessionID, const WebCore::ApplePaySessionPaymentRequest& paymentRequest)
240 {
241     auto result = adoptNS([PAL::allocPKPaymentRequestInstance() init]);
242
243     [result setOriginatingURL:originatingURL];
244
245     if ([result respondsToSelector:@selector(setThumbnailURLs:)]) {
246         auto thumbnailURLs = adoptNS([[NSMutableArray alloc] init]);
247         for (auto& linkIconURL : linkIconURLs)
248             [thumbnailURLs addObject:static_cast<NSURL *>(linkIconURL)];
249
250         [result setThumbnailURLs:thumbnailURLs.get()];
251     } else if (!linkIconURLs.isEmpty())
252         [result setThumbnailURL:linkIconURLs[0]];
253
254 #if HAVE(PASSKIT_GRANULAR_ERRORS)
255     [result setAPIType:toAPIType(paymentRequest.requester())];
256 #endif
257
258     [result setCountryCode:paymentRequest.countryCode()];
259     [result setCurrencyCode:paymentRequest.currencyCode()];
260     [result setBillingContact:paymentRequest.billingContact().pkContact()];
261     [result setShippingContact:paymentRequest.shippingContact().pkContact()];
262 #if HAVE(PASSKIT_GRANULAR_ERRORS)
263     [result setRequiredBillingContactFields:toPKContactFields(paymentRequest.requiredBillingContactFields()).get()];
264     [result setRequiredShippingContactFields:toPKContactFields(paymentRequest.requiredShippingContactFields()).get()];
265 #else
266     [result setRequiredBillingAddressFields:toPKAddressField(paymentRequest.requiredBillingContactFields())];
267     [result setRequiredShippingAddressFields:toPKAddressField(paymentRequest.requiredShippingContactFields())];
268 #endif
269
270     [result setSupportedNetworks:toSupportedNetworks(paymentRequest.supportedNetworks()).get()];
271     [result setMerchantCapabilities:toPKMerchantCapabilities(paymentRequest.merchantCapabilities())];
272
273     [result setShippingType:toPKShippingType(paymentRequest.shippingType())];
274
275     auto shippingMethods = adoptNS([[NSMutableArray alloc] init]);
276     for (auto& shippingMethod : paymentRequest.shippingMethods())
277         [shippingMethods addObject:toPKShippingMethod(shippingMethod)];
278     [result setShippingMethods:shippingMethods.get()];
279
280     auto paymentSummaryItems = adoptNS([[NSMutableArray alloc] init]);
281     for (auto& lineItem : paymentRequest.lineItems()) {
282         if (PKPaymentSummaryItem *summaryItem = toPKPaymentSummaryItem(lineItem))
283             [paymentSummaryItems addObject:summaryItem];
284     }
285
286     if (PKPaymentSummaryItem *totalItem = toPKPaymentSummaryItem(paymentRequest.total()))
287         [paymentSummaryItems addObject:totalItem];
288
289     [result setPaymentSummaryItems:paymentSummaryItems.get()];
290
291     [result setExpectsMerchantSession:YES];
292
293     if (!paymentRequest.applicationData().isNull()) {
294         auto applicationData = adoptNS([[NSData alloc] initWithBase64EncodedString:paymentRequest.applicationData() options:0]);
295         [result setApplicationData:applicationData.get()];
296     }
297
298 #if HAVE(PASSKIT_GRANULAR_ERRORS)
299     [result setSupportedCountries:toNSSet(paymentRequest.supportedCountries()).get()];
300 #endif
301
302     // FIXME: Instead of using respondsToSelector, this should use a proper #if version check.
303     auto& bundleIdentifier = m_client.paymentCoordinatorSourceApplicationBundleIdentifier(*this, sessionID);
304     if (!bundleIdentifier.isEmpty() && [result respondsToSelector:@selector(setSourceApplicationBundleIdentifier:)])
305         [result setSourceApplicationBundleIdentifier:bundleIdentifier];
306
307     auto& secondaryIdentifier = m_client.paymentCoordinatorSourceApplicationSecondaryIdentifier(*this, sessionID);
308     if (!secondaryIdentifier.isEmpty() && [result respondsToSelector:@selector(setSourceApplicationSecondaryIdentifier:)])
309         [result setSourceApplicationSecondaryIdentifier:secondaryIdentifier];
310
311 #if PLATFORM(IOS_FAMILY)
312     auto& serviceType = m_client.paymentCoordinatorCTDataConnectionServiceType(*this, sessionID);
313     if (!serviceType.isEmpty() && [result respondsToSelector:@selector(setCTDataConnectionServiceType:)])
314         [result setCTDataConnectionServiceType:serviceType];
315 #endif
316
317     return result;
318 }
319
320 void WebPaymentCoordinatorProxy::platformCompletePaymentSession(const Optional<WebCore::PaymentAuthorizationResult>& result)
321 {
322     m_authorizationPresenter->completePaymentSession(result);
323 }
324
325 void WebPaymentCoordinatorProxy::platformCompleteMerchantValidation(const WebCore::PaymentMerchantSession& paymentMerchantSession)
326 {
327     m_authorizationPresenter->completeMerchantValidation(paymentMerchantSession);
328 }
329
330 void WebPaymentCoordinatorProxy::platformCompleteShippingMethodSelection(const Optional<WebCore::ShippingMethodUpdate>& update)
331 {
332     m_authorizationPresenter->completeShippingMethodSelection(update);
333 }
334
335 void WebPaymentCoordinatorProxy::platformCompleteShippingContactSelection(const Optional<WebCore::ShippingContactUpdate>& update)
336 {
337     m_authorizationPresenter->completeShippingContactSelection(update);
338 }
339
340 void WebPaymentCoordinatorProxy::platformCompletePaymentMethodSelection(const Optional<WebCore::PaymentMethodUpdate>& update)
341 {
342     m_authorizationPresenter->completePaymentMethodSelection(update);
343 }
344
345 Vector<String> WebPaymentCoordinatorProxy::platformAvailablePaymentNetworks()
346 {
347     NSArray<PKPaymentNetwork> *availableNetworks = [PAL::getPKPaymentRequestClass() availableNetworks];
348     Vector<String> result;
349     result.reserveInitialCapacity(availableNetworks.count);
350     for (PKPaymentNetwork network in availableNetworks)
351         result.uncheckedAppend(network);
352     return result;
353 }
354
355 } // namespace WebKit
356
357 #endif // ENABLE(APPLE_PAY)