Skip to content

Commit f0f8bb9

Browse files
feat: add paymentInstruments and payment delegation
1 parent f153c9b commit f0f8bb9

File tree

7 files changed

+121
-71
lines changed

7 files changed

+121
-71
lines changed

modules/@shopify/checkout-sheet-kit/src/components/Checkout.tsx

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,7 @@ interface NativeCheckoutWebViewProps {
118118
nativeEvent: CheckoutAddressChangeStart;
119119
}) => void;
120120
onPaymentMethodChangeStart?: (event: {
121-
nativeEvent: {
122-
id: string;
123-
type: string;
124-
currentCard?: {
125-
last4: string;
126-
brand: string;
127-
};
128-
};
121+
nativeEvent: CheckoutPaymentMethodChangeStart;
129122
}) => void;
130123
}
131124

@@ -259,13 +252,7 @@ export const Checkout = forwardRef<CheckoutRef, CheckoutProps>(
259252
const handlePaymentMethodChangeStart = useCallback<
260253
Required<NativeCheckoutWebViewProps>['onPaymentMethodChangeStart']
261254
>(
262-
(event: {
263-
nativeEvent: {
264-
id: string;
265-
type: string;
266-
currentCard?: {last4: string; brand: string};
267-
};
268-
}) => {
255+
event => {
269256
if (!event.nativeEvent) return;
270257
onPaymentMethodChangeStart?.(event.nativeEvent);
271258
},

modules/@shopify/checkout-sheet-kit/src/events.d.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export interface Cart {
4646
discountAllocations: CartDiscountAllocation[];
4747
/** Delivery addresses for the cart */
4848
delivery: CartDelivery;
49+
/** Payment information for the cart */
50+
payment: CartPayment;
4951
}
5052

5153
/**
@@ -262,6 +264,25 @@ export interface CartDeliveryAddress {
262264
*/
263265
export type CartAddress = CartDeliveryAddress;
264266

267+
/**
268+
* Payment instrument available for selection at checkout.
269+
* Output type from Storefront API.
270+
*
271+
* @see https://shopify.dev/docs/api/storefront/latest/objects/CartPaymentInstrument
272+
*/
273+
export interface CartPaymentInstrument {
274+
externalReference: string;
275+
}
276+
277+
/**
278+
* Payment information available for the cart.
279+
*
280+
* @see https://shopify.dev/docs/api/storefront/latest/objects/CartPayment
281+
*/
282+
export interface CartPayment {
283+
instruments: CartPaymentInstrument[];
284+
}
285+
265286
/**
266287
* Discount applied to a cart line or the entire cart.
267288
* Shows how much was discounted and which code/promotion applied it.
@@ -474,6 +495,11 @@ export interface CartInput {
474495
* Optional - use to apply discount codes to the cart.
475496
*/
476497
discountCodes?: string[];
498+
/**
499+
* Payment instruments for the cart.
500+
* Optional - use to update payment methods.
501+
*/
502+
paymentInstruments?: CartPaymentInstrumentInput[];
477503
}
478504

479505
/**
@@ -592,8 +618,6 @@ export interface CheckoutPaymentMethodChangeStart {
592618
id: string;
593619
/** Type of payment change event */
594620
type: 'paymentMethodChangeStart';
595-
/** Cart state with current payment instruments */
596-
cart?: {
597-
paymentInstruments?: CartPaymentInstrumentInput[];
598-
};
621+
/** Cart state when the event was emitted */
622+
cart: Cart;
599623
}

modules/@shopify/checkout-sheet-kit/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,8 @@ export type {
503503
CartDeliveryInput,
504504
CartInput,
505505
CardBrand,
506+
CartPayment,
507+
CartPaymentInstrument,
506508
CartPaymentInstrumentDisplayInput,
507509
CartPaymentInstrumentInput,
508510
CheckoutAddressChangeStart,

modules/@shopify/checkout-sheet-kit/tests/testFixtures.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export function createTestCart(overrides?: Partial<Cart>): Cart {
6969
},
7070
],
7171
},
72+
payment: {
73+
instruments: [],
74+
},
7275
...overrides,
7376
};
7477
}

