Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Update DX for Apple Pay #236

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ target 'example_0_70_6' do
pod 'PrimerSDK'
pod 'Primer3DS'
pod 'PrimerIPay88MYSDK'
pod 'PrimerKlarnaSDK', '1.1.0'
pod 'PrimerKlarnaSDK', '1.1.1'
pod 'PrimerStripeSDK', '1.0.0'

target 'example_0_70_6Tests' do
Expand Down
17 changes: 8 additions & 9 deletions packages/example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ PODS:
- React-Core
- Primer3DS (2.3.2)
- PrimerIPay88MYSDK (0.1.7)
- PrimerKlarnaSDK (1.1.0)
- PrimerKlarnaSDK (1.1.1)
- PrimerSDK (2.31.0):
- PrimerSDK/Core (= 2.31.0)
- PrimerSDK/Core (2.31.0)
Expand Down Expand Up @@ -291,7 +291,7 @@ PODS:
- React-jsinspector (0.72.0)
- React-logger (0.72.0):
- glog
- react-native-safe-area-context (4.10.1):
- react-native-safe-area-context (4.8.1):
- React-Core
- react-native-segmented-control (2.4.3):
- React-Core
Expand Down Expand Up @@ -400,10 +400,9 @@ PODS:
- React-jsi (= 0.72.0)
- React-logger (= 0.72.0)
- React-perflogger (= 0.72.0)
- RNScreens (3.31.1):
- RNScreens (3.29.0):
- RCT-Folly (= 2021.07.22.00)
- React-Core
- React-RCTImage
- SocketRocket (0.6.0)
- Yoga (1.14.0)

Expand All @@ -416,7 +415,7 @@ DEPENDENCIES:
- primer-io-react-native (from `../../sdk`)
- Primer3DS
- PrimerIPay88MYSDK
- PrimerKlarnaSDK (= 1.1.0)
- PrimerKlarnaSDK (= 1.1.1)
- PrimerSDK
- PrimerStripeSDK (= 1.0.0)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
Expand Down Expand Up @@ -563,7 +562,7 @@ SPEC CHECKSUMS:
primer-io-react-native: 4b4eb3b62c7787537191a6f74598db8a78c29587
Primer3DS: 81e7969033230c7346a517cd609be956914738bb
PrimerIPay88MYSDK: 436ee0be7e2c97e4e81456ccddee20175e9e3c4d
PrimerKlarnaSDK: 83e9a1357a7247bf8fa2836fc945cf97644d601d
PrimerKlarnaSDK: 564105170cc7b467bf95c31851813ea41c468f8b
PrimerSDK: 78d1a48ba9b4677f78da9750448af6c72007f4fe
PrimerStripeSDK: c37d4e7c1b5256d67d4890c4cc4b38ddc9427489
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1
Expand All @@ -581,7 +580,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: de4e98ab0f2661affeb54931806a79a93a34d1ea
React-jsinspector: 8d754fc957255a29d93e52fc67a895045cdc8703
React-logger: 454ffb01980778a43b0153ee98721d0275b56616
react-native-safe-area-context: dcab599c527c2d7de2d76507a523d20a0b83823d
react-native-safe-area-context: cd1169d797a2ef722a00bfc5af10748d5b6c94f9
react-native-segmented-control: d88b35fcd06c0c8e1f1f7ed1eb066c2b02570fdd
React-NativeModulesApple: 1d81d927ef1a67a3545a01e14c2e98500bf9b199
React-perflogger: 684a11499a0589cc42135d6d5cc04d0e4e0e261a
Expand All @@ -600,10 +599,10 @@ SPEC CHECKSUMS:
React-runtimescheduler: 4a36521cc1ec1bc3997ae2462b6779dadaae376b
React-utils: c12d2e75c8bbc727939ddc4319ed95493395ed5a
ReactCommon: b9547f82aed45eccc1aa59034dc6c72809e37000
RNScreens: 93ae3be2f119d955620f9bbb39ad372adb53b7a9
RNScreens: 3c5b9f4a9dcde752466854b6109b79c0e205dad3
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
Yoga: 1d6727ed193122f6adaf435c3de1a768326ff83b

PODFILE CHECKSUM: e1d93bf98428be80f71d4652c0496e390624cf68
PODFILE CHECKSUM: 4505e915c8982e98dcb8297a1c627233ce3a067f

