Guide

React guide to trigger the YKNPG transaction flow from a page (development demo).

React guide to trigger the YKNPG transaction flow from a page (development demo).

Security note

  • The bearer token must stay on a server. For production, create a small backend endpoint that adds the Authorization header. The example below uses the token from .env.local for local testing only.

Prerequisites

  • Node.js 18+, npm.
  1. Create app
  • npm create vite@latest demo-react -- --template react
  • cd demo-react && npm install
  • npm install axios
  1. Add environment
  • File: .env.local
VITE_YKNPG_API_TOKEN=replace-with-your-token
VITE_YKNPG_BASE_URL=https://yknpg.ngoul.com/api/v1
  1. Add helper API client
  • File: src/api.js
import axios from "axios";

const client = axios.create({
  baseURL: import.meta.env.VITE_YKNPG_BASE_URL || "https://yknpg.ngoul.com/api/v1",
  headers: { Authorization: `Bearer ${import.meta.env.VITE_YKNPG_API_TOKEN}` },
  timeout: 10000,
});

export async function startTransaction(amount, phone) {
  const createPayload = {
    amount,
    provider: "ORANGE_MONEY",
    user_infos: { number: phone },
    callback_url: "http://example.com",
    callback_method: "POST",
    your_message: "pay this",
    your_order_ref: "demo-1",
    provider_fees_on_customer: true,
  };
  const { data: created } = await client.post("/transactions/", createPayload);
  const { data: paid } = await client.post(`/transactions/${created.uuid}/pay/`);
  let status = paid.status;
  while (status === "new" || status === "paying") {
    await new Promise((r) => setTimeout(r, 5000));
    const { data } = await client.get(`/transactions/${created.uuid}/`);
    status = data.status;
  }
  return { created, paid, finalStatus: status };
}
  1. Create UI
  • File: src/App.jsx
import { useState } from "react";
import { startTransaction } from "./api";

function App() {
  const [phone, setPhone] = useState("");
  const [amount, setAmount] = useState(1000);
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const submit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError(null);
    try {
      const data = await startTransaction(Number(amount), phone);
      setResult(data);
    } catch (err) {
      setError(err.response?.data || err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <main style={{ maxWidth: 480, margin: "2rem auto", fontFamily: "sans-serif" }}>
      <h1>Start payment</h1>
      <form onSubmit={submit}>
        <label>Phone</label>
        <input value={phone} onChange={(e) => setPhone(e.target.value)} required />
        <label>Amount</label>
        <input
          type="number"
          value={amount}
          min="1"
          onChange={(e) => setAmount(e.target.value)}
          required
        />
        <button type="submit" disabled={loading}>
          {loading ? "Processing..." : "Pay"}
        </button>
      </form>
      {error && <p style={{ color: "red" }}>{String(error)}</p>}
      {result && (
        <section>
          <p>Gateway instructions: {result.created.instructions}</p>
          <p>Post-pay instructions: {result.paid.instructions}</p>
          <p>Final status: {result.finalStatus}</p>
        </section>
      )}
    </main>
  );
}

export default App;
  1. Run
  • npm run dev
  • Open the printed local URL and test.

Notes

  • Move the token to a backend API before releasing to users.