Skip to content

Commit 1c258ae

Browse files
authored
Implement submit start (#396)
1 parent b332283 commit 1c258ae

File tree

15 files changed

+400
-11
lines changed

15 files changed

+400
-11
lines changed

modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/RCTCheckoutWebView.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ of this software and associated documentation files (the "Software"), to deal
5555
import com.shopify.checkoutsheetkit.lifecycleevents.CheckoutStartEvent;
5656
import com.shopify.checkoutsheetkit.rpc.events.CheckoutAddressChangeStart;
5757
import com.shopify.checkoutsheetkit.rpc.events.CheckoutAddressChangeStartEvent;
58+
import com.shopify.checkoutsheetkit.rpc.events.CheckoutSubmitStart;
59+
import com.shopify.checkoutsheetkit.rpc.events.CheckoutSubmitStartEvent;
5860

5961
import com.fasterxml.jackson.core.type.TypeReference;
6062
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -351,6 +353,26 @@ public void onAddressChangeStart(@NonNull CheckoutAddressChangeStart event) {
351353
}
352354
}
353355

356+
@Override
357+
public void onSubmitStart(@NonNull CheckoutSubmitStart event) {
358+
try {
359+
CheckoutSubmitStartEvent params = event.getParams();
360+
Map<String, Object> eventData = new HashMap<>();
361+
362+
eventData.put("id", event.getId());
363+
eventData.put("type", "submitStart");
364+
eventData.put("cart", params.getCart());
365+
366+
Map<String, Object> checkoutData = new HashMap<>();
367+
checkoutData.put("id", params.getCheckout().getId());
368+
eventData.put("checkout", checkoutData);
369+
370+
sendEvent("onSubmitStart", serializeToWritableMap(eventData));
371+
} catch (Exception e) {
372+
Log.e(TAG, "Error processing submit start event", e);
373+
}
374+
}
375+
354376
@Override
355377
public void onLinkClick(@NonNull Uri uri) {
356378
WritableMap params = Arguments.createMap();

modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/RCTCheckoutWebViewManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
9191
events.put("onCancel", createEventMap("onCancel"));
9292
events.put("onLinkClick", createEventMap("onLinkClick"));
9393
events.put("onAddressChangeStart", createEventMap("onAddressChangeStart"));
94+
events.put("onSubmitStart", createEventMap("onSubmitStart"));
9495
return events;
9596
}
9697

modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/SheetCheckoutEventProcessor.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ of this software and associated documentation files (the "Software"), to deal
4444
import com.shopify.checkoutsheetkit.lifecycleevents.CheckoutStartEvent;
4545
import com.shopify.checkoutsheetkit.rpc.events.CheckoutAddressChangeStart;
4646
import com.shopify.checkoutsheetkit.rpc.events.CheckoutAddressChangeStartEvent;
47+
import com.shopify.checkoutsheetkit.rpc.events.CheckoutSubmitStart;
48+
import com.shopify.checkoutsheetkit.rpc.events.CheckoutSubmitStartEvent;
4749
import com.fasterxml.jackson.databind.ObjectMapper;
4850

4951
import java.io.IOException;
@@ -172,6 +174,31 @@ public void onAddressChangeStart(@NonNull CheckoutAddressChangeStart event) {
172174
}
173175
}
174176

177+
@Override
178+
public void onSubmitStart(@NonNull CheckoutSubmitStart event) {
179+
try {
180+
CheckoutSubmitStartEvent params = event.getParams();
181+
if (params == null) {
182+
Log.e("ShopifyCheckoutSheetKit", "Submit start event has null params");
183+
return;
184+
}
185+
186+
Map<String, Object> eventData = new HashMap<>();
187+
eventData.put("id", event.getId());
188+
eventData.put("type", "submitStart");
189+
eventData.put("cart", params.getCart());
190+
191+
Map<String, Object> checkoutData = new HashMap<>();
192+
checkoutData.put("id", params.getCheckout().getId());
193+
eventData.put("checkout", checkoutData);
194+
195+
String data = mapper.writeValueAsString(eventData);
196+
sendEventWithStringData("submitStart", data);
197+
} catch (IOException e) {
198+
Log.e("ShopifyCheckoutSheetKit", "Error processing submit start event", e);
199+
}
200+
}
201+
175202
// Private
176203

