Files
english/.opencode/skills/payment-integration/scripts/sepay-webhook-verify.js
2026-04-12 01:06:31 +07:00

194 lines
5.4 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* SePay Webhook Verification Script
*
* Verifies SePay webhook authenticity and processes transaction data.
* Supports API Key and OAuth2 authentication.
*
* Usage:
* node sepay-webhook-verify.js <webhook-payload-json>
*
* Environment Variables:
* SEPAY_WEBHOOK_AUTH_TYPE - Authentication type (api_key or oauth2 or none)
* SEPAY_WEBHOOK_API_KEY - API key for verification (if using api_key)
*/
const crypto = require('crypto');
class SePayWebhookVerifier {
constructor(authType = 'none', apiKey = null) {
this.authType = authType;
this.apiKey = apiKey;
}
/**
* Verify webhook authenticity
*/
verifyAuthentication(headers) {
if (this.authType === 'none') {
console.log('⚠️ Warning: No authentication configured');
return true;
}
if (this.authType === 'api_key') {
const authHeader = headers['authorization'] || headers['Authorization'];
if (!authHeader) {
throw new Error('Missing Authorization header');
}
const expectedAuth = `Apikey ${this.apiKey}`;
if (authHeader !== expectedAuth) {
throw new Error('Invalid API key');
}
return true;
}
if (this.authType === 'oauth2') {
const authHeader = headers['authorization'] || headers['Authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new Error('Missing or invalid OAuth2 Bearer token');
}
// In production, verify token with OAuth2 provider
console.log('✓ OAuth2 token present (full verification needed in production)');
return true;
}
throw new Error(`Unknown auth type: ${this.authType}`);
}
/**
* Check for duplicate transactions
*/
isDuplicate(transactionId, processedIds = new Set()) {
return processedIds.has(transactionId);
}
/**
* Validate webhook payload structure
*/
validatePayload(payload) {
const required = [
'id',
'gateway',
'transactionDate',
'accountNumber',
'transferType',
'transferAmount',
'referenceCode'
];
for (const field of required) {
if (!(field in payload)) {
throw new Error(`Missing required field: ${field}`);
}
}
// Validate transfer type
if (!['in', 'out'].includes(payload.transferType)) {
throw new Error(`Invalid transferType: ${payload.transferType}`);
}
// Validate amount
if (typeof payload.transferAmount !== 'number' || payload.transferAmount <= 0) {
throw new Error('Invalid transferAmount');
}
return true;
}
/**
* Process webhook payload
*/
process(payload, headers = {}) {
try {
// 1. Verify authentication
this.verifyAuthentication(headers);
// 2. Validate payload structure
this.validatePayload(payload);
// 3. Extract transaction data
const transaction = {
id: payload.id,
gateway: payload.gateway,
transactionDate: new Date(payload.transactionDate),
accountNumber: payload.accountNumber,
code: payload.code || null,
content: payload.content || '',
transferType: payload.transferType,
transferAmount: payload.transferAmount,
accumulated: payload.accumulated || 0,
subAccount: payload.subAccount || null,
referenceCode: payload.referenceCode
};
return {
success: true,
transaction,
isIncoming: transaction.transferType === 'in',
isOutgoing: transaction.transferType === 'out'
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
// CLI Usage
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('Usage: node sepay-webhook-verify.js <webhook-payload-json>');
console.log('\nEnvironment Variables:');
console.log(' SEPAY_WEBHOOK_AUTH_TYPE - Authentication type (api_key, oauth2, none)');
console.log(' SEPAY_WEBHOOK_API_KEY - API key for verification');
process.exit(1);
}
try {
const payload = JSON.parse(args[0]);
const authType = process.env.SEPAY_WEBHOOK_AUTH_TYPE || 'none';
const apiKey = process.env.SEPAY_WEBHOOK_API_KEY || null;
const verifier = new SePayWebhookVerifier(authType, apiKey);
// Mock headers for CLI testing
const headers = {};
if (authType === 'api_key' && apiKey) {
headers['Authorization'] = `Apikey ${apiKey}`;
}
const result = verifier.process(payload, headers);
if (result.success) {
console.log('✓ Webhook verified successfully\n');
console.log('Transaction Details:');
console.log(` ID: ${result.transaction.id}`);
console.log(` Gateway: ${result.transaction.gateway}`);
console.log(` Type: ${result.transaction.transferType}`);
console.log(` Amount: ${result.transaction.transferAmount.toLocaleString('vi-VN')} VND`);
console.log(` Reference: ${result.transaction.referenceCode}`);
console.log(` Content: ${result.transaction.content || 'N/A'}`);
console.log(`\n Incoming: ${result.isIncoming ? 'Yes' : 'No'}`);
console.log(` Outgoing: ${result.isOutgoing ? 'Yes' : 'No'}`);
} else {
console.error('✗ Verification failed:', result.error);
process.exit(1);
}
} catch (error) {
console.error('✗ Error:', error.message);
process.exit(1);
}
}
module.exports = SePayWebhookVerifier;