AI & LLM integration guide
Are you building your Divit integration with the help of an AI coding assistant like Gemini, Cursor, GitHub Copilot, ChatGPT, or Cline?
This guide is designed specifically for you to copy and feed directly into your LLM prompt, or save as an instructions file in your workspace (such as .cursorrules or .clinerules). Providing this high-density reference sheet allows your AI coding assistant to generate perfect, ready-to-run integration code on the first try.
1. Prerequisites & Environment Variables
Before asking your LLM to write code, make sure you configure your environment variables (e.g. in your .env or configuration config helper). Feed these variable names to your LLM:
DIVIT_ENV: Current environment mode (sandboxorproduction).DIVIT_API_BASE_URL: Base API URL (https://sandbox-api.divit.devorhttps://api.divit.com.hk).DIVIT_API_KEY: Secret merchant credential starting withdvt_.DIVIT_SIGNATURE_KEY: Signature secret configured in the Divit Merchant Admin Portal for webhook validation.
2. Fast Copy-Paste Prompt for LLMs
If you are using a chat-based LLM (like Gemini or ChatGPT) to write your integration, copy and paste the markdown block below as your starting prompt:
I want to integrate Divit Pay Now (FPS bank-to-bank checkout) into my backend.
Here is the official Divit reference sheet. Please write clean, secure code for:
1. Initializing the API client (Phase 1)
2. Creating a payment order (POST /directpay/o/order) (Phase 2)
3. Directing client browsers/webviews (Phase 3)
4. Handling secure asynchronous webhook event callbacks (success = 2001, expired = 4001) with cryptographic verification using X-DIVIT-SIGNATURE and HMAC-SHA256 (Phase 4)
5. Building a fallback endpoint to query order status manually (GET /paynow/orders/{orderID}/status) (Phase 5)
---
### Divit API Cheat-Sheet:
- **Sandbox API Host**: https://sandbox-api.divit.dev
- **Production API Host**: https://api.divit.com.hk
#### API Authentication Header:
api-key: <merchant_api_key>
Content-Type: application/json
#### Create Order Payload Structure:
POST /directpay/o/order
{
"order": {
"totalAmount": {
"amount": 12050, // in smallest subunit (cents), e.g. HKD 120.50 -> 12050
"currency": "HKD"
},
"webhookSuccess": "https://your-site.com/success",
"webhookFailure": "https://your-site.com/cancel",
"webhookEvents": "https://your-api.com/api/webhooks/divit",
"merchantRef": "ORDER-10024A"
},
"customer": {
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]",
"tel": "+85291234567",
"language": "en"
}
}
Response: { "code": 0, "message": "OK", "data": { "redirectURI": "...", "orderID": "..." } }
#### Webhook Signature Verification Algorithm:
- Header: X-DIVIT-SIGNATURE (format: t=timestamp,s1=signature_hash)
- Formula: HMAC_SHA256(secret_key, timestamp + "." + rawBodyString)
- NOTE: You must use the raw, unparsed request body string for HMAC calculation. Re-stringifying parsed JSON will corrupt formatting and break signature verification.
---
Please generate the code in <Your Programming Language / Framework>.
3. Reusable Code Templates
These verified templates can be copy-pasted or referenced directly by your LLM to implement order creation and webhook validation.
- Node.js (Express)
- Python (FastAPI)
const express = require('express');
const crypto = require('crypto');
const app = express();
const DIVIT_API_KEY = process.env.DIVIT_API_KEY;
const DIVIT_SIGNATURE_KEY = process.env.DIVIT_SIGNATURE_KEY;
const DIVIT_API_BASE = process.env.DIVIT_API_BASE_URL || 'https://sandbox-api.divit.dev';
// IMPORTANT: Express must capture the raw body for signature verification
app.post('/api/webhooks/divit', express.raw({ type: 'application/json' }), (req, res) => {
const signatureHeader = req.headers['x-divit-signature'];
const rawBody = req.body.toString('utf-8');
// Verify signature
if (!verifyDivitSignature(signatureHeader, rawBody, DIVIT_SIGNATURE_KEY)) {
return res.status(401).send('Invalid Signature');
}
// Parse body after verification
const payload = JSON.parse(rawBody);
const { eventId } = payload.event;
const { OrderID, MerchantRef } = payload.eventData;
if (eventId === 2001) {
// Payment Succeeded - Fulfill the order
console.log(`Payment successful for order ${MerchantRef} (Divit ID: ${OrderID})`);
} else if (eventId === 4001) {
// Payment Expired
console.log(`Payment expired for order ${MerchantRef}`);
}
res.status(200).json({ received: true });
});
function verifyDivitSignature(headerVal, rawBody, signatureKey) {
if (!headerVal || !rawBody || !signatureKey) return false;
const params = {};
headerVal.split(',').forEach(item => {
const [key, value] = item.trim().split('=');
params[key] = value;
});
const timestamp = params['t'];
const expectedSignature = params['s1'];
if (!timestamp || !expectedSignature) return false;
const signatureContent = `${timestamp}.${rawBody}`;
const calculatedSignature = crypto
.createHmac('sha256', signatureKey)
.update(signatureContent)
.digest('base64');
try {
return crypto.timingSafeEqual(
Buffer.from(calculatedSignature),
Buffer.from(expectedSignature)
);
} catch (e) {
return calculatedSignature === expectedSignature;
}
}
import hmac
import hashlib
import base64
from fastapi import FastAPI, Request, Header, HTTPException, status
app = FastAPI()
DIVIT_SIGNATURE_KEY = "your_secret_signature_key"
def verify_divit_signature(header_val: str, raw_body_bytes: bytes, signature_key: str) -> bool:
if not header_val or not raw_body_bytes or not signature_key:
return False
try:
parts = {item.split('=')[0].strip(): item.split('=')[1].strip() for item in header_val.split(',')}
timestamp = parts.get('t')
expected_sig = parts.get('s1')
if not timestamp or not expected_sig:
return False
sig_content = f"{timestamp}.".encode('utf-8') + raw_body_bytes
calculated_sig = base64.b64encode(
hmac.new(signature_key.encode('utf-8'), sig_content, hashlib.sha256).digest()
).decode('utf-8')
return hmac.compare_digest(calculated_sig, expected_sig)
except Exception:
return False
@app.post("/api/webhooks/divit")
async def handle_webhook(request: Request, x_divit_signature: str = Header(None)):
raw_body = await request.body()
if not verify_divit_signature(x_divit_signature, raw_body, DIVIT_SIGNATURE_KEY):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid Webhook Signature"
)
payload = await request.json()
event_id = payload["event"]["eventId"]
order_id = payload["eventData"]["OrderID"]
merchant_ref = payload["eventData"]["MerchantRef"]
if event_id == 2001:
# Fulfill order
print(f"Fulfilling order: {merchant_ref}")
elif event_id == 4001:
# Release inventory
print(f"Order expired: {merchant_ref}")
return {"status": "success"}
4. Workspace Rule File (e.g., .cursorrules)
If you are using Cursor, VS Code + Cline, or Copilot, you can place a file named .cursorrules or .github/copilot-instructions.md at the root of your project directory.
Copy and paste the template below to ensure your workspace LLM never makes integration errors when coding with Divit:
# Divit Pay Now Integration Rules
You are an expert assistant configured to write integrations for Divit Pay Now, a bank-to-bank FPS checkout service in Hong Kong.
## Environment Variables to Bind:
- `DIVIT_ENV`: "sandbox" or "production"
- `DIVIT_API_BASE_URL`: "https://sandbox-api.divit.dev" or "https://api.divit.com.hk"
- `DIVIT_API_KEY`: Secret merchant key (starts with "dvt_")
- `DIVIT_SIGNATURE_KEY`: Signature webhook verification secret
## Core Rules & Pitfalls to Avoid:
1. **Webhook Raw Request Body (CRITICAL)**:
- When verifying the `X-DIVIT-SIGNATURE` header, ALWAYS use the RAW, unparsed bytes/string of the incoming HTTP request body.
- DO NOT parse the body into a JSON object and then call `JSON.stringify()` or similar, as character spacing/ordering differences will result in a signature mismatch.
2. **Currency Subunit Rule**:
- Divit uses the smallest currency subunit format for transaction values.
- HKD amounts must be multiplied by 100 and parsed as an integer (e.g., HKD $120.50 -> `12050`).
3. **Signature Verification Signature Content**:
- Parse the `X-DIVIT-SIGNATURE` header to extract the timestamp `t` and signature `s1`.
- Reconstruct the signature content string exactly as: `t + "." + rawRequestBody`.
- Generate the Base64 HMAC-SHA256 signature using the local `DIVIT_SIGNATURE_KEY` as secret.
- Use a constant-time comparison helper (like `crypto.timingSafeEqual` in Node or `hmac.compare_digest` in Python) to prevent timing attacks.
4. **Security Best Practice**:
- Never expose the `api-key` or `signature-key` on the client-side. Keep them inside backend environment variables (`process.env` or `.env` files).
5. Sandbox Testing & Verification Guide
To test and simulate the payment journey end-to-end without spending real currency:
- Initiate a standard order creation while in sandbox mode (
DIVIT_ENV=sandbox/ Base URLhttps://sandbox-api.divit.dev). - Redirect to the generated
redirectURIreturned in the response object. - Open the FPS Payment Simulator at bank.divit.dev.
- Input the reference ID or scan the sandbox payment QR code on the payment screen to initiate a mockup payment.
- Confirm that your callback server receives the webhook event
2001(Payment Success) and processes the transaction correctly.