@bazileros/payfast
v0.2.5
Published
PayFast payment gateway Convex component — one-time payments, subscriptions, tokenized charges, refunds, and ITN webhooks
Readme
@bazileros/payfast — PayFast Convex Component
PayFast payment gateway integration for Convex apps. One-time payments (Custom Integration form redirect), recurring billing, tokenized charges, refunds, and ITN webhook processing.
Setup
npm install @bazileros/payfast1. Register the component
convex/convex.config.ts:
import { defineApp } from "convex/server";
import payfast from "@bazileros/payfast/convex.config";
import { v } from "convex/values";
const app = defineApp({
env: {
PAYFAST_MERCHANT_ID: v.string(),
PAYFAST_MERCHANT_KEY: v.string(),
PAYFAST_PASSPHRASE: v.string(),
PAYFAST_SANDBOX: v.optional(v.string()),
},
});
app.use(payfast, {
env: {
PAYFAST_MERCHANT_ID: app.env.PAYFAST_MERCHANT_ID,
PAYFAST_MERCHANT_KEY: app.env.PAYFAST_MERCHANT_KEY,
PAYFAST_PASSPHRASE: app.env.PAYFAST_PASSPHRASE,
PAYFAST_SANDBOX: app.env.PAYFAST_SANDBOX,
},
});
export default app;| Variable | Required | Description |
|---|---|---|
| PAYFAST_MERCHANT_ID | Yes | PayFast merchant ID |
| PAYFAST_MERCHANT_KEY | Yes | PayFast merchant key |
| PAYFAST_PASSPHRASE | Yes | PayFast passphrase |
| PAYFAST_SANDBOX | No | Set "true" for sandbox |
2. Mount ITN webhook
convex/http.ts:
import { registerRoutes } from "@bazileros/payfast";
import { httpRouter } from "convex/server";
import { components } from "./_generated/api";
const http = httpRouter();
registerRoutes(http, components.payfast, {
events: {
onPaymentComplete: (ctx, pfData) => {
// grant access, send email, etc.
},
onPaymentCancelled: (ctx, pfData) => {
// handle cancellation
},
},
});
export default http;Configure PayFast to send ITNs to https://<your-deployment>.convex.cloud/http/payfast/itn.
3. Use React hooks
Wrap your app with PayfastProvider for context-based hooks:
import { PayfastProvider, usePayfastCheckout } from "@bazileros/payfast/react";
import { components } from "../convex/_generated/api";
function App() {
return (
<PayfastProvider component={components.payfast}>
<DonateButton />
</PayfastProvider>
);
}
function DonateButton() {
// No need to pass component — it comes from context
const { generateCheckout, formActionUrl, formFields, loading } = usePayfastCheckout({
amount: 100,
itemName: "Donation",
});
return (
<form action={formActionUrl} method="POST">
{formFields && Object.entries(formFields).map(([k, v]) => (
<input key={k} type="hidden" name={k} value={v} />
))}
<button type="submit" disabled={loading}>Donate R100</button>
</form>
);
}You can also pass the component explicitly if not using the provider:
const { generateCheckout, formActionUrl, formFields } = usePayfastCheckout(components.payfast, {
amount: 100,
itemName: "Donation",
});Features
Payment method
Restrict which payment methods the buyer sees:
usePayfastCheckout({
amount: 100,
itemName: "Widget",
paymentMethod: "cc", // credit card only
});Available values:
| Code | Method |
|---|---|
| ef | EFT |
| cc | Credit Card |
| dc | Debit Card |
| mp | Masterpass |
| mc | Mobicred |
| sc | SCode |
| ss | SnapScan |
| zp | Zapper |
| mt | MoreTyme |
| rc | Store Card |
| mu | Mukuru |
| ap | Apple Pay |
| sp | Samsung Pay |
| cp | Capitec Pay |
| ab | Absa Pay |
| gp | Google Pay |
| nd | Nedbank Direct EFT |
| pf | Payflex (Buy Now, Pay Later) |
Split payments
Split a transaction across multiple receivers:
usePayfastCheckout({
amount: 100,
itemName: "Marketplace sale",
setup: JSON.stringify({
split_payments: [
{ merchant_id: "10000123", percentage: 90 },
{ merchant_id: "10000456", percentage: 10 },
],
}),
});One-time payment (Custom Integration)
const { generateCheckout, formActionUrl, formFields, loading, error } = usePayfastCheckout({
amount: 100,
itemName: "Donation",
returnUrl: "https://mysite.com/success",
cancelUrl: "https://mysite.com/cancel",
notifyUrl: "https://mydeployment.convex.cloud/http/payfast/itn",
mPaymentId: "order-123",
});Onsite payments (hosted iframe)
Not available in sandbox mode.
import { usePayfastOnsite } from "@bazileros/payfast/react";
const { generateOnsite, paymentIdentifier, loading } = usePayfastOnsite({
amount: 100,
itemName: "Widget",
returnUrl: "https://mysite.com/success",
cancelUrl: "https://mysite.com/cancel",
});
// Pass paymentIdentifier to PayFast engine.jsList transactions
import { useTransactions } from "@bazileros/payfast/react";
function TransactionList() {
const transactions = useTransactions({ userId: "user_1", limit: 10 });
return <pre>{JSON.stringify(transactions, null, 2)}</pre>;
}Subscription management
import { useSubscriptionActions } from "@bazileros/payfast/react";
function SubscriptionControls({ token }: { token: string }) {
const { pause, unpause, cancel, update } = useSubscriptionActions(token);
return (
<>
<button onClick={pause}>Pause</button>
<button onClick={unpause}>Resume</button>
<button onClick={cancel}>Cancel</button>
<button onClick={() => update({ amount: 200 })}>Change to R200</button>
</>
);
}Adhoc charges
Charge a subscription outside its billing cycle.
Not available in sandbox mode.
import { useAdhocCharge } from "@bazileros/payfast/react";
const { charge, loading, error } = useAdhocCharge();
await charge({ token: "tok_abc", amount: 50, itemName: "One-off delivery" });Refunds
Not available in sandbox mode.
import { useRefund } from "@bazileros/payfast/react";
const { refund, loading, error } = useRefund();
await refund({ ptxId: "1701500", amount: 100 });Server-side class
import { Payfast } from "@bazileros/payfast";
import { components } from "../_generated/api";
const pf = new Payfast(components.payfast, {
sandbox: true,
getUserInfo: async (ctx) => ({ userId: "user_1" }),
});
// Auto-resolves userId from getUserInfo
await pf.createCheckoutSession(ctx, { amount: 100, itemName: "Widget" });
await pf.pauseSubscription(ctx, { token: "tok_abc" });
await pf.listTransactions(ctx, { status: "COMPLETE" });
// Card update URL for subscriptions
const url = pf.getCardUpdateUrl("tok_abc", "https://mysite.com/cards");Architecture
src/component/lib.ts— Convex functions: checkout, ITN persistence, subscriptions, refundssrc/component/http.ts— ITN webhook with echo-back + signature validationsrc/component/itn.ts— Shared ITN validation (IP, echo-back, signature)src/client/index.ts—Payfastclass,registerRouteshelper,ItnEventHandlerssrc/react/index.ts— React hooks overuseQuery/useMutation/useActionsrc/react/context.tsx—PayfastProviderfor context-based hook usage
