This commit is contained in:
2026-04-12 01:06:31 +07:00
commit 10d660cbcb
1066 changed files with 228596 additions and 0 deletions

View File

@@ -0,0 +1,244 @@
#!/usr/bin/env node
/**
* Checkout Helper Script
*
* Generate checkout sessions for both SePay and Polar platforms.
*
* Usage:
* node checkout-helper.js <platform> <config-json>
*
* Platforms: sepay, polar
*
* Environment Variables:
* SEPAY_MERCHANT_ID, SEPAY_SECRET_KEY, SEPAY_ENV
* POLAR_ACCESS_TOKEN, POLAR_SERVER
*/
const crypto = require('crypto');
class CheckoutHelper {
/**
* Generate SePay checkout form fields
*/
static generateSePayCheckout(config) {
const {
merchantId,
secretKey,
orderInvoiceNumber,
orderAmount,
currency = 'VND',
successUrl,
errorUrl,
cancelUrl,
orderDescription,
operation = 'PURCHASE'
} = config;
// Validate required fields
const required = ['merchantId', 'secretKey', 'orderInvoiceNumber', 'orderAmount', 'successUrl', 'errorUrl', 'cancelUrl'];
for (const field of required) {
if (!config[field]) {
throw new Error(`Missing required field: ${field}`);
}
}
// Build fields
const fields = {
merchant_id: merchantId,
operation: operation,
order_invoice_number: orderInvoiceNumber,
order_amount: orderAmount,
currency: currency,
success_url: successUrl,
error_url: errorUrl,
cancel_url: cancelUrl,
order_description: orderDescription || `Order ${orderInvoiceNumber}`,
timestamp: new Date().toISOString()
};
// Generate HMAC SHA256 signature
const signatureData = Object.keys(fields)
.sort()
.map(key => `${key}=${fields[key]}`)
.join('&');
const signature = crypto
.createHmac('sha256', secretKey)
.update(signatureData)
.digest('hex');
fields.signature = signature;
return {
fields,
formUrl: config.env === 'production'
? 'https://pay.sepay.vn/v1/init'
: 'https://sandbox.pay.sepay.vn/v1/init',
htmlForm: this.generateHTMLForm(fields, config.env === 'production'
? 'https://pay.sepay.vn/v1/init'
: 'https://sandbox.pay.sepay.vn/v1/init')
};
}
/**
* Generate Polar checkout configuration
*/
static generatePolarCheckout(config) {
const {
productPriceId,
successUrl,
externalCustomerId,
customerEmail,
customerName,
discountId,
metadata,
embedOrigin
} = config;
// Validate required fields
if (!productPriceId) {
throw new Error('Missing required field: productPriceId');
}
if (!successUrl) {
throw new Error('Missing required field: successUrl');
}
// Must be absolute URL
if (!successUrl.startsWith('http://') && !successUrl.startsWith('https://')) {
throw new Error('successUrl must be an absolute URL');
}
const checkoutConfig = {
product_price_id: productPriceId,
success_url: successUrl
};
// Add optional fields
if (externalCustomerId) checkoutConfig.external_customer_id = externalCustomerId;
if (customerEmail) checkoutConfig.customer_email = customerEmail;
if (customerName) checkoutConfig.customer_name = customerName;
if (discountId) checkoutConfig.discount_id = discountId;
if (metadata) checkoutConfig.metadata = metadata;
if (embedOrigin) checkoutConfig.embed_origin = embedOrigin;
return {
config: checkoutConfig,
apiEndpoint: config.server === 'sandbox'
? 'https://sandbox-api.polar.sh/v1/checkouts'
: 'https://api.polar.sh/v1/checkouts',
curlCommand: this.generatePolarCurl(checkoutConfig, config.accessToken, config.server)
};
}
/**
* Generate HTML form for SePay
*/
static generateHTMLForm(fields, actionUrl) {
const inputs = Object.keys(fields)
.map(key => ` <input type="hidden" name="${key}" value="${fields[key]}" />`)
.join('\n');
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SePay Payment</title>
</head>
<body>
<form id="payment-form" action="${actionUrl}" method="POST">
${inputs}
<button type="submit">Pay Now</button>
</form>
<script>
// Auto-submit form
// document.getElementById('payment-form').submit();
</script>
</body>
</html>
`.trim();
}
/**
* Generate cURL command for Polar
*/
static generatePolarCurl(config, accessToken, server = 'production') {
const endpoint = server === 'sandbox'
? 'https://sandbox-api.polar.sh/v1/checkouts'
: 'https://api.polar.sh/v1/checkouts';
return `curl -X POST ${endpoint} \\
-H "Authorization: Bearer ${accessToken}" \\
-H "Content-Type: application/json" \\
-d '${JSON.stringify(config, null, 2)}'`;
}
}
// CLI Usage
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length < 2) {
console.log('Usage: node checkout-helper.js <platform> <config-json>');
console.log('\nPlatforms:');
console.log(' sepay - SePay checkout form generation');
console.log(' polar - Polar checkout session configuration');
console.log('\nExamples:');
console.log('\nSePay:');
console.log(' node checkout-helper.js sepay \'{"orderInvoiceNumber":"ORD001","orderAmount":100000,"successUrl":"https://example.com/success","errorUrl":"https://example.com/error","cancelUrl":"https://example.com/cancel"}\'');
console.log('\nPolar:');
console.log(' node checkout-helper.js polar \'{"productPriceId":"price_xxx","successUrl":"https://example.com/success","externalCustomerId":"user_123"}\'');
process.exit(1);
}
try {
const platform = args[0].toLowerCase();
const config = JSON.parse(args[1]);
if (platform === 'sepay') {
// Get from environment or config
config.merchantId = config.merchantId || process.env.SEPAY_MERCHANT_ID;
config.secretKey = config.secretKey || process.env.SEPAY_SECRET_KEY;
config.env = config.env || process.env.SEPAY_ENV || 'sandbox';
const result = CheckoutHelper.generateSePayCheckout(config);
console.log('✓ SePay Checkout Generated\n');
console.log('Form URL:', result.formUrl);
console.log('\nForm Fields:');
console.log(JSON.stringify(result.fields, null, 2));
console.log('\nHTML Form:');
console.log(result.htmlForm);
} else if (platform === 'polar') {
// Get from environment or config
config.accessToken = config.accessToken || process.env.POLAR_ACCESS_TOKEN;
config.server = config.server || process.env.POLAR_SERVER || 'production';
if (!config.accessToken) {
console.error('✗ Error: POLAR_ACCESS_TOKEN is required');
console.error('Set it via environment variable or in config JSON');
process.exit(1);
}
const result = CheckoutHelper.generatePolarCheckout(config);
console.log('✓ Polar Checkout Configuration Generated\n');
console.log('API Endpoint:', result.apiEndpoint);
console.log('\nCheckout Configuration:');
console.log(JSON.stringify(result.config, null, 2));
console.log('\ncURL Command:');
console.log(result.curlCommand);
} else {
console.error(`✗ Error: Unknown platform '${platform}'`);
console.error('Supported platforms: sepay, polar');
process.exit(1);
}
} catch (error) {
console.error('✗ Error:', error.message);
process.exit(1);
}
}
module.exports = CheckoutHelper;