COCOAPODS: 1.15.2
132 changes: 66 additions & 66 deletions packages/example/ios/example_0_70_6.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions packages/example/src/screens/CheckoutScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,8 @@ const CheckoutScreen = (props: any) => {
merchantName: appPaymentParameters.merchantName || "Merchant name",
isCaptureBillingAddressEnabled: true,
showApplePayForUnsupportedDevice: true,
checkProvidedNetworks: false
checkProvidedNetworks: false,
shippingOptions: appPaymentParameters.shippingOptions
},
googlePayOptions: {
isCaptureBillingAddressEnabled: true,
Expand Down Expand Up @@ -321,14 +322,6 @@ const CheckoutScreen = (props: any) => {
}
};

if (appPaymentParameters.merchantName) {
//@ts-ignore
settings.paymentMethodOptions.applePayOptions = {
merchantIdentifier: 'merchant.checkout.team',
merchantName: appPaymentParameters.merchantName
};
}

const onVaultManagerButtonTapped = async () => {
try {
setIsLoading(true);
Expand Down
103 changes: 103 additions & 0 deletions packages/example/src/screens/SettingsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { styles } from '../styles';
import SegmentedControl from '@react-native-segmented-control/segmented-control';
import { Environment, makeEnvironmentFromIntVal, makePaymentHandlingFromIntVal, PaymentHandling } from '../network/Environment';
import { appPaymentParameters, IClientSessionAddress, IClientSessionCustomer, IClientSessionLineItem, IClientSessionOrder, IClientSessionPaymentMethod, IClientSessionPaymentMethodOptions, IClientSessionRequestBody } from '../models/IClientSessionRequestBody';
import { useState } from 'react';
import { Switch } from 'react-native';
import TextField from '../components/TextField';
import type { NewLineItemScreenProps } from './NewLineItemSreen';
Expand All @@ -22,6 +23,11 @@ export interface AppPaymentParameters {
paymentHandling: PaymentHandling;
clientSessionRequestBody: IClientSessionRequestBody;
merchantName?: string;
shippingOptions?: {
isCaptureShippingAddressEnabled: boolean;
requireShippingMethod: boolean;
additionalShippingContactFields: string[];
};
}

export let customApiKey: string | undefined;
Expand Down Expand Up @@ -66,6 +72,11 @@ const SettingsScreen = ({ navigation }) => {
const [visaSurcharge, setVisaSurcharge] = React.useState<number | undefined>(appPaymentParameters.clientSessionRequestBody.paymentMethod?.options?.PAYMENT_CARD?.networks.VISA?.surcharge.amount);
const [masterCardSurcharge, setMasterCardSurcharge] = React.useState<number | undefined>(appPaymentParameters.clientSessionRequestBody.paymentMethod?.options?.PAYMENT_CARD?.networks.MASTERCARD?.surcharge.amount);

const [isShippingOptionsApplied, setIsShippingOptionsApplied] = useState<boolean>(false);
const [isCaptureShippingAddressEnabled, setIsCaptureShippingAddressEnabled] = useState<boolean>(false);
const [requireShippingMethod, setRequireShippingMethod] = useState<boolean>(false);
const [additionalShippingContactFields, setAdditionalShippingContactFields] = useState<string[]>([]);

const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.black : Colors.white
};
Expand Down Expand Up @@ -617,6 +628,7 @@ const SettingsScreen = ({ navigation }) => {
{renderMerchantSection()}
{renderCustomerSection()}
{renderSurchargeSection()}
{renderShippingOptionsSection()}
</View>
);
}
Expand Down Expand Up @@ -674,6 +686,91 @@ const SettingsScreen = ({ navigation }) => {
);
}

