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.localfor local testing only.
Prerequisites
- Node.js 18+, npm.
- Create app
npm create vite@latest demo-react -- --template reactcd demo-react && npm installnpm install axios
- Add environment
- File:
.env.local
VITE_YKNPG_API_TOKEN=replace-with-your-token
VITE_YKNPG_BASE_URL=https://yknpg.ngoul.com/api/v1
- 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 };
}
- 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;
- Run
npm run dev- Open the printed local URL and test.
Notes
- Move the token to a backend API before releasing to users.