Skip to content

feat: add confirmPayment on paymentElement - #16

Open
aritro2002 wants to merge 1 commit into
mainfrom
feat/integration-update
Open

feat: add confirmPayment on paymentElement#16
aritro2002 wants to merge 1 commit into
mainfrom
feat/integration-update

Conversation

@aritro2002

@aritro2002 aritro2002 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

Adds support for the Elements embedded UI integration pattern .

Impact on existing functionality:

  • hyper.confirmPayment() is unchanged — existing integrations continue to work.
  • elements.create('payment', options) — old two-arg JS call sites will still receive a function and the second arg is silently ignored; no runtime break.

New Integration Steps:

React

import React, { useState, useEffect, useRef } from "react";
import {
  PaymentElement,
  useHyper,
  useWidgets,
} from "@juspay-tech/react-hyper-js";
import Cart from "./Cart";
import Completion from "./Completion";
import "./App.css";

... 

export default function CheckoutForm({ layoutQueryParam, optionsQueryParam }) {
  const hyper = useHyper();
  const elements = useWidgets();

  const [isSuccess, setIsSuccess] = useState(false);
  const [message, setMessage] = useState(null);
  const [isProcessing, setIsProcessing] = useState(false);
  const ref = useRef(null);


  // Handle form submission
  const handleSubmit = async (e) => {
    e.preventDefault();

    // Prevent submission if Hyper isn't ready or already processing
    if (!hyper || !elements || isProcessing) return;

    setIsProcessing(true);
    setMessage(null);

    try {
      const { error, status } = await elements.confirmPayment({
        // elements,
        confirmParams: {
          return_url: window.location.origin,
        },
      });
      if (error) {
        setMessage(error.message || "An unknown error occurred.");
      }

      // Handle status returned by Hyper.js (e.g., succeeded, processing, failed)
      if (status) {
        handlePaymentStatus(status, setMessage, setIsSuccess);
      }
    } catch (err) {
      setMessage(`Error confirming payment: ${err.message}`);
    } finally {
      setIsProcessing(false);
    }
  };



  return (
    ...
                  <PaymentElement
                    id="payment-element"
                    ref={ref}
                    options={paymentElementOptions(
                      parseLayoutParam(layoutQueryParam),
                      parseOptionsParam(optionsQueryParam),
                    )}
                  />
 
...

Js

  widgets = hyper.elements({
    appearance,
    clientSecret: "pay_JGdoyaAOpfom9dz4DFa6_secret_Xgg5jOrb1qockRpOOMfP",
  });
  
  const unifiedCheckoutOptions = {
    layout: {
      savedMethodCustomization: {
        groupingBehavior: "groupByPaymentMethods",
      },
    },
    wallets: {
      walletReturnUrl: "https://example.com/complete",

    },
  };
  
  const unifiedCheckout = widgets.create({
    type: "payment",
    options: unifiedCheckoutOptions,
  });
  
  unifiedCheckout.mount("#unified-checkout");
  
  globalWidgets1 = unifiedCheckout;
  
  async function handleSubmit(e) {
  setLoading(true);

  const { error, data, status } = await globalWidgets1.confirmPayment({
    confirmParams: { return_url: "https://example.com/complete" },
  });

  if (error && error.type === "validation_error") {
    showMessage(error.message);
  } else if (status === "succeeded") {
    addClass("#hypers-sdk", "hidden");
    removeClass("#orderSuccess", "hidden");
  } else {
    showMessage("An unexpected error occurred.");
  }

  setLoading(false);
}  
 ...

How did you test it?

case 1: js old integration

Screen.Recording.2026-07-20.at.4.15.36.pm.mov

case 2: js new integration

Screen.Recording.2026-07-20.at.4.18.27.pm.mov

case 3: react old integration

Screen.Recording.2026-07-20.at.4.20.01.pm.mov

case 4: react new integration

Screen.Recording.2026-07-20.at.4.21.55.pm.mov

Version Update

  • I have bumped the package version in package.json following semantic versioning:
    • x.x.x for major changes (breaking).
    • x.x.x for minor changes (new feature, no breaking changes).
    • x.x.x for patch changes (bug fixes, minor improvements).

Checklist

  • I ran npm run re:build and verified the build artifacts.
  • I reviewed the code for style, readability, and consistency.
  • I verified the changes are backward compatible (if applicable).
  • I tested this change in a real or simulated consuming project.
  • I updated documentation, README, or usage examples if necessary.

},
[hyperSwitch],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imperative handle is installed on the first commit, while switchContext is still defaultSwitchContext. Calling ref.current.confirmPayment() before the Hyper promise/element is ready therefore resolves {} and performs no request. Should we gate this on real element readiness (or reject with a stable sdk_not_ready error) instead of exposing the default no-op implementation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handle isn't frozen on mount — useImperativeHandle1 re-runs whenever hyperSwitch changes (the [hyperSwitch] dep array), so once the SDK context resolves the ref updates automatically.
That said, you're right that there's a window where calling ref.current.confirmPayment() too early would silently no-op using defaultSwitchContext. We could address this by checking ready event which is fired in almost every widget.
image

onReady=props["onReady"]
onFocus=props["onFocus"]
onBlur=props["onBlur"]
onClick=props["onClick"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why these changes were needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change from @react.component let make = (~id, ...) to React.forwardRef((props, ref_) => ...) was required to support ref-forwarding.
Also, the peer dependency is "react": "^17.0.0 || ^18.0.0 || ^19.0.0". This is a library, and its consumers may be on React 17 or 18. Dropping forwardRef in favor of React 19's ref-as-prop would silently break for those consumers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants