/* * Copyright (C) 2019 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "PaymentInstallmentConfigurationWebCore.h" #if HAVE(PASSKIT_INSTALLMENTS) #import "ApplePayInstallmentConfigurationWebCore.h" #import "ApplePayInstallmentItemType.h" #import "ApplePayInstallmentRetailChannel.h" #import "ExceptionOr.h" #import #import #import namespace WebCore { // FIXME: Merge with toDecimalNumber() in WebPaymentCoordinatorProxyCocoa. static NSDecimalNumber *toDecimalNumber(const String& amount) { if (!amount) return [NSDecimalNumber zero]; return [NSDecimalNumber decimalNumberWithString:amount locale:@{ NSLocaleDecimalSeparator : @"." }]; } static String fromDecimalNumber(NSDecimalNumber *number) { auto numberFormatter = adoptNS([[NSNumberFormatter alloc] init]); [numberFormatter setNumberStyle:NSNumberFormatterNoStyle]; [numberFormatter setMinimumIntegerDigits:1]; [numberFormatter setMinimumFractionDigits:2]; [numberFormatter setMaximumFractionDigits:[numberFormatter maximumIntegerDigits]]; return [numberFormatter stringFromNumber:number]; } static std::optional applePaySetupFeatureType(PKPaymentSetupFeatureType featureType) { switch (featureType) { case PKPaymentSetupFeatureTypeApplePay: return ApplePaySetupFeatureType::ApplePay; case PKPaymentSetupFeatureTypeAppleCard: return ApplePaySetupFeatureType::AppleCard; default: ASSERT_NOT_REACHED(); return std::nullopt; } } static PKPaymentSetupFeatureType platformFeatureType(ApplePaySetupFeatureType featureType) { switch (featureType) { case ApplePaySetupFeatureType::ApplePay: return PKPaymentSetupFeatureTypeApplePay; case ApplePaySetupFeatureType::AppleCard: return PKPaymentSetupFeatureTypeAppleCard; } } static ApplePayInstallmentItemType applePayItemType(PKInstallmentItemType itemType) { switch (itemType) { case PKInstallmentItemTypeGeneric: return ApplePayInstallmentItemType::Generic; case PKInstallmentItemTypePhone: return ApplePayInstallmentItemType::Phone; case PKInstallmentItemTypePad: return ApplePayInstallmentItemType::Pad; case PKInstallmentItemTypeWatch: return ApplePayInstallmentItemType::Watch; case PKInstallmentItemTypeMac: return ApplePayInstallmentItemType::Mac; } } static PKInstallmentItemType platformItemType(ApplePayInstallmentItemType itemType) { switch (itemType) { case ApplePayInstallmentItemType::Generic: return PKInstallmentItemTypeGeneric; case ApplePayInstallmentItemType::Phone: return PKInstallmentItemTypePhone; case ApplePayInstallmentItemType::Pad: return PKInstallmentItemTypePad; case ApplePayInstallmentItemType::Watch: return PKInstallmentItemTypeWatch; case ApplePayInstallmentItemType::Mac: return PKInstallmentItemTypeMac; } } static ApplePayInstallmentRetailChannel applePayRetailChannel(PKInstallmentRetailChannel retailChannel) { switch (retailChannel) { case PKInstallmentRetailChannelUnknown: return ApplePayInstallmentRetailChannel::Unknown; case PKInstallmentRetailChannelApp: return ApplePayInstallmentRetailChannel::App; case PKInstallmentRetailChannelWeb: return ApplePayInstallmentRetailChannel::Web; case PKInstallmentRetailChannelInStore: return ApplePayInstallmentRetailChannel::InStore; } } static PKInstallmentRetailChannel platformRetailChannel(ApplePayInstallmentRetailChannel retailChannel) { switch (retailChannel) { case ApplePayInstallmentRetailChannel::Unknown: return PKInstallmentRetailChannelUnknown; case ApplePayInstallmentRetailChannel::App: return PKInstallmentRetailChannelApp; case ApplePayInstallmentRetailChannel::Web: return PKInstallmentRetailChannelWeb; case ApplePayInstallmentRetailChannel::InStore: return PKInstallmentRetailChannelInStore; } } static RetainPtr makeNSArrayElement(const ApplePayInstallmentItem& item) { ASSERT(PAL::getPKPaymentInstallmentItemClass()); auto installmentItem = adoptNS([PAL::allocPKPaymentInstallmentItemInstance() init]); [installmentItem setInstallmentItemType:platformItemType(item.type)]; [installmentItem setAmount:toDecimalNumber(item.amount)]; [installmentItem setCurrencyCode:item.currencyCode]; [installmentItem setProgramIdentifier:item.programIdentifier]; [installmentItem setApr:toDecimalNumber(item.apr)]; [installmentItem setProgramTerms:item.programTerms]; return installmentItem; } static std::optional makeVectorElement(const ApplePayInstallmentItem*, id arrayElement) { if (![arrayElement isKindOfClass:PAL::getPKPaymentInstallmentItemClass()]) return std::nullopt; PKPaymentInstallmentItem *item = arrayElement; return ApplePayInstallmentItem { applePayItemType([item installmentItemType]), fromDecimalNumber([item amount]), [item currencyCode], [item programIdentifier], fromDecimalNumber([item apr]), [item programTerms], }; } static RetainPtr createPlatformConfiguration(const ApplePayInstallmentConfiguration& coreConfiguration, NSDictionary *applicationMetadata) { if (!PAL::getPKPaymentInstallmentConfigurationClass()) return nil; auto configuration = adoptNS([PAL::allocPKPaymentInstallmentConfigurationInstance() init]); [configuration setFeature:platformFeatureType(coreConfiguration.featureType)]; [configuration setBindingTotalAmount:toDecimalNumber(coreConfiguration.bindingTotalAmount)]; [configuration setCurrencyCode:coreConfiguration.currencyCode]; [configuration setInStorePurchase:coreConfiguration.isInStorePurchase]; [configuration setOpenToBuyThresholdAmount:toDecimalNumber(coreConfiguration.openToBuyThresholdAmount)]; auto merchandisingImageData = adoptNS([[NSData alloc] initWithBase64EncodedString:coreConfiguration.merchandisingImageData options:0]); [configuration setMerchandisingImageData:merchandisingImageData.get()]; [configuration setInstallmentMerchantIdentifier:coreConfiguration.merchantIdentifier]; [configuration setReferrerIdentifier:coreConfiguration.referrerIdentifier]; if (!PAL::getPKPaymentInstallmentItemClass()) return configuration; [configuration setInstallmentItems:createNSArray(coreConfiguration.items).get()]; [configuration setApplicationMetadata:applicationMetadata]; [configuration setRetailChannel:platformRetailChannel(coreConfiguration.retailChannel)]; return configuration; } ExceptionOr PaymentInstallmentConfiguration::create(const ApplePayInstallmentConfiguration& configuration) { NSDictionary *applicationMetadataDictionary = nil; if (!configuration.applicationMetadata.isNull()) { NSData *applicationMetadata = [configuration.applicationMetadata dataUsingEncoding:NSUTF8StringEncoding]; applicationMetadataDictionary = dynamic_objc_cast([NSJSONSerialization JSONObjectWithData:applicationMetadata options:0 error:nil]); if (!applicationMetadataDictionary) return Exception { TypeError, "applicationMetadata must be a JSON object"_s }; } return PaymentInstallmentConfiguration(configuration, applicationMetadataDictionary); } PaymentInstallmentConfiguration::PaymentInstallmentConfiguration(const ApplePayInstallmentConfiguration& configuration, NSDictionary *applicationMetadata) : m_configuration { createPlatformConfiguration(configuration, applicationMetadata) } { } PaymentInstallmentConfiguration::PaymentInstallmentConfiguration(RetainPtr&& configuration) : m_configuration { WTFMove(configuration) } { } PKPaymentInstallmentConfiguration *PaymentInstallmentConfiguration::platformConfiguration() const { return m_configuration.get(); } ApplePayInstallmentConfiguration PaymentInstallmentConfiguration::applePayInstallmentConfiguration() const { ApplePayInstallmentConfiguration installmentConfiguration; if (!PAL::getPKPaymentInstallmentConfigurationClass()) return installmentConfiguration; if (auto featureType = applePaySetupFeatureType([m_configuration feature])) installmentConfiguration.featureType = *featureType; else return installmentConfiguration; installmentConfiguration.bindingTotalAmount = fromDecimalNumber([m_configuration bindingTotalAmount]); installmentConfiguration.currencyCode = [m_configuration currencyCode]; installmentConfiguration.isInStorePurchase = [m_configuration isInStorePurchase]; installmentConfiguration.openToBuyThresholdAmount = fromDecimalNumber([m_configuration openToBuyThresholdAmount]); installmentConfiguration.merchandisingImageData = [[m_configuration merchandisingImageData] base64EncodedStringWithOptions:0]; installmentConfiguration.merchantIdentifier = [m_configuration installmentMerchantIdentifier]; installmentConfiguration.referrerIdentifier = [m_configuration referrerIdentifier]; if (!PAL::getPKPaymentInstallmentItemClass()) return installmentConfiguration; RetainPtr applicationMetadataString; if (NSDictionary *applicationMetadataDictionary = [m_configuration applicationMetadata]) { if (NSData *applicationMetadata = [NSJSONSerialization dataWithJSONObject:applicationMetadataDictionary options:NSJSONWritingSortedKeys error:nil]) applicationMetadataString = adoptNS([[NSString alloc] initWithData:applicationMetadata encoding:NSUTF8StringEncoding]); } installmentConfiguration.items = makeVector([m_configuration installmentItems]); installmentConfiguration.applicationMetadata = applicationMetadataString.get(); installmentConfiguration.retailChannel = applePayRetailChannel([m_configuration retailChannel]); return installmentConfiguration; } } // namespace WebCore #endif // HAVE(PASSKIT_INSTALLMENTS)