177204
private Map<String, Object> populateErrorDetails(CheckoutException error) {

modules/@shopify/checkout-sheet-kit/ios/RCTCheckoutWebView.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class RCTCheckoutWebView: UIView {
8585
@objc var onLinkClick: RCTBubblingEventBlock?
8686
@objc var onAddressChangeStart: RCTBubblingEventBlock?
8787
@objc var onPaymentChangeIntent: RCTBubblingEventBlock?
88+
@objc var onSubmitStart: RCTBubblingEventBlock?
8889

8990
override init(frame: CGRect) {
9091
super.init(frame: frame)
@@ -324,4 +325,30 @@ extension RCTCheckoutWebView: CheckoutDelegate {
324325

325326
onPaymentChangeIntent?(eventData)
326327
}
328+
329+
/// Called when the buyer attempts to submit the checkout.
330+
///
331+
/// This event is only emitted when native payment delegation is configured
332+
/// for the authenticated app.
333+
///
334+
/// - Parameter event: The submit start event containing:
335+
/// - id: Unique identifier for responding to the event
336+
/// - cart: Current cart state
337+
/// - checkout: Checkout session information
338+
func checkoutDidStartSubmit(event: CheckoutSubmitStart) {
339+
guard let id = event.id else { return }
340+
341+
self.events.set(key: id, event: event)
342+
343+
let cartJSON = ShopifyEventSerialization.encodeToJSON(from: event.params.cart)
344+
345+
onSubmitStart?([
346+
"id": event.id,
347+
"type": "submitStart",
348+
"cart": cartJSON,
349+
"checkout": [
350+
"id": event.params.checkout.id
351+
],
352+
])
353+
}
327354
}

modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit+EventSerialization.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ internal enum ShopifyEventSerialization {
8585
]
8686
}
8787

88+
/**
89+
* Converts a CheckoutSubmitStart to a React Native compatible dictionary.
90+
*/
91+
static func serialize(checkoutSubmitStart event: CheckoutSubmitStart) -> [String: Any] {
92+
return [
93+
"id": event.id as Any,
94+
"type": "submitStart",
95+
"cart": encodeToJSON(from: event.params.cart),
96+
"checkout": [
97+
"id": event.params.checkout.id
98+
]
99+
]
100+
}
101+
88102
static func serialize(clickEvent url: URL) -> [String: URL] {
89103
return ["url": url]
90104
}

modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ @interface RCT_EXTERN_MODULE (RCTCheckoutWebViewManager, RCTViewManager)
128128
*/
129129
RCT_EXPORT_VIEW_PROPERTY(onPaymentChangeIntent, RCTBubblingEventBlock)
130130

131+
/**
132+
* Emitted when the buyer attempts to submit the checkout
133+
*/
134+
RCT_EXPORT_VIEW_PROPERTY(onSubmitStart, RCTBubblingEventBlock)
135+
131136
/**
132137
* Emitted when a link is clicked
133138
*/

modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate {
5353
}
5454

5555
override func supportedEvents() -> [String]! {
56-
return ["close", "complete", "start", "error", "addressChangeStart"]
56+
return ["close", "complete", "start", "error", "addressChangeStart", "submitStart"]
5757
}
5858

5959
override func startObserving() {
@@ -82,6 +82,12 @@ class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate {
8282
}
8383
}
8484

85+
func checkoutDidStartSubmit(event: CheckoutSubmitStart) {
86+
if hasListeners {
87+
sendEvent(withName: "submitStart", body: ShopifyEventSerialization.serialize(checkoutSubmitStart: event))
88+
}
89+
}
90+
8591
func shouldRecoverFromError(error: CheckoutError) -> Bool {
8692
return error.isRecoverable
8793
}

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ import type {
3535
CheckoutException,
3636
} from '..';
3737
import {useCheckoutEvents} from '../CheckoutEventProvider';
38-
import type {CheckoutAddressChangeStart, CheckoutStartEvent} from '../events';
38+
import type {
39+
CheckoutAddressChangeStart,
40+
CheckoutStartEvent,
41+
CheckoutSubmitStart
42+
} from '../events';
3943

4044
export interface CheckoutProps {
4145
/**
@@ -81,6 +85,14 @@ export interface CheckoutProps {
8185
*/
8286
onAddressChangeStart?: (event: CheckoutAddressChangeStart) => void;
8387

88+
/**
89+
* Called when the buyer attempts to submit the checkout.
90+
*
91+
* Note: This callback is only invoked when native payment delegation is configured
92+
* for the authenticated app.
93+
*/
94+
onSubmitStart?: (event: CheckoutSubmitStart) => void;
95+
8496
/**
8597
* Style for the webview container
8698
*/
@@ -110,6 +122,7 @@ interface NativeCheckoutWebViewProps {
110122
onCancel?: () => void;
111123
onLinkClick?: (event: {nativeEvent: {url: string}}) => void;
112124
onAddressChangeStart?: (event: {nativeEvent: CheckoutAddressChangeStart}) => void;
125+
onSubmitStart?: (event: {nativeEvent: CheckoutSubmitStart}) => void;
113126
}
114127

115128
const RCTCheckoutWebView =
@@ -167,6 +180,7 @@ export const Checkout = forwardRef<CheckoutRef, CheckoutProps>(
167180
onCancel,
168181
onLinkClick,
169182
onAddressChangeStart,
183+
onSubmitStart,
170184
style,
171185
testID,
172186
},
@@ -239,6 +253,16 @@ export const Checkout = forwardRef<CheckoutRef, CheckoutProps>(
239253
[onAddressChangeStart],
240254
);
241255

256+
const handleSubmitStart = useCallback<
257+
Required<NativeCheckoutWebViewProps>['onSubmitStart']
258+
>(
259+
(event: {nativeEvent: CheckoutSubmitStart}) => {
260+
if (!event.nativeEvent) return;
261+
onSubmitStart?.(event.nativeEvent);
262+
},
263+
[onSubmitStart],
264+
);
265+
242266
const reload = useCallback(() => {
243267
if (!webViewRef.current) {
244268
return;
@@ -273,6 +297,7 @@ export const Checkout = forwardRef<CheckoutRef, CheckoutProps>(
273297
onCancel={handleCancel}
274298
onLinkClick={handleLinkClick}
275299
onAddressChangeStart={handleAddressChangeStart}
300+
onSubmitStart={handleSubmitStart}
276301
/>
277302
);
278303
},

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

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,6 @@ export interface CheckoutResponseError {
379379

380380
/**
381381
* Mailing address input for delivery.
382-
* Aligns with Storefront API MailingAddressInput.
383382
*
384383
* @see https://shopify.dev/docs/api/storefront/latest/input-objects/MailingAddressInput
385384
*/
@@ -428,7 +427,6 @@ export interface CartDeliveryAddressInput {
428427

429428
/**
430429
* Delivery-related fields for the cart.
431-
* Aligns with Storefront API CartDeliveryInput.
432430
*
433431
* @see https://shopify.dev/docs/api/storefront/latest/input-objects/CartDeliveryInput
434432
*/
@@ -441,7 +439,6 @@ export interface CartDeliveryInput {
441439

442440
/**
443441
* The customer associated with the cart.
444-
* Aligns with Storefront API CartBuyerIdentityInput.
445442
*
446443
* @see https://shopify.dev/docs/api/storefront/latest/input-objects/CartBuyerIdentityInput
447444
*/
@@ -523,7 +520,7 @@ export interface CheckoutAddressChangeStart {
523520
*/
524521
export interface CheckoutAddressChangeStartResponse {
525522
/**
526-
* Updated cart input with the delivery address to set.
523+
* Updated cart input with the delivery addresses to set.
527524
*/
528525
cart?: CartInput;
529526
/**
@@ -548,3 +545,71 @@ export interface CheckoutPaymentChangeIntent {
548545
brand: string;
549546
};
550547
}
548+
549+
/**
550+
* Checkout session information.
551+
*/
552+
export interface CheckoutSession {
553+
/** Globally unique identifier for the checkout session */
554+
id: string;
555+
}
556+
557+
/**
558+
* Event emitted when the buyer attempts to submit the checkout.
559+
*
560+
* This event is only emitted when native payment delegation is configured
561+
* for the authenticated app.
562+
*/
563+
export interface CheckoutSubmitStart {
564+
/**
565+
* Unique identifier for this event instance.
566+
* Use this ID with the CheckoutEventProvider to respond to the event.
567+
*/
568+
id: string;
569+
570+
/**
571+
* The event type identifier
572+
*/
573+
type: 'submitStart';
574+
575+
/**
576+
* The current cart state when the event was emitted.
577+
*/
578+
cart: Cart;
579+
580+
/**
581+
* The checkout session information.
582+
*/
583+
checkout: CheckoutSession;
584+
}
585+
586+
/**
587+
* Payment token input for delegated payment processing.
588+
*/
589+
export interface PaymentTokenInput {
590+
token: string;
591+
tokenType: string;
592+
tokenProvider: string;
593+
}
594+
595+
/**
596+
* Response payload for CheckoutSubmitStart event.
597+
* Use with CheckoutEventProvider.respondToEvent() or useShopifyEvent().respondWith()
598+
*
599+
* Note: This response is only used when native payment delegation is enabled
600+
* for the authenticated app.
601+
*/
602+
export interface CheckoutSubmitStartResponse {
603+
/**
604+
* Optional payment token information for delegated payment processing.
605+
*/
606+
payment?: PaymentTokenInput;
607+
/**
608+
* Updated cart input with delivery addresses and optional buyer identity.
609+
*/
610+
cart?: CartInput;
611+
/**
612+
* Optional array of errors if the submission failed.
613+
*/
614+
errors?: CheckoutResponseError[];
615+
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO
2222
*/
2323

2424
import type {EmitterSubscription} from 'react-native';
25-
import type {CheckoutCompleteEvent, CheckoutStartEvent} from './events';
25+
import type {CheckoutCompleteEvent, CheckoutStartEvent, CheckoutSubmitStart} from './events';
2626
import type {CheckoutException} from './errors';
2727

2828
export type Maybe<T> = T | undefined;
@@ -153,6 +153,7 @@ export type CheckoutEvent =
153153
| 'start'
154154
| 'error'
155155
| 'addressChangeStart'
156+
| 'submitStart'
156157
| 'geolocationRequest';
157158

158159
export interface GeolocationRequestEvent {
@@ -173,13 +174,17 @@ export type CheckoutStartEventCallback = (
173174
export type CheckoutAddressChangeStartCallback = (
174175
event: CheckoutAddressChangeStart,
175176
) => void;
177+
export type CheckoutSubmitStartCallback = (
178+
event: CheckoutSubmitStart,
179+
) => void;
176180

177181
export type CheckoutEventCallback =
178182
| CloseEventCallback
179183
| CheckoutExceptionCallback
180184
| CheckoutCompleteEventCallback
181185
| CheckoutStartEventCallback
182186
| CheckoutAddressChangeStartCallback
187+
| CheckoutSubmitStartCallback
183188
| GeolocationRequestEventCallback;
184189

185190
/**
@@ -263,6 +268,11 @@ function addEventListener(
263268
callback: CheckoutAddressChangeStartCallback,
264269
): Maybe<EmitterSubscription>;
265270

271+
function addEventListener(
272+
event: 'submitStart',
273+
callback: CheckoutSubmitStartCallback,
274+
): Maybe<EmitterSubscription>;
275+
266276
function addEventListener(
267277
event: 'error',
268278
callback: CheckoutExceptionCallback,

0 commit comments

Comments
 (0)