create-brainerce-store 1.3.2 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "create-brainerce-store",
34
- version: "1.2.1",
34
+ version: "1.3.3",
35
35
  description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
36
36
  bin: {
37
37
  "create-brainerce-store": "dist/index.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-brainerce-store",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
5
5
  "bin": {
6
6
  "create-brainerce-store": "dist/index.js"
@@ -18,6 +18,8 @@ declare global {
18
18
  onError?: (response: unknown) => void;
19
19
  onTimeout?: (response: unknown) => void;
20
20
  onWalletChange?: (state: string) => void;
21
+ onPaymentStart?: (response: unknown) => void;
22
+ onPaymentCancel?: (response: unknown) => void;
21
23
  };
22
24
  }) => void;
23
25
  renderPaymentOptions: (authCode: string) => void;
@@ -33,14 +35,60 @@ interface PaymentStepProps {
33
35
  className?: string;
34
36
  }
35
37
 
38
+ /**
39
+ * Load a script tag if not already present in the DOM.
40
+ * Resolves when the script loads (or immediately if already present).
41
+ */
42
+ function loadScript(src: string, optional = false): Promise<void> {
43
+ return new Promise((resolve) => {
44
+ if (document.querySelector(`script[src="${src}"]`)) {
45
+ resolve();
46
+ return;
47
+ }
48
+ const script = document.createElement('script');
49
+ script.src = src;
50
+ script.async = true;
51
+ script.onload = () => resolve();
52
+ script.onerror = () => {
53
+ if (optional) {
54
+ resolve(); // Non-blocking for optional SDKs
55
+ } else {
56
+ resolve(); // Still resolve — caller handles missing global
57
+ }
58
+ };
59
+ document.head.appendChild(script);
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Wait for window.growPayment to become available (set by the SDK script).
65
+ */
66
+ function waitForGrowGlobal(timeoutMs = 5000): Promise<boolean> {
67
+ return new Promise((resolve) => {
68
+ if (window.growPayment) {
69
+ resolve(true);
70
+ return;
71
+ }
72
+ const start = Date.now();
73
+ const check = setInterval(() => {
74
+ if (window.growPayment) {
75
+ clearInterval(check);
76
+ resolve(true);
77
+ } else if (Date.now() - start > timeoutMs) {
78
+ clearInterval(check);
79
+ resolve(false);
80
+ }
81
+ }, 50);
82
+ });
83
+ }
84
+
36
85
  export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
37
86
  const [paymentIntent, setPaymentIntent] = useState<PaymentIntent | null>(null);
38
87
  const [loading, setLoading] = useState(true);
39
88
  const [error, setError] = useState<string | null>(null);
40
89
  const [growReady, setGrowReady] = useState(false);
41
- const [growSdkInitialized, setGrowSdkInitialized] = useState(false);
42
- const growInitCalled = useRef(false);
43
- const growRenderCalled = useRef(false);
90
+ const [sdkScriptLoaded, setSdkScriptLoaded] = useState(false);
91
+ const renderAttempted = useRef(false);
44
92
 
45
93
  const handleGrowSuccess = useCallback(
46
94
  async (response: unknown) => {
@@ -71,90 +119,36 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
71
119
  setError(msg);
72
120
  }, []);
73
121
 
74
- // Step 1: Load Grow SDK script and call init() — runs on mount
75
- // This matches the documented flow where init() is called on script.onload
122
+ // Step 1: Load SDK scripts (Apple Pay + Grow) — just load, don't init yet
76
123
  useEffect(() => {
77
- if (growInitCalled.current) return;
78
- growInitCalled.current = true;
79
-
80
- // Load Apple Pay SDK (required for Apple Pay in Grow wallet)
81
- function loadApplePaySdk(): Promise<void> {
82
- return new Promise((resolve) => {
83
- if (document.querySelector(`script[src="${APPLE_PAY_SDK_URL}"]`)) {
84
- resolve();
85
- return;
86
- }
87
- const script = document.createElement('script');
88
- script.src = APPLE_PAY_SDK_URL;
89
- script.async = true;
90
- script.onload = () => resolve();
91
- script.onerror = () => {
92
- console.warn('Apple Pay SDK failed to load — Apple Pay will be unavailable');
93
- resolve(); // Non-blocking: other payment methods still work
94
- };
95
- document.head.appendChild(script);
96
- });
97
- }
124
+ let cancelled = false;
98
125
 
99
- function loadAndInitGrowSdk() {
100
- if (window.growPayment) {
101
- initSdk('DEV'); // Default, will be overridden if needed
102
- return;
103
- }
126
+ async function loadSdkScripts() {
127
+ // Load Apple Pay SDK first (optional — needed for Apple Pay in Grow wallet)
128
+ await loadScript(APPLE_PAY_SDK_URL, true);
129
+
130
+ // Load Grow SDK script
131
+ await loadScript(GROW_SDK_URL);
132
+
133
+ // Wait for growPayment global to be set by the script
134
+ const available = await waitForGrowGlobal();
135
+
136
+ if (cancelled) return;
104
137
 
105
- const existing = document.querySelector(`script[src="${GROW_SDK_URL}"]`);
106
- if (existing) {
107
- const check = setInterval(() => {
108
- if (window.growPayment) {
109
- clearInterval(check);
110
- initSdk('DEV');
111
- }
112
- }, 100);
138
+ if (!available) {
139
+ setError('Failed to load payment SDK');
113
140
  return;
114
141
  }
115
142
 
116
- const script = document.createElement('script');
117
- script.type = 'text/javascript';
118
- script.async = true;
119
- script.src = GROW_SDK_URL;
120
- script.onload = () => {
121
- // Wait for growPayment global to be set
122
- const check = setInterval(() => {
123
- if (window.growPayment) {
124
- clearInterval(check);
125
- initSdk('DEV');
126
- }
127
- }, 50);
128
- // Timeout after 5s
129
- setTimeout(() => clearInterval(check), 5000);
130
- };
131
- script.onerror = () => {
132
- setError('Failed to load payment SDK');
133
- };
134
- document.head.appendChild(script);
143
+ setSdkScriptLoaded(true);
135
144
  }
136
145
 
137
- function initSdk(env: string) {
138
- if (!window.growPayment) return;
139
- console.info('Grow SDK: calling init with environment:', env);
140
- window.growPayment.init({
141
- environment: env,
142
- version: 1,
143
- events: {
144
- onSuccess: handleGrowSuccess,
145
- onFailure: handleGrowFailure,
146
- onError: handleGrowError,
147
- onWalletChange: (state: string) => {
148
- console.info('Grow wallet state:', state);
149
- },
150
- },
151
- });
152
- setGrowSdkInitialized(true);
153
- }
146
+ loadSdkScripts();
154
147
 
155
- // Load Apple Pay SDK first, then Grow SDK
156
- loadApplePaySdk().then(() => loadAndInitGrowSdk());
157
- }, [handleGrowSuccess, handleGrowFailure, handleGrowError]);
148
+ return () => {
149
+ cancelled = true;
150
+ };
151
+ }, []);
158
152
 
159
153
  // Step 2: Create payment intent (API call)
160
154
  useEffect(() => {
@@ -189,30 +183,47 @@ export function PaymentStep({ checkoutId, className }: PaymentStepProps) {
189
183
  createIntent();
190
184
  }, [checkoutId]);
191
185
 
192
- // Step 3: When BOTH SDK is initialized AND payment intent is ready, render payment options
186
+ // Step 3: When BOTH SDK script is loaded AND payment intent is ready:
187
+ // - init() with the CORRECT environment from the payment intent
188
+ // - then renderPaymentOptions() with the authCode
193
189
  useEffect(() => {
194
- if (!growSdkInitialized) return;
190
+ if (!sdkScriptLoaded) return;
195
191
  if (!paymentIntent || paymentIntent.provider !== 'grow') return;
196
- if (growRenderCalled.current) return;
197
192
  if (!window.growPayment) return;
193
+ if (renderAttempted.current) return;
198
194
 
199
- growRenderCalled.current = true;
195
+ renderAttempted.current = true;
200
196
 
201
- // Parse authCode from clientSecret (format: "ENV|authCode")
197
+ // Parse environment and authCode from clientSecret (format: "ENV|authCode")
202
198
  const pipeIndex = paymentIntent.clientSecret.indexOf('|');
199
+ const env = pipeIndex !== -1 ? paymentIntent.clientSecret.substring(0, pipeIndex) : 'DEV';
203
200
  const authCode =
204
201
  pipeIndex !== -1
205
202
  ? paymentIntent.clientSecret.substring(pipeIndex + 1)
206
203
  : paymentIntent.clientSecret;
207
204
 
208
- console.info('Grow SDK: calling renderPaymentOptions');
205
+ // Init SDK with the correct environment (must match the authCode's origin)
206
+ console.info('Grow SDK: init with environment:', env);
207
+ window.growPayment.init({
208
+ environment: env,
209
+ version: 1,
210
+ events: {
211
+ onSuccess: handleGrowSuccess,
212
+ onFailure: handleGrowFailure,
213
+ onError: handleGrowError,
214
+ onWalletChange: (state: string) => {
215
+ console.info('Grow wallet state:', state);
216
+ },
217
+ },
218
+ });
209
219
 
210
- // Small delay to ensure SDK internal state is ready after init()
220
+ // Render payment options small delay to allow init() to complete internal setup
221
+ console.info('Grow SDK: calling renderPaymentOptions');
211
222
  setTimeout(() => {
212
223
  window.growPayment?.renderPaymentOptions(authCode);
213
224
  setGrowReady(true);
214
- }, 500);
215
- }, [growSdkInitialized, paymentIntent]);
225
+ }, 300);
226
+ }, [sdkScriptLoaded, paymentIntent, handleGrowSuccess, handleGrowFailure, handleGrowError]);
216
227
 
217
228
  if (loading) {
218
229
  return (