[Mac] Ensure Apple Pay is unavailable when PassKit.framework is missing
[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 #if PLATFORM(MAC)
58     if (!PAL::isPassKitFrameworkAvailable())
59         return false;
60 #endif
61
62     return [PAL::getPKPaymentAuthorizationViewControllerClass() canMakePayments];
63 }
64
65 void WebPaymentCoordinatorProxy::platformCanMakePaymentsWithActiveCard(const String& merchantIdentifier, const String& domainName, PAL::SessionID sessionID, WTF::Function<void(bool)>&& completionHandler)
66 {
67 #if PLATFORM(MAC)
68     if (!PAL::isPassKitFrameworkAvailable())
69         return completionHandler(false);
70 #endif
71
72 #if HAVE(PASSKIT_GRANULAR_ERRORS)
73     PKCanMakePaymentsWithMerchantIdentifierDomainAndSourceApplication(merchantIdentifier, domainName, m_client.paymentCoordinatorSourceApplicationSecondaryIdentifier(*this, sessionID), makeBlockPtr([completionHandler = WTFMove(completionHandler)](BOOL canMakePayments, NSError *error) mutable {
74         if (error)
75             LOG_ERROR("PKCanMakePaymentsWithMerchantIdentifierAndDomain error %@", error);
76
77         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), canMakePayments] {
78             completionHandler(canMakePayments);
79         });
80     }).get());
81 #else
82     PKCanMakePaymentsWithMerchantIdentifierAndDomain(merchantIdentifier, domainName, makeBlockPtr([completionHandler = WTFMove(completionHandler)](BOOL canMakePayments, NSError *error) mutable {
83         if (error)
84             LOG_ERROR("PKCanMakePaymentsWithMerchantIdentifierAndDomain error %@", error);
85
86         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), canMakePayments] {
87             completionHandler(canMakePayments);
88         });
89     }).get());
90 #endif
91 }
92
93 void WebPaymentCoordinatorProxy::platformOpenPaymentSetup(const String& merchantIdentifier, const String& domainName, WTF::Function<void(bool)>&& completionHandler)
94 {
95 #if PLATFORM(MAC)
96     if (!PAL::isPassKitFrameworkAvailable())
97         return completionHandler(false);
98 #endif
99
100     auto passLibrary = adoptNS([PAL::allocPKPassLibraryInstance() init]);
101     [passLibrary openPaymentSetupForMerchantIdentifier:merchantIdentifier domain:domainName completion:makeBlockPtr([completionHandler = WTFMove(completionHandler)](BOOL result) mutable {
102         RunLoop::main().dispatch([completionHandler = WTFMove(completionHandler), result] {
103             completionHandler(result);
104         });
105     }).get()];
106 }
107
108 #if HAVE(PASSKIT_GRANULAR_ERRORS)
109 static RetainPtr<NSSet> toPKContactFields(const WebCore::ApplePaySessionPaymentRequest::ContactFields& contactFields)
110 {
111     Vector<NSString *> result;
112
113     if (contactFields.postalAddress)
114         result.append(PAL::get_PassKit_PKContactFieldPostalAddress());
115     if (contactFields.phone)
116         result.append(PAL::get_PassKit_PKContactFieldPhoneNumber());
117     if (contactFields.email)
118         result.append(PAL::get_PassKit_PKContactFieldEmailAddress());
119     if (contactFields.name)
120         result.append(PAL::get_PassKit_PKContactFieldName());
121     if (contactFields.phoneticName)
122         result.append(PAL::get_PassKit_PKContactFieldPhoneticName());
123
124     return adoptNS([[NSSet alloc] initWithObjects:result.data() count:result.size()]);
125 }
126 #else
127 static PKAddressField toPKAddressField(const WebCore::ApplePaySessionPaymentRequest::ContactFields& contactFields)
128 {
129     PKAddressField result = 0;
130
131     if (contactFields.postalAddress)
132         result |= PKAddressFieldPostalAddress;
133     if (contactFields.phone)
134         result |= PKAddressFieldPhone;
135     if (contactFields.email)
136         result |= PKAddressFieldEmail;
137     if (contactFields.name)
138         result |= PKAddressFieldName;
139
140     return result;
141 }
142 #endif
143
144 PKPaymentSummaryItemType toPKPaymentSummaryItemType(WebCore::ApplePaySessionPaymentRequest::LineItem::Type type)
145 {
146     switch (type) {
147     case WebCore::ApplePaySessionPaymentRequest::LineItem::Type::Final:
148         return PKPaymentSummaryItemTypeFinal;
149
150     case WebCore::ApplePaySessionPaymentRequest::LineItem::Type::Pending:
151         return PKPaymentSummaryItemTypePending;
152     }
153 }
154
155 NSDecimalNumber *toDecimalNumber(const String& amount)
156 {
157     if (!amount)
158         return [NSDecimalNumber zero];
159     return [NSDecimalNumber decimalNumberWithString:amount locale:@{ NSLocaleDecimalSeparator : @"." }];
160 }
161
162 PKPaymentSummaryItem *toPKPaymentSummaryItem(const WebCore::ApplePaySessionPaymentRequest::LineItem& lineItem)
163 {
164     return [PAL::getPKPaymentSummaryItemClass() summaryItemWithLabel:lineItem.label amount:toDecimalNumber(lineItem.amount) type:toPKPaymentSummaryItemType(lineItem.type)];
165 }
166
167 NSArray *toPKPaymentSummaryItems(const WebCore::ApplePaySessionPaymentRequest::TotalAndLineItems& totalAndLineItems)
168 {
169     NSMutableArray *paymentSummaryItems = [NSMutableArray arrayWithCapacity:totalAndLineItems.lineItems.size() + 1];
170     for (auto& lineItem : totalAndLineItems.lineItems) {
171         if (PKPaymentSummaryItem *summaryItem = toPKPaymentSummaryItem(lineItem))
172             [paymentSummaryItems addObject:summaryItem];
173     }
174
175     if (PKPaymentSummaryItem *totalItem = toPKPaymentSummaryItem(totalAndLineItems.total))
176         [paymentSummaryItems addObject:totalItem];
177
178     return paymentSummaryItems;
179 }
180
181 static PKMerchantCapability toPKMerchantCapabilities(const WebCore::ApplePaySessionPaymentRequest::MerchantCapabilities& merchantCapabilities)
182 {
183     PKMerchantCapability result = 0;
184     if (merchantCapabilities.supports3DS)
185         result |= PKMerchantCapability3DS;
186     if (merchantCapabilities.supportsEMV)
187         result |= PKMerchantCapabilityEMV;
188     if (merchantCapabilities.supportsCredit)
189         result |= PKMerchantCapabilityCredit;
190     if (merchantCapabilities.supportsDebit)
191         result |= PKMerchantCapabilityDebit;
192
193     return result;
194 }
195
196 static RetainPtr<NSArray> toSupportedNetworks(const Vector<String>& supportedNetworks)
197 {
198     auto result = adoptNS([[NSMutableArray alloc] initWithCapacity:supportedNetworks.size()]);
199     for (auto& supportedNetwork : supportedNetworks)
200         [result addObject:supportedNetwork];
201     return result;
202 }
203
204 static PKShippingType toPKShippingType(WebCore::ApplePaySessionPaymentRequest::ShippingType shippingType)
205 {
206     switch (shippingType) {
207     case WebCore::ApplePaySessionPaymentRequest::ShippingType::Shipping:
208         return PKShippingTypeShipping;
209
210     case WebCore::ApplePaySessionPaymentRequest::ShippingType::Delivery:
211         return PKShippingTypeDelivery;
212
213     case WebCore::ApplePaySessionPaymentRequest::ShippingType::StorePickup:
214         return PKShippingTypeStorePickup;
215
216     case WebCore::ApplePaySessionPaymentRequest::ShippingType::ServicePickup:
217         return PKShippingTypeServicePickup;
218     }
219 }
220
221 PKShippingMethod *toPKShippingMethod(const WebCore::ApplePaySessionPaymentRequest::ShippingMethod& shippingMethod)
222 {
223     PKShippingMethod *result = [PAL::getPKShippingMethodClass() summaryItemWithLabel:shippingMethod.label amount:toDecimalNumber(shippingMethod.amount)];
224     [result setIdentifier:shippingMethod.identifier];
225     [result setDetail:shippingMethod.detail];
226
227     return result;
228 }
229     
230 #if HAVE(PASSKIT_GRANULAR_ERRORS)
231 static RetainPtr<NSSet> toNSSet(const Vector<String>& strings)
232 {
233     if (strings.isEmpty())
234         return nil;
235
236     auto mutableSet = adoptNS([[NSMutableSet alloc] initWithCapacity:strings.size()]);
237     for (auto& string : strings)
238         [mutableSet addObject:string];
239
240     return WTFMove(mutableSet);
241 }
242
243 static PKPaymentRequestAPIType toAPIType(WebCore::ApplePaySessionPaymentRequest::Requester requester)
244 {
245     switch (requester) {
246     case WebCore::ApplePaySessionPaymentRequest::Requester::ApplePayJS:
247         return PKPaymentRequestAPITypeWebJS;
248     case WebCore::ApplePaySessionPaymentRequest::Requester::PaymentRequest:
249         return PKPaymentRequestAPITypeWebPaymentRequest;
250     }
251 }
252 #endif
253
254 RetainPtr<PKPaymentRequest> WebPaymentCoordinatorProxy::platformPaymentRequest(const URL& originatingURL, const Vector<URL>& linkIconURLs, PAL::SessionID sessionID, const WebCore::ApplePaySessionPaymentRequest& paymentRequest)
255 {
256     auto result = adoptNS([PAL::allocPKPaymentRequestInstance() init]);
257
258     [result setOriginatingURL:originatingURL];
259
260     if ([result respondsToSelector:@selector(setThumbnailURLs:)]) {
261         auto thumbnailURLs = adoptNS([[NSMutableArray alloc] init]);
262         for (auto& linkIconURL : linkIconURLs)
263             [thumbnailURLs addObject:static_cast<NSURL *>(linkIconURL)];
264
265         [result setThumbnailURLs:thumbnailURLs.get()];
266     } else if (!linkIconURLs.isEmpty())
267         [result setThumbnailURL:linkIconURLs[0]];
268
269 #if HAVE(PASSKIT_GRANULAR_ERRORS)
270     [result setAPIType:toAPIType(paymentRequest.requester())];
271 #endif
272
273     [result setCountryCode:paymentRequest.countryCode()];
274     [result setCurrencyCode:paymentRequest.currencyCode()];
275     [result setBillingContact:paymentRequest.billingContact().pkContact()];
276     [result setShippingContact:paymentRequest.shippingContact().pkContact()];
277 #if HAVE(PASSKIT_GRANULAR_ERRORS)
278     [result setRequiredBillingContactFields:toPKContactFields(paymentRequest.requiredBillingContactFields()).get()];
279     [result setRequiredShippingContactFields:toPKContactFields(paymentRequest.requiredShippingContactFields()).get()];
280 #else
281     [result setRequiredBillingAddressFields:toPKAddressField(paymentRequest.requiredBillingContactFields())];
282     [result setRequiredShippingAddressFields:toPKAddressField(paymentRequest.requiredShippingContactFields())];
283 #endif
284
285     [result setSupportedNetworks:toSupportedNetworks(paymentRequest.supportedNetworks()).get()];
286     [result setMerchantCapabilities:toPKMerchantCapabilities(paymentRequest.merchantCapabilities())];
287
288     [result setShippingType:toPKShippingType(paymentRequest.shippingType())];
289
290     auto shippingMethods = adoptNS([[NSMutableArray alloc] init]);
291     for (auto& shippingMethod : paymentRequest.shippingMethods())
292         [shippingMethods addObject:toPKShippingMethod(shippingMethod)];
293     [result setShippingMethods:shippingMethods.get()];
294
295     auto paymentSummaryItems = adoptNS([[NSMutableArray alloc] init]);
296     for (auto& lineItem : paymentRequest.lineItems()) {
297         if (PKPaymentSummaryItem *summaryItem = toPKPaymentSummaryItem(lineItem))
298             [paymentSummaryItems addObject:summaryItem];
299     }
300
301     if (PKPaymentSummaryItem *totalItem = toPKPaymentSummaryItem(paymentRequest.total()))
302         [paymentSummaryItems addObject:totalItem];
303
304     [result setPaymentSummaryItems:paymentSummaryItems.get()];
305
306     [result setExpectsMerchantSession:YES];
307
308     if (!paymentRequest.applicationData().isNull()) {
309         auto applicationData = adoptNS([[NSData alloc] initWithBase64EncodedString:paymentRequest.applicationData() options:0]);
310         [result setApplicationData:applicationData.get()];
311     }
312
313 #if HAVE(PASSKIT_GRANULAR_ERRORS)
314     [result setSupportedCountries:toNSSet(paymentRequest.supportedCountries()).get()];
315 #endif
316
317     // FIXME: Instead of using respondsToSelector, this should use a proper #if version check.
318     auto& bundleIdentifier = m_client.paymentCoordinatorSourceApplicationBundleIdentifier(*this, sessionID);
319     if (!bundleIdentifier.isEmpty() && [result respondsToSelector:@selector(setSourceApplicationBundleIdentifier:)])
320         [result setSourceApplicationBundleIdentifier:bundleIdentifier];
321
322     auto& secondaryIdentifier = m_client.paymentCoordinatorSourceApplicationSecondaryIdentifier(*this, sessionID);
323     if (!secondaryIdentifier.isEmpty() && [result respondsToSelector:@selector(setSourceApplicationSecondaryIdentifier:)])
324         [result setSourceApplicationSecondaryIdentifier:secondaryIdentifier];
325
326 #if PLATFORM(IOS_FAMILY)
327     auto& serviceType = m_client.paymentCoordinatorCTDataConnectionServiceType(*this, sessionID);
328     if (!serviceType.isEmpty() && [result respondsToSelector:@selector(setCTDataConnectionServiceType:)])
329         [result setCTDataConnectionServiceType:serviceType];
330 #endif
331
332     return result;
333 }
334
335 void WebPaymentCoordinatorProxy::platformCompletePaymentSession(const Optional<WebCore::PaymentAuthorizationResult>& result)
336 {
337     m_authorizationPresenter->completePaymentSession(result);
338 }
339
340 void WebPaymentCoordinatorProxy::platformCompleteMerchantValidation(const WebCore::PaymentMerchantSession& paymentMerchantSession)
341 {
342     m_authorizationPresenter->completeMerchantValidation(paymentMerchantSession);
343 }
344
345 void WebPaymentCoordinatorProxy::platformCompleteShippingMethodSelection(const Optional<WebCore::ShippingMethodUpdate>& update)
346 {
347     m_authorizationPresenter->completeShippingMethodSelection(update);
348 }
349
350 void WebPaymentCoordinatorProxy::platformCompleteShippingContactSelection(const Optional<WebCore::ShippingContactUpdate>& update)
351 {
352     m_authorizationPresenter->completeShippingContactSelection(update);
353 }
354
355 void WebPaymentCoordinatorProxy::platformCompletePaymentMethodSelection(const Optional<WebCore::PaymentMethodUpdate>& update)
356 {
357     m_authorizationPresenter->completePaymentMethodSelection(update);
358 }
359
360 Vector<String> WebPaymentCoordinatorProxy::platformAvailablePaymentNetworks()
361 {
362 #if PLATFORM(MAC)
363     if (!PAL::isPassKitFrameworkAvailable())
364         return { };
365 #endif
366
367     NSArray<PKPaymentNetwork> *availableNetworks = [PAL::getPKPaymentRequestClass() availableNetworks];
368     Vector<String> result;
369     result.reserveInitialCapacity(availableNetworks.count);
370     for (PKPaymentNetwork network in availableNetworks)
371         result.uncheckedAppend(network);
372     return result;
373 }
374
375 } // namespace WebKit
376
377 #endif // ENABLE(APPLE_PAY)