@reevit/react 0.2.9 → 0.3.1

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.mjs CHANGED
@@ -176,6 +176,7 @@ var ReevitAPIClient = class {
176
176
  method: this.mapPaymentMethod(method),
177
177
  country,
178
178
  customer_id: config.email || config.metadata?.customerId,
179
+ phone: config.phone,
179
180
  metadata
180
181
  };
181
182
  return this.request("POST", "/v1/payments/intents", request);
@@ -187,7 +188,13 @@ var ReevitAPIClient = class {
187
188
  return this.request("GET", `/v1/payments/${paymentId}`);
188
189
  }
189
190
  /**
190
- * Confirms a payment after PSP callback
191
+ * Confirms a payment intent after PSP callback (public endpoint)
192
+ */
193
+ async confirmPaymentIntent(paymentId, clientSecret) {
194
+ return this.request("POST", `/v1/payments/${paymentId}/confirm-intent?client_secret=${clientSecret}`);
195
+ }
196
+ /**
197
+ * Confirms a payment after PSP callback (authenticated endpoint)
191
198
  */
192
199
  async confirmPayment(paymentId) {
193
200
  return this.request("POST", `/v1/payments/${paymentId}/confirm`);
@@ -231,7 +238,12 @@ function reevitReducer(state, action) {
231
238
  case "INIT_START":
232
239
  return { ...state, status: "loading", error: null };
233
240
  case "INIT_SUCCESS":
234
- return { ...state, status: "ready", paymentIntent: action.payload };
241
+ return {
242
+ ...state,
243
+ status: "ready",
244
+ paymentIntent: action.payload,
245
+ selectedMethod: action.payload.availableMethods?.length === 1 ? action.payload.availableMethods[0] : state.selectedMethod
246
+ };
235
247
  case "INIT_ERROR":
236
248
  return { ...state, status: "failed", error: action.payload };
237
249
  case "SELECT_METHOD":
@@ -267,6 +279,8 @@ function mapToPaymentIntent(response, config) {
267
279
  status: response.status,
268
280
  recommendedPsp: mapProviderToPsp(response.provider),
269
281
  availableMethods: config.paymentMethods || ["card", "mobile_money"],
282
+ reference: response.reference || response.id,
283
+ // Use backend reference or fallback to ID
270
284
  connectionId: response.connection_id,
271
285
  provider: response.provider,
272
286
  feeAmount: response.fee_amount,
@@ -280,14 +294,17 @@ function useReevit(options) {
280
294
  const [state, dispatch] = useReducer(reevitReducer, {
281
295
  ...initialState,
282
296
  status: config.initialPaymentIntent ? "ready" : "idle",
283
- paymentIntent: config.initialPaymentIntent || null
297
+ paymentIntent: config.initialPaymentIntent || null,
298
+ selectedMethod: config.initialPaymentIntent?.availableMethods?.length === 1 ? config.initialPaymentIntent.availableMethods[0] : null
284
299
  });
285
300
  const apiClientRef = useRef(null);
286
301
  const initializingRef = useRef(!!config.initialPaymentIntent);
287
302
  useEffect(() => {
288
- if (config.initialPaymentIntent && (!state.paymentIntent || state.paymentIntent.id !== config.initialPaymentIntent.id)) {
289
- dispatch({ type: "INIT_SUCCESS", payload: config.initialPaymentIntent });
290
- initializingRef.current = true;
303
+ if (config.initialPaymentIntent) {
304
+ if (!state.paymentIntent || state.paymentIntent.id !== config.initialPaymentIntent.id) {
305
+ dispatch({ type: "INIT_SUCCESS", payload: config.initialPaymentIntent });
306
+ initializingRef.current = true;
307
+ }
291
308
  }
292
309
  }, [config.initialPaymentIntent, state.paymentIntent?.id]);
293
310
  if (!apiClientRef.current) {
@@ -366,8 +383,10 @@ function useReevit(options) {
366
383
  if (!apiClient) {
367
384
  throw new Error("API client not initialized");
368
385
  }
369
- const { data, error } = await apiClient.confirmPayment(state.paymentIntent.id);
386
+ const clientSecret = state.paymentIntent.clientSecret;
387
+ const { data, error } = clientSecret ? await apiClient.confirmPaymentIntent(state.paymentIntent.id, clientSecret) : await apiClient.confirmPayment(state.paymentIntent.id);
370
388
  if (error) {
389
+ console.error("[useReevit] Confirmation error:", error);
371
390
  dispatch({ type: "PROCESS_ERROR", payload: error });
372
391
  onError?.(error);
373
392
  return;
@@ -380,11 +399,17 @@ function useReevit(options) {
380
399
  paymentMethod: state.selectedMethod,
381
400
  psp: state.paymentIntent.recommendedPsp,
382
401
  pspReference: paymentData.pspReference || data?.provider_ref_id || "",
383
- status: "success",
384
- metadata: paymentData
402
+ status: data?.status === "succeeded" ? "success" : "pending",
403
+ metadata: { ...paymentData, backend_status: data?.status }
385
404
  };
386
- dispatch({ type: "PROCESS_SUCCESS", payload: result });
387
- onSuccess?.(result);
405
+ console.log("[useReevit] Process result:", result);
406
+ if (result.status === "success") {
407
+ dispatch({ type: "PROCESS_SUCCESS", payload: result });
408
+ onSuccess?.(result);
409
+ } else {
410
+ dispatch({ type: "PROCESS_SUCCESS", payload: result });
411
+ onSuccess?.(result);
412
+ }
388
413
  } catch (err) {
389
414
  const error = {
390
415
  code: "PAYMENT_FAILED",
@@ -674,9 +699,11 @@ function loadPaystackScript() {
674
699
  function PaystackBridge({
675
700
  publicKey,
676
701
  email,
702
+ phone,
677
703
  amount,
678
704
  currency = "GHS",
679
705
  reference,
706
+ accessCode,
680
707
  metadata,
681
708
  channels = ["card", "mobile_money"],
682
709
  onSuccess,
@@ -687,47 +714,70 @@ function PaystackBridge({
687
714
  const initialized = useRef(false);
688
715
  const startPayment = useCallback(async () => {
689
716
  try {
717
+ console.log("[PaystackBridge] Starting payment", {
718
+ hasPublicKey: !!publicKey,
719
+ email,
720
+ amount,
721
+ reference,
722
+ hasAccessCode: !!accessCode
723
+ });
690
724
  if (!publicKey) {
691
725
  throw new Error("Paystack public key is required but was empty");
692
726
  }
693
- if (!email) {
694
- throw new Error("Email is required for Paystack payments");
727
+ if (!email && !accessCode) {
728
+ throw new Error("Email is required for Paystack payments when no access code is provided");
695
729
  }
696
- if (!amount || amount <= 0) {
697
- throw new Error("Valid amount is required for Paystack payments");
730
+ if (!amount && !accessCode) {
731
+ throw new Error("Valid amount is required for Paystack payments when no access code is provided");
698
732
  }
699
733
  await loadPaystackScript();
700
734
  if (!window.PaystackPop) {
701
735
  throw new Error("Paystack script loaded but PaystackPop not available");
702
736
  }
703
- const handler = window.PaystackPop.setup({
737
+ const setupConfig = {
704
738
  key: publicKey,
705
739
  email,
740
+ phone,
706
741
  amount,
707
- // Paystack expects amount in kobo/pesewas (smallest unit)
708
742
  currency,
709
743
  ref: reference,
744
+ access_code: accessCode,
710
745
  metadata,
711
746
  channels,
712
747
  callback: (response) => {
748
+ console.log("[PaystackBridge] Callback received", response);
749
+ let usedMethod = "card";
750
+ if (channels && channels.length === 1) {
751
+ usedMethod = channels[0];
752
+ } else if (response.message?.toLowerCase().includes("mobile money")) {
753
+ usedMethod = "mobile_money";
754
+ }
713
755
  const result = {
714
- paymentId: response.transaction,
756
+ paymentId: response.reference,
757
+ // Use the reference as paymentId because we set it to Reevit's UUID
715
758
  reference: response.reference,
716
759
  amount,
717
760
  currency,
718
- paymentMethod: "card",
719
- // Paystack handles this internally
761
+ paymentMethod: usedMethod,
720
762
  psp: "paystack",
721
- pspReference: response.trans,
763
+ pspReference: response.transaction,
764
+ // Paystack's internal transaction ID
722
765
  status: response.status === "success" ? "success" : "pending",
723
- metadata: { trxref: response.trxref }
766
+ metadata: {
767
+ ...response,
768
+ trxref: response.trxref,
769
+ paystack_transaction_id: response.transaction,
770
+ paystack_trans: response.trans
771
+ }
724
772
  };
725
773
  onSuccess(result);
726
774
  },
727
775
  onClose: () => {
776
+ console.log("[PaystackBridge] Modal closed");
728
777
  onClose();
729
778
  }
730
- });
779
+ };
780
+ const handler = window.PaystackPop.setup(setupConfig);
731
781
  handler.openIframe();
732
782
  } catch (err) {
733
783
  const errorMessage = err instanceof Error ? err.message : "Failed to initialize Paystack";
@@ -739,7 +789,7 @@ function PaystackBridge({
739
789
  };
740
790
  onError(error);
741
791
  }
742
- }, [publicKey, email, amount, currency, reference, metadata, channels, onSuccess, onError, onClose]);
792
+ }, [publicKey, email, amount, currency, reference, accessCode, metadata, channels, onSuccess, onError, onClose]);
743
793
  useEffect(() => {
744
794
  if (autoStart && !initialized.current) {
745
795
  initialized.current = true;
@@ -769,6 +819,7 @@ function ReevitCheckout({
769
819
  reference,
770
820
  metadata,
771
821
  paymentMethods = ["card", "mobile_money"],
822
+ initialPaymentIntent,
772
823
  // Callbacks
773
824
  onSuccess,
774
825
  onError,
@@ -810,7 +861,17 @@ function ReevitCheckout({
810
861
  isLoading,
811
862
  isComplete
812
863
  } = useReevit({
813
- config: { publicKey, amount, currency, email, phone, reference, metadata, paymentMethods },
864
+ config: {
865
+ publicKey,
866
+ amount,
867
+ currency,
868
+ email,
869
+ phone,
870
+ reference,
871
+ metadata,
872
+ paymentMethods,
873
+ initialPaymentIntent
874
+ },
814
875
  apiBaseUrl,
815
876
  onSuccess: (result2) => {
816
877
  onSuccess?.(result2);
@@ -826,10 +887,19 @@ function ReevitCheckout({
826
887
  onStateChange
827
888
  });
828
889
  useEffect(() => {
829
- if (isOpen && status === "idle") {
890
+ if (isOpen && status === "idle" && !initialPaymentIntent) {
830
891
  initialize();
831
892
  }
832
- }, [isOpen, status, initialize]);
893
+ }, [isOpen, status, initialize, initialPaymentIntent]);
894
+ useEffect(() => {
895
+ if (isOpen && (selectedMethod === "card" || selectedMethod === "mobile_money") && !showPSPBridge) {
896
+ if (selectedMethod === "card" && paymentIntent) {
897
+ setShowPSPBridge(true);
898
+ } else if (selectedMethod === "mobile_money" && paymentIntent && (momoData?.phone || phone)) {
899
+ setShowPSPBridge(true);
900
+ }
901
+ }
902
+ }, [isOpen, selectedMethod, showPSPBridge, paymentIntent, momoData, phone]);
833
903
  const handleOpen = useCallback(() => {
834
904
  if (controlledIsOpen !== void 0) return;
835
905
  setIsOpen(true);
@@ -883,15 +953,16 @@ function ReevitCheckout({
883
953
  setShowPSPBridge(false);
884
954
  }, [reset]);
885
955
  const themeStyles = theme ? createThemeVariables(theme) : {};
886
- const trigger = children ? /* @__PURE__ */ jsx("span", { onClick: handleOpen, role: "button", tabIndex: 0, children }) : /* @__PURE__ */ jsxs("button", { className: "reevit-trigger-btn", onClick: handleOpen, children: [
956
+ const isControlled = controlledIsOpen !== void 0;
957
+ const trigger = children ? /* @__PURE__ */ jsx("span", { onClick: isControlled ? void 0 : handleOpen, role: isControlled ? void 0 : "button", tabIndex: isControlled ? void 0 : 0, children }) : !isControlled ? /* @__PURE__ */ jsxs("button", { className: "reevit-trigger-btn", onClick: handleOpen, children: [
887
958
  "Pay ",
888
959
  formatAmount(amount, currency)
889
- ] });
960
+ ] }) : null;
890
961
  const renderContent = () => {
891
- if (status === "loading") {
962
+ if (status === "loading" || status === "processing") {
892
963
  return /* @__PURE__ */ jsxs("div", { className: "reevit-loading", children: [
893
964
  /* @__PURE__ */ jsx("div", { className: "reevit-spinner" }),
894
- /* @__PURE__ */ jsx("p", { children: "Preparing checkout..." })
965
+ /* @__PURE__ */ jsx("p", { children: status === "loading" ? "Preparing checkout..." : "Processing payment..." })
895
966
  ] });
896
967
  }
897
968
  if (status === "success" && result) {
@@ -919,15 +990,18 @@ function ReevitCheckout({
919
990
  {
920
991
  publicKey: pspKey,
921
992
  email,
993
+ phone: momoData?.phone || phone,
922
994
  amount: paymentIntent?.amount ?? amount,
923
995
  currency: paymentIntent?.currency ?? currency,
924
996
  reference,
997
+ accessCode: paymentIntent?.clientSecret,
925
998
  metadata: {
926
999
  ...metadata,
927
1000
  // Override with correct payment intent ID for webhook routing
928
1001
  // This ensures Paystack webhook includes the correct ID to find the payment
929
1002
  payment_id: paymentIntent?.id,
930
- connection_id: paymentIntent?.connectionId ?? metadata?.connection_id
1003
+ connection_id: paymentIntent?.connectionId ?? metadata?.connection_id,
1004
+ customer_phone: momoData?.phone || phone
931
1005
  },
932
1006
  channels: selectedMethod === "mobile_money" ? ["mobile_money"] : ["card"],
933
1007
  onSuccess: handlePSPSuccess,