Intergration

Intergrations

Lets now intergrate our payment for to call the server actions.

Lets begin by advancing our handleSubmit function to validate the formData and call the stkPush server action.

Validation and Calling STK PUSH action

components/PaymentForm.tsx
//add this at the top of the components  and make sure your have useState imported
 const [loading, setLoading] = useState <boolean> (false);
 
  const handleSubmit = async () => {
    setLoading(true);
 
    const formData = {
      mpesa_number: dataFromForm.mpesa_phone.trim(),
      name: dataFromForm.name.trim(),
      amount: dataFromForm.amount,
    };
 
    //validate as you wish - we just just validate the phone number for now to allow any mpesa number format
 
    const kenyanPhoneNumberRegex =
      /^(07\d{8}|01\d{8}|2547\d{8}|2541\d{8}|\+2547\d{8}|\+2541\d{8})$/;
 
    if (!kenyanPhoneNumberRegex.test(formData.mpesa_number)) {
      setLoading(false);
      return alert("Invalid mpesa number");
    }
 
    const { data: stkData, error: stkError } = await sendStkPush(formData);
 
    if (stkError) {
      setLoading(false);
      return alert(stkError);
    }
 
    const checkoutRequestId = stkData.CheckoutRequestID;
 
    console.log(checkoutRequestId)
    alert("stk push sent successfully");
  };

Dont forget to import the sendStkPush server action.

components/PaymentForm.tsx
import { sendStkPush } from "@/actions/stkPush";

Lets also update our submit button to handle the loading

components/PaymentForm.tsx
<button
  type="submit"
  onClick={handleSubmit}
  disabled={loading}
  className="inline-flex w-full items-center justify-center rounded-md border border-transparent bg-orange-500 px-4 py-4 text-base font-semibold text-white transition-all duration-200 hover:bg-orange-600 focus:bg-orange-600 focus:outline-none"
>
  {loading ? "Processing.." : "Proceed With Payment"}
</button>;

Hurray!! You have successfully sent your First STK push.

Polling Transaction status

The function above returns a sucess when an stk push is sent to the users device successfully.

We need a way to now check for the users interaction with the stk push popup.

To achieve this, we have a couple of options depending on the complexity of your project. In this guide we will use short polling - a techique that will allow use to make request to the stkquery api at a certain interval to check for the stkpush status and dispay relevant feedback on the UI.

Lets start by adding the stk query logic in our paymentForm component. We will utilize javascripts setInterval to achieve this.

components/PaymentForm.tsx
//add this just before the handleSubmit Function
var reqcount = 0;
 
const stkPushQueryWithIntervals = (CheckoutRequestID: string) => {
  const timer = setInterval(async () => {
    reqcount += 1;
 
    if (reqcount === 15) {
      //handle long payment
      clearInterval(timer);
      setStkQueryLoading(false);
      setLoading(false);
      setErrorMessage("You took too long to pay");
    }
 
    const { data, error } = await stkPushQuery(CheckoutRequestID);
 
    if (error) {
      if (error.response.data.errorCode !== "500.001.1001") {
        setStkQueryLoading(false);
        setLoading(false);
        setErrorMessage(error?.response?.data?.errorMessage);
      }
    }
 
    if (data) {
      if (data.ResultCode === "0") {
        clearInterval(timer);
        setStkQueryLoading(false);
        setLoading(false);
        setSuccess(true);
      } else {
        clearInterval(timer);
        setStkQueryLoading(false);
        setLoading(false);
        setErrorMessage(data?.ResultDesc);
      }
    }
  }, 2000);
};
 
const handleSubmit = async () => {
    // ...... rest of the code from before
}

The stkPushQueryWithIntervals function is designed to repeatedly check the status of an STK push transaction at regular intervals:

Initialization:

  • A variable reqcount is initialized to keep track of the number of requests made.

Set Interval:

  • setInterval is used to create a timer that runs the function every 2 seconds.

Request Count Check:

  • If reqcount reaches 15 (indicating a long payment process), the timer is cleared, and an error message is displayed to the user.

STK Query API Call:

  • The function makes a call to the stkPushQuery API with the CheckoutRequestID.
  • If an error occurs and it's not a specific known error (500.001.1001), the timer is cleared, and the error message is displayed.

Response Handling:

  • If the API returns a success response (ResultCode is "0"), the timer is cleared, and a success message is displayed.
  • If the API returns a failure response, the timer is cleared, and the error message is displayed.

Error Handling:

  • Appropriate messages are set based on the success or failure of the transaction.

This logic ensures that the UI is updated in real-time based on the user's interaction with the STK push popup.

Dont forget to import the stkPushQuery server action.

components/PaymentForm.tsx
import { stkPushQuery } from "@/actions/stkPushQuery";

Lets now add the neccessary pieces of state.

components/PaymentForm.tsx
//add these below the loading usestate
  const [success, setSuccess] = useState<boolean>(false);
  const [stkQueryLoading, setStkQueryLoading] = useState<boolean>(false);

To visually show the loading checkout proceses, we will now create two more componets that will dispay a loading screen and a success screen inside our components folder

Loading indicator

components/StkQueryLoading.tsx
export default function STKPushQueryLoading({number}:{number:string}) {
    return (
      <div className="space-y-2 text-center text-black p-10 bg-gray-100">
        <h1 className="animate-pulse">PROCESSING PAYMENT..</h1>
        <h1>Stk push sent to {number}</h1>
        <h1>Enter Pin to confirm payment</h1>
      </div>
    );
  }

Payment successfull indicator

components/Success.tsx
export default function PaymentSuccess() {
  return (
    <div className="space-y-2 text-center text-black p-10 bg-gray-100">
      <h1>Your Payment was processed succesfully</h1>
      <h1>Thank You for your Donation</h1>
    </div>
  );
}

Last thing, lets now render the sections based on our flow

components/PaymentForm.tsx
// import the components at the top of the payment form component
 
import PaymentSuccess from "./Success";
import STKPushQueryLoading from "./StkQueryLoading";
 
//add the following conditional rendering for your code
 
function PaymentForm() {
    ...rest of the code
    return (
        <>
        {stkQueryLoading ? (
          <STKPushQueryLoading number={dataFromForm.mpesa_phone}/>
        ) : success ? (
          <PaymentSuccess />
        ) : (
            <div className="lg:pl-12"
              ...rest of the code
            </div>
          )}
        </>
      ) }   

One last thing, lets call the stkPushQueryWithIntervals after an stk push is sent successfully.

HandleSubmit Function
const handleSubmit = async () => {
    // ...... rest of the code from before
    //you can remove the console log and the alert
    setStkQueryLoading(true)
    stkPushQueryWithIntervals(checkoutRequestId);
}

At this point, Our stk push and stk query logic should be working perfect as expected