const renderShippingOptionsSection = () => {
return (
<View style={{ marginTop: 12, marginBottom: 8 }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ ...styles.heading1, marginBottom: 4 }}>
Shipping Options
</Text>
<View style={{ flex: 1 }} />
<Switch
value={isShippingOptionsApplied}
onValueChange={val => {
setIsShippingOptionsApplied(val);
}}
/>
</View>

{
!isShippingOptionsApplied ? null :
<View>
<View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 4 }}>
<Text style={{ flex: 1 }}>Capture Shipping Address Enabled</Text>
<Switch
value={isCaptureShippingAddressEnabled}
onValueChange={val => {
setIsCaptureShippingAddressEnabled(val);
}}
/>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 4 }}>
<Text style={{ flex: 1 }}>Require Shipping Method</Text>
<Switch
value={requireShippingMethod}
onValueChange={val => {
setRequireShippingMethod(val);
}}
/>
</View>
<Text style={{ ...styles.heading2, marginVertical: 4 }}>Additional Shipping Contact Fields</Text>
<View style={{ marginVertical: 4 }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ flex: 1 }}>Name</Text>
<Switch
value={additionalShippingContactFields.includes('name')}
onValueChange={val => {
if (val) {
setAdditionalShippingContactFields([...additionalShippingContactFields, 'name']);
} else {
setAdditionalShippingContactFields(additionalShippingContactFields.filter(field => field !== 'name'));
}
}}
/>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ flex: 1 }}>Email Address</Text>
<Switch
value={additionalShippingContactFields.includes('emailAddress')}
onValueChange={val => {
if (val) {
setAdditionalShippingContactFields([...additionalShippingContactFields, 'emailAddress']);
} else {
setAdditionalShippingContactFields(additionalShippingContactFields.filter(field => field !== 'emailAddress'));
}
}}
/>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ flex: 1 }}>Phone Number</Text>
<Switch
value={additionalShippingContactFields.includes('phoneNumber')}
onValueChange={val => {
if (val) {
setAdditionalShippingContactFields([...additionalShippingContactFields, 'phoneNumber']);
} else {
setAdditionalShippingContactFields(additionalShippingContactFields.filter(field => field !== 'phoneNumber'));
}
}}
/>
</View>
</View>
</View>
}
</View>
);
};

const updateAppPaymentParameters = () => {
appPaymentParameters.merchantName = merchantName;
appPaymentParameters.environment = environment;
Expand Down Expand Up @@ -779,6 +876,12 @@ const SettingsScreen = ({ navigation }) => {
currentClientSessionRequestBody.paymentMethod = Object.keys(currentPaymentMethod).length === 0 ? undefined : currentPaymentMethod;

appPaymentParameters.clientSessionRequestBody = currentClientSessionRequestBody;
appPaymentParameters.shippingOptions = isShippingOptionsApplied ? {
isCaptureShippingAddressEnabled: isCaptureShippingAddressEnabled,
requireShippingMethod: requireShippingMethod,
additionalShippingContactFields: additionalShippingContactFields,
} : undefined;

}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,51 @@

var applePayOptions: PrimerApplePayOptions?
if let rnApplePayOptions = ((settingsJson["paymentMethodOptions"] as? [String: Any])?["applePayOptions"] as? [String: Any]),
let rnApplePayMerchantIdentifier = rnApplePayOptions["merchantIdentifier"] as? String,
let rnApplePayMerchantName = rnApplePayOptions["merchantName"] as? String
let rnApplePayMerchantIdentifier = rnApplePayOptions["merchantIdentifier"] as? String
{
let rnApplePayMerchantName = rnApplePayOptions["merchantName"] as? String
let rnApplePayIsCaptureBillingAddressEnabled = (rnApplePayOptions["isCaptureBillingAddressEnabled"] as? Bool) ?? false
let rnApplePayShowApplePayForUnsupportedDevice = (rnApplePayOptions["showApplePayForUnsupportedDevice"] as? Bool) ?? true
let rnApplePayCheckProvidedNetworks = (rnApplePayOptions["checkProvidedNetworks"] as? Bool) ?? true

var shippingOptions: PrimerApplePayOptions.ShippingOptions? = nil

Check failure on line 44 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

'ShippingOptions' is not a member type of class 'PrimerSDK.PrimerApplePayOptions'

Check failure on line 44 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

'ShippingOptions' is not a member type of class 'PrimerSDK.PrimerApplePayOptions'

Check failure on line 44 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Build and upload app to Appetize 🚀

'ShippingOptions' is not a member type of class 'PrimerSDK.PrimerApplePayOptions'
if let rnShippingOptions = rnApplePayOptions["shippingOptions"] as? [String: Any] {
let isCaptureShippingAddressEnabled = rnShippingOptions["isCaptureShippingAddressEnabled"] as? Bool ?? false
let requireShippingMethod = rnShippingOptions["requireShippingMethod"] as? Bool ?? false

var additionalShippingContactFields: [PrimerApplePayOptions.ShippingOptions.AdditionalShippingContactField]? = nil

Check failure on line 49 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

'ShippingOptions' is not a member type of class 'PrimerSDK.PrimerApplePayOptions'

Check failure on line 49 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

'ShippingOptions' is not a member type of class 'PrimerSDK.PrimerApplePayOptions'

Check failure on line 49 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Build and upload app to Appetize 🚀

'ShippingOptions' is not a member type of class 'PrimerSDK.PrimerApplePayOptions'
if let additionalFieldsStrings = rnShippingOptions["additionalShippingContactFields"] as? [String] {
additionalShippingContactFields = additionalFieldsStrings.compactMap { fieldStr in
switch fieldStr {
case "name":
return .name

Check failure on line 54 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

cannot infer contextual base in reference to member 'name'

Check failure on line 54 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

cannot infer contextual base in reference to member 'name'

Check failure on line 54 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Build and upload app to Appetize 🚀

cannot infer contextual base in reference to member 'name'
case "emailAddress":
return .emailAddress
case "phoneNumber":
return .phoneNumber
default:
return nil // Ignore unknown values or handle accordingly
}
}
}

shippingOptions = PrimerApplePayOptions.ShippingOptions(

Check failure on line 65 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

type 'PrimerApplePayOptions' has no member 'ShippingOptions'

Check failure on line 65 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

type 'PrimerApplePayOptions' has no member 'ShippingOptions'

Check failure on line 65 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Build and upload app to Appetize 🚀

type 'PrimerApplePayOptions' has no member 'ShippingOptions'
isCaptureShippingAddressEnabled: isCaptureShippingAddressEnabled,
additionalShippingContactFields: additionalShippingContactFields,
requireShippingMethod: requireShippingMethod
)
}

applePayOptions = PrimerApplePayOptions(
merchantIdentifier: rnApplePayMerchantIdentifier,
merchantName: rnApplePayMerchantName,
isCaptureBillingAddressEnabled: rnApplePayIsCaptureBillingAddressEnabled,
showApplePayForUnsupportedDevice: rnApplePayShowApplePayForUnsupportedDevice,
checkProvidedNetworks: rnApplePayCheckProvidedNetworks)
checkProvidedNetworks: rnApplePayCheckProvidedNetworks,
shippingOptions: shippingOptions

Check failure on line 78 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

extra argument 'shippingOptions' in call

Check failure on line 78 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Run iOS unit tests

extra argument 'shippingOptions' in call

Check failure on line 78 in packages/sdk/ios/Sources/DataModels/PrimerSettings+Extensions.swift

View workflow job for this annotation

GitHub Actions / Build and upload app to Appetize 🚀

extra argument 'shippingOptions' in call
)
}


var klarnaOptions: PrimerKlarnaOptions?
if let rnKlarnaRecurringPaymentDescription = ((settingsJson["paymentMethodOptions"] as? [String: Any])?["klarnaOptions"] as? [String: Any])?["recurringPaymentDescription"] as? String {
Expand Down
21 changes: 20 additions & 1 deletion packages/sdk/src/models/PrimerSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,30 @@ interface IPrimerApayaOptions {

interface IPrimerApplePayOptions {
merchantIdentifier: string;
merchantName: string;
/**
* @deprecated Use Client Session API to provide merchant name value: https://primer.io/docs/payment-methods/apple-pay/direct-integration#prepare-the-client-session
*/
merchantName?: string;
isCaptureBillingAddressEnabled?: boolean;
/**
* If you don't want to present the Apple Pay option when the device doesn't support it, set this to `false`.
* Default value is `true`.
*/
showApplePayForUnsupportedDevice?: boolean;
/**
* Due to reports about the Apple Pay flow not presenting because `canMakePayments(usingNetworks:)` was returning false
* when there were no cards in the Wallet, we introduced this flag to continue supporting the old behavior.
* Default value is `true`.
*/
checkProvidedNetworks?: boolean;
shippingOptions?: IShippingOptions;
}
interface IShippingOptions {
isCaptureShippingAddressEnabled: boolean;
additionalShippingContactFields?: AdditionalShippingContactField[];
requireShippingMethod: boolean;
}
type AdditionalShippingContactField = 'name' | 'emailAddress' | 'phoneNumber';

interface IPrimerCardPaymentOptions {
is3DSOnVaultingEnabled: boolean;
Expand Down
Loading