@@ -35,6 +35,7 @@ of this software and associated documentation files (the "Software"), to deal
3535import com .facebook .react .modules .core .DeviceEventManagerModule ;
3636import com .facebook .react .bridge .ReactApplicationContext ;
3737import com .fasterxml .jackson .databind .ObjectMapper ;
38+ import com .fasterxml .jackson .databind .node .ObjectNode ;
3839import java .io .IOException ;
3940import java .util .HashMap ;
4041import java .util .Map ;
@@ -44,24 +45,17 @@ public class CustomCheckoutListener extends DefaultCheckoutListener {
4445 private final ObjectMapper mapper = new ObjectMapper ();
4546
4647 @ Nullable
47- private Callback onCloseCallback ;
48- @ Nullable
49- private Callback onFailCallback ;
50- @ Nullable
51- private Callback onGeolocationRequestCallback ;
48+ private Callback dispatchCallback ;
5249
5350 // Geolocation-specific variables
5451
5552 private String geolocationOrigin ;
5653 private GeolocationPermissions .Callback geolocationCallback ;
5754
5855 public CustomCheckoutListener (Context context , ReactApplicationContext reactContext ,
59- @ Nullable Callback onClose , @ Nullable Callback onFail ,
60- @ Nullable Callback onGeolocationRequest ) {
56+ @ Nullable Callback dispatch ) {
6157 this .reactContext = reactContext ;
62- this .onCloseCallback = onClose ;
63- this .onFailCallback = onFail ;
64- this .onGeolocationRequestCallback = onGeolocationRequest ;
58+ this .dispatchCallback = dispatch ;
6559 }
6660
6761 // Public methods
@@ -77,35 +71,29 @@ public void invokeGeolocationCallback(boolean allow) {
7771 // Lifecycle events
7872
7973 /**
80- * This method is called when the checkout sheet webpage requests geolocation
81- * permissions.
82- *
83- * Since the app needs to request permissions first before granting, we store
84- * the callback and origin in memory and emit a "geolocationRequest" event to
85- * the app. The app will then request the necessary geolocation permissions
86- * and invoke the native callback with the result.
74+ * Called when the checkout sheet's webpage requests geolocation
75+ * permissions. The platform callback is stored in memory; the dispatcher
76+ * is invoked with a `geolocationRequest` envelope so JS can either route
77+ * to a per-call handler or run the default permission flow.
8778 *
88- * @param origin - The origin of the request
89- * @param callback - The callback to invoke when the app requests permissions
79+ * Multi-shot — the same checkout sheet may request geolocation multiple
80+ * times during a single `present()` call, so the dispatcher is not
81+ * nulled after invocation.
9082 */
9183 @ Override
9284 public void onGeolocationPermissionsShowPrompt (@ NonNull String origin ,
9385 @ NonNull GeolocationPermissions .Callback callback ) {
9486
95- // Store the callback and origin in memory. The kit will wait for the app to
96- // request permissions first before granting.
9787 this .geolocationCallback = callback ;
9888 this .geolocationOrigin = origin ;
9989
90+ if (dispatchCallback == null ) {
91+ return ;
92+ }
10093 try {
101- Map <String , Object > event = new HashMap <>();
102- event .put ("origin" , origin );
103- String payload = mapper .writeValueAsString (event );
104- if (onGeolocationRequestCallback != null ) {
105- onGeolocationRequestCallback .invoke (payload );
106- } else {
107- sendEventWithStringData ("geolocationRequest" , payload );
108- }
94+ Map <String , Object > payload = new HashMap <>();
95+ payload .put ("origin" , origin );
96+ dispatchCallback .invoke (buildEnvelope ("geolocationRequest" , payload ));
10997 } catch (IOException e ) {
11098 Log .e ("ShopifyCheckoutKit" , "Error emitting \" geolocationRequest\" event" , e );
11199 }
@@ -115,37 +103,49 @@ public void onGeolocationPermissionsShowPrompt(@NonNull String origin,
115103 public void onGeolocationPermissionsHidePrompt () {
116104 super .onGeolocationPermissionsHidePrompt ();
117105
118- // Reset the geolocation callback and origin when the prompt is hidden.
119106 this .geolocationCallback = null ;
120107 this .geolocationOrigin = null ;
121108 }
122109
123110 @ Override
124111 public void onCheckoutFailed (CheckoutException checkoutError ) {
125- if (onFailCallback == null ) {
112+ if (dispatchCallback == null ) {
126113 return ;
127114 }
128115 try {
129- String data = mapper .writeValueAsString (populateErrorDetails (checkoutError ));
130- onFailCallback .invoke (data );
116+ dispatchCallback .invoke (buildEnvelope ("fail" , populateErrorDetails (checkoutError )));
131117 } catch (IOException e ) {
132118 Log .e ("ShopifyCheckoutKit" , "Error processing checkout failed event" , e );
133119 } finally {
134- onFailCallback = null ;
120+ dispatchCallback = null ;
135121 }
136122 }
137123
138124 @ Override
139125 public void onCheckoutCanceled () {
140- if (onCloseCallback == null ) {
126+ if (dispatchCallback == null ) {
141127 return ;
142128 }
143- onCloseCallback .invoke ();
144- onCloseCallback = null ;
129+ try {
130+ dispatchCallback .invoke (buildEnvelope ("close" , null ));
131+ } catch (IOException e ) {
132+ Log .e ("ShopifyCheckoutKit" , "Error processing checkout canceled event" , e );
133+ } finally {
134+ dispatchCallback = null ;
135+ }
145136 }
146137
147138 // Private
148139
140+ private String buildEnvelope (String type , @ Nullable Object payload ) throws IOException {
141+ ObjectNode envelope = mapper .createObjectNode ();
142+ envelope .put ("type" , type );
143+ if (payload != null ) {
144+ envelope .set ("payload" , mapper .valueToTree (payload ));
145+ }
146+ return mapper .writeValueAsString (envelope );
147+ }
148+
149149 private Map <String , Object > populateErrorDetails (CheckoutException checkoutError ) {
150150 Map <String , Object > errorMap = new HashMap ();
151151 errorMap .put ("__typename" , getErrorTypeName (checkoutError ));
0 commit comments