sample/ios/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2891,6 +2891,6 @@ SPEC CHECKSUMS:
28912891
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
28922892
Yoga: a742cc68e8366fcfc681808162492bc0aa7a9498
28932893

2894-
PODFILE CHECKSUM: c18c67add5bb02cbc43b46941cc12b33c4a7572f
2894+
PODFILE CHECKSUM: e096ca5df616e23383b3aad99d7484917ddb6df1
28952895

28962896
COCOAPODS: 1.15.2

sample/src/screens/BuyNow/CheckoutScreen.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
Checkout,
2525
type CheckoutAddressChangeStart,
2626
type CheckoutCompleteEvent,
27+
type CheckoutPaymentMethodChangeStart,
2728
type CheckoutRef,
2829
type CheckoutStartEvent,
2930
} from '@shopify/checkout-sheet-kit';
@@ -47,7 +48,8 @@ export default function CheckoutScreen(props: {
4748
navigation.navigate('Address', {id: event.id});
4849
};
4950

50-
const onPaymentMethodChangeStart = (event: {id: string}) => {
51+
const onPaymentMethodChangeStart = (event: CheckoutPaymentMethodChangeStart) => {
52+
console.log('<CheckoutScreen /> onPaymentMethodChangeStart: ', event);
5153
navigation.navigate('Payment', {id: event.id});
5254
};
5355

sample/src/screens/BuyNow/PaymentScreen.tsx

Lines changed: 82 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ import type {RouteProp} from '@react-navigation/native';
2121
import {useNavigation, useRoute} from '@react-navigation/native';
2222
import React from 'react';
2323
import {Button, StyleSheet, Text, TouchableOpacity, View} from 'react-native';
24-
import {useShopifyEvent} from '@shopify/checkout-sheet-kit';
24+
import {
25+
useShopifyEvent,
26+
type CardBrand,
27+
type CartInput,
28+
type CartPaymentInstrumentInput,
29+
type MailingAddressInput,
30+
} from '@shopify/checkout-sheet-kit';
2531
import {useCart} from '../../context/Cart';
2632
import type {BuyNowStackParamList} from './types';
2733

@@ -31,68 +37,96 @@ export default function PaymentScreen() {
3137
const event = useShopifyEvent(route.params.id);
3238
const {selectedPaymentIndex, setSelectedPaymentIndex} = useCart();
3339

34-
const paymentOptions = [
40+
const paymentOptions: Array<{
41+
id: string;
42+
label: string;
43+
cardHolderName: string;
44+
last4: string;
45+
brand: CardBrand;
46+
expiry: {month: number; year: number};
47+
billingAddress: MailingAddressInput;
48+
}> = [
3549
{
50+
id: 'card-personal-visa-4242',
3651
label: 'Personal Visa',
37-
card: {
38-
last4: '4242',
39-
brand: 'Visa',
40-
},
41-
billing: {
42-
useDeliveryAddress: true,
52+
cardHolderName: 'John Doe',
53+
last4: '4242',
54+
brand: 'VISA',
55+
expiry: {month: 12, year: 2028},
56+
billingAddress: {
57+
firstName: 'John',
58+
lastName: 'Doe',
59+
address1: '123 Main St',
60+
city: 'San Francisco',
61+
provinceCode: 'CA',
62+
countryCode: 'US',
63+
zip: '94102',
4364
},
4465
},
4566
{
67+
id: 'card-business-mc-5555',
4668
label: 'Business MasterCard',
47-
card: {
48-
last4: '5555',
49-
brand: 'Mastercard',
50-
},
51-
billing: {
52-
useDeliveryAddress: true,
69+
cardHolderName: 'Jane Smith',
70+
last4: '5555',
71+
brand: 'MASTERCARD',
72+
expiry: {month: 6, year: 2027},
73+
billingAddress: {
74+
firstName: 'Jane',
75+
lastName: 'Smith',
76+
address1: '456 Market St',
77+
city: 'San Francisco',
78+
provinceCode: 'CA',
79+
countryCode: 'US',
80+
zip: '94103',
5381
},
5482
},
5583
{
84+
id: 'card-corporate-amex-0005',
5685
label: 'Corporate Amex',
57-
card: {
58-
last4: '0005',
59-
brand: 'American Express',
60-
},
61-
billing: {
62-
useDeliveryAddress: false,
63-
address: {
64-
firstName: 'Corporate',
65-
lastName: 'Billing',
66-
address1: '123 Business Blvd',
67-
address2: 'Suite 500',
68-
city: 'New York',
69-
provinceCode: 'NY',
70-
countryCode: 'US',
71-
zip: '10001',
72-
phone: '+1-212-555-0100',
73-
company: 'Acme Corporation',
74-
},
86+
cardHolderName: 'Corporate Account',
87+
last4: '0005',
88+
brand: 'AMERICAN_EXPRESS',
89+
expiry: {month: 3, year: 2026},
90+
billingAddress: {
91+
firstName: 'Corporate',
92+
lastName: 'Billing',
93+
address1: '123 Business Blvd',
94+
address2: 'Suite 500',
95+
city: 'New York',
96+
provinceCode: 'NY',
97+
countryCode: 'US',
98+
zip: '10001',
99+
phone: '+1-212-555-0100',
100+
company: 'Acme Corporation',
75101
},
76102
},
77103
];
78104

79-
const handlePaymentSelection = () => {
105+
const handlePaymentSelection = async () => {
80106
const selectedPayment = paymentOptions[selectedPaymentIndex];
81-
event.respondWith({
82-
card: selectedPayment!.card,
83-
billing: selectedPayment!.billing,
84-
});
107+
if (!selectedPayment) return;
108+
109+
const paymentInstrument: CartPaymentInstrumentInput = {
110+
externalReference: selectedPayment.id,
111+
display: {
112+
last4: selectedPayment.last4,
113+
brand: selectedPayment.brand,
114+
cardHolderName: selectedPayment.cardHolderName,
115+
expiry: selectedPayment.expiry,
116+
},
117+
billingAddress: selectedPayment.billingAddress,
118+
};
119+
120+
const response: CartInput = {
121+
paymentInstruments: [paymentInstrument],
122+
};
123+
124+
await event.respondWith(response);
85125
navigation.goBack();
86126
};
87127

88-
const getCardIcon = (brand: string) => {
89-
// In a real app, you'd use actual card brand icons
90-
const brandIcons: {[key: string]: string} = {
91-
'Visa': '💳',
92-
'Mastercard': '💳',
93-
'American Express': '💳',
94-
};
95-
return brandIcons[brand] || '💳';
128+
const getCardIcon = (_brand: CardBrand) => {
129+
return '💳';
96130
};
97131

98132
return (
@@ -116,16 +150,14 @@ export default function PaymentScreen() {
116150
</View>
117151
<View style={styles.paymentInfo}>
118152
<View style={styles.cardHeader}>
119-
<Text style={styles.cardIcon}>{getCardIcon(option.card.brand)}</Text>
153+
<Text style={styles.cardIcon}>{getCardIcon(option.brand)}</Text>
120154
<Text style={styles.paymentLabel}>{option.label}</Text>
121155
</View>
122156
<Text style={styles.cardDetails}>
123-
{option.card.brand} •••• {option.card.last4}
157+
{option.brand} •••• {option.last4}
124158
</Text>
125159
<Text style={styles.billingInfo}>
126-
{option.billing.useDeliveryAddress
127-
? 'Uses delivery address'
128-
: `Separate billing: ${option.billing.address?.city}, ${option.billing.address?.provinceCode}`}
160+
{option.billingAddress.city}, {option.billingAddress.provinceCode}
129161
</Text>
130162
</View>
131163
</TouchableOpacity>

0 commit comments

Comments
 (0)