Hie Good Day.
Does paynow support recurring payments when granted tokenization. Your documentaion states that we can request authorization to tokenize from support@paynow.co.zw.
Your WhatsApp support says you do not support recurring payments which in my view is conflicting with your documentation.
I’ve attached screenshots and links of what I found in your documentation.
We want put it in our SAAS project that requires monthly subscriptions.
Below is an example how we can implement it in our project,
Flutter (Client) - Request Tokenization Redirect
// inside _SubscriptionScreenState
Future<String?> _createTokenizeRedirect() async {
final callable = functions.httpsCallable(‘createTokenizeTransaction’);
final result = await callable.call(<String, dynamic>{
‘uid’: user.uid,
‘amount’: 10.00,
‘reference’: ‘saas-sub-${user.uid}-${DateTime.now().millisecondsSinceEpoch}’,
‘email’: null,
});
final data = result.data as Map;
return data[‘redirectUrl’] as String?;
}
void _startTokenizeFlow() async {
final redirect = await _createTokenizeRedirect();
if (redirect == null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(‘Failed to create tokenize transaction’)));
return;
}
// Open Paynow page for card tokenization
final token = await Navigator.of(context).push(MaterialPageRoute(
builder: (_) => PaynowWebviewPage(initialUrl: redirect),
));
// Poll Firestore for token arrival (added by webhook)
final tokenDoc = FirebaseFirestore.instance.collection(‘user_tokens’).doc(user.uid);
bool found = false;
for (int i = 0; i < 20; i++) {
final snap = await tokenDoc.get();
if (snap.exists && snap.data()?[‘tokens’] != null) {
found = true;
break;
}
await Future.delayed(Duration(seconds: 2));
}
if (found) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(‘Card token stored — subscription active’)));
await FirebaseFirestore.instance.collection(‘subscriptions’).doc(user.uid).set({
‘uid’: user.uid,
‘price’: 10.0,
‘status’: ‘active’,
‘createdAt’: FieldValue.serverTimestamp(),
‘primaryTokenIndex’: 0,
}, SetOptions(merge: true));
}
}
Cloud Function (Server) – Create Tokenization Transaction
app.post(’/createTokenizeTransaction’, async (req, res) => {
const { uid, amount, reference, email } = req.body;
const paynow = createPaynow();
const payload = {
id: INTEGRATION_ID,
returnurl: RETURN_URL,
resulturl: RESULT_URL,
reference,
amount: (Number(amount)).toFixed(2),
additionalinfo: ‘tokenize’,
authemail: email || ‘’,
tokenize: ‘true’
};
const hash = paynow.generateHash(payload, INTEGRATION_KEY);
payload.hash = hash;
const r = await axios.post(‘https://www.paynow.co.zw/interface/initiatetransaction’,
qs.stringify(payload),
{ headers: { ‘Content-Type’: ‘application/x-www-form-urlencoded’ } }
);
// Parse Paynow response
const parsed = Object.fromEntries(
r.data.split(’&’).map(kv => kv.split(’=’).map(decodeURIComponent))
);
if (parsed.status?.toLowerCase() === ‘ok’) {
await db.collection(‘tokenize_attempts’).doc(reference).set({
uid, reference, pollUrl: parsed.pollurl, createdAt: admin.firestore.FieldValue.serverTimestamp()
});
res.json({ redirectUrl: parsed.redirecturl });
} else {
res.status(400).json({ error: parsed.error || ‘Tokenize transaction failed’ });
}
});
Cloud Function – Webhook Receives and Stores Token
app.post(’/paynow-webhook’, async (req, res) => {
const body = req.body || {};
const reference = body.reference;
const token = body.token || body.cardtoken || null;
const attemptDoc = await db.collection(‘tokenize_attempts’).doc(reference).get();
const uid = attemptDoc.exists ? attemptDoc.data().uid : null;
if (token && uid) {
const tokenRef = db.collection(‘user_tokens’).doc(uid);
await db.runTransaction(async (tx) => {
const snap = await tx.get(tokenRef);
let tokens = snap.exists ? snap.data().tokens || [] : [];
if (tokens.length >= 3) tokens.shift(); // maintain max 3 backup cards
tokens.push({ token, meta: body, createdAt: admin.firestore.FieldValue.serverTimestamp() });
tx.set(tokenRef, { tokens }, { merge: true });
});
}
res.status(200).send(‘OK’);
});
Cloud Function – Charge User by Saved Token
app.post(’/charge-with-token’, async (req, res) => {
const { uid, amount, reference, tokenIndex } = req.body;
const tokenDoc = await db.collection(‘user_tokens’).doc(uid).get();
const tokens = tokenDoc.data()?.tokens || [];
const tokenObj = tokens[tokenIndex || 0]; // primary or backup card
const token = tokenObj.token;
const payload = {
id: INTEGRATION_ID,
reference: reference || recurring-${uid}-${Date.now()},
amount: Number(amount).toFixed(2),
method: ‘vmc’,
merchanttrace: mt-${uid}-${Date.now()},
token
};
const paynow = createPaynow();
payload.hash = paynow.generateHash(payload, INTEGRATION_KEY);
const r = await axios.post(‘https://www.paynow.co.zw/interface/remotetransaction’,
qs.stringify(payload),
{ headers: { ‘Content-Type’: ‘application/x-www-form-urlencoded’ } }
);
const parsed = Object.fromEntries(
r.data.split(’&’).map(kv => kv.split(’=’).map(decodeURIComponent))
);
await db.collection(‘charges’).add({
uid,
reference: payload.reference,
response: parsed,
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
res.json({ success: true, parsed });
});
Please let me know if this works, or how we can implement recurring payments in our system with Paynow
