- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
184 lines
6.7 KiB
JavaScript
184 lines
6.7 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* Test Stripe Integration for Koha Donation System
|
||
* Tests the complete donation flow with Stripe test mode
|
||
*/
|
||
|
||
require('dotenv').config();
|
||
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
||
|
||
const COLORS = {
|
||
reset: '\x1b[0m',
|
||
green: '\x1b[32m',
|
||
red: '\x1b[31m',
|
||
yellow: '\x1b[33m',
|
||
blue: '\x1b[34m',
|
||
cyan: '\x1b[36m'
|
||
};
|
||
|
||
function log(color, symbol, message) {
|
||
console.log(`${color}${symbol} ${message}${COLORS.reset}`);
|
||
}
|
||
|
||
async function testStripeIntegration() {
|
||
console.log('\n' + '═'.repeat(60));
|
||
console.log(' Stripe Integration Test - Koha Donation System');
|
||
console.log('═'.repeat(60) + '\n');
|
||
|
||
let allTestsPassed = true;
|
||
|
||
try {
|
||
// Test 1: Verify environment variables
|
||
console.log(`${COLORS.blue}▶ Test 1: Environment Variables${COLORS.reset}\n`);
|
||
|
||
const requiredVars = {
|
||
'STRIPE_SECRET_KEY': process.env.STRIPE_SECRET_KEY,
|
||
'STRIPE_PUBLISHABLE_KEY': process.env.STRIPE_PUBLISHABLE_KEY,
|
||
'STRIPE_KOHA_PRODUCT_ID': process.env.STRIPE_KOHA_PRODUCT_ID,
|
||
'STRIPE_KOHA_5_PRICE_ID': process.env.STRIPE_KOHA_5_PRICE_ID,
|
||
'STRIPE_KOHA_15_PRICE_ID': process.env.STRIPE_KOHA_15_PRICE_ID,
|
||
'STRIPE_KOHA_50_PRICE_ID': process.env.STRIPE_KOHA_50_PRICE_ID
|
||
};
|
||
|
||
for (const [key, value] of Object.entries(requiredVars)) {
|
||
if (!value || value.includes('placeholder') || value.includes('PLACEHOLDER')) {
|
||
log(COLORS.red, '✗', `${key} is missing or placeholder`);
|
||
allTestsPassed = false;
|
||
} else {
|
||
const displayValue = key.includes('KEY') ? value.substring(0, 20) + '...' : value;
|
||
log(COLORS.green, '✓', `${key}: ${displayValue}`);
|
||
}
|
||
}
|
||
|
||
// Test 2: Verify product exists
|
||
console.log(`\n${COLORS.blue}▶ Test 2: Verify Stripe Product${COLORS.reset}\n`);
|
||
|
||
try {
|
||
const product = await stripe.products.retrieve(process.env.STRIPE_KOHA_PRODUCT_ID);
|
||
log(COLORS.green, '✓', `Product found: ${product.name}`);
|
||
console.log(` ID: ${product.id}`);
|
||
console.log(` Active: ${product.active}`);
|
||
} catch (error) {
|
||
log(COLORS.red, '✗', `Product not found: ${error.message}`);
|
||
allTestsPassed = false;
|
||
}
|
||
|
||
// Test 3: Verify prices exist
|
||
console.log(`\n${COLORS.blue}▶ Test 3: Verify Stripe Prices${COLORS.reset}\n`);
|
||
|
||
const priceIds = [
|
||
{ name: 'Foundation ($5/month)', id: process.env.STRIPE_KOHA_5_PRICE_ID },
|
||
{ name: 'Advocate ($15/month)', id: process.env.STRIPE_KOHA_15_PRICE_ID },
|
||
{ name: 'Champion ($50/month)', id: process.env.STRIPE_KOHA_50_PRICE_ID }
|
||
];
|
||
|
||
for (const priceConfig of priceIds) {
|
||
try {
|
||
const price = await stripe.prices.retrieve(priceConfig.id);
|
||
const amount = price.unit_amount / 100;
|
||
const currency = price.currency.toUpperCase();
|
||
const interval = price.recurring ? `/${price.recurring.interval}` : '(one-time)';
|
||
log(COLORS.green, '✓', `${priceConfig.name}: ${currency} $${amount}${interval}`);
|
||
} catch (error) {
|
||
log(COLORS.red, '✗', `${priceConfig.name} not found: ${error.message}`);
|
||
allTestsPassed = false;
|
||
}
|
||
}
|
||
|
||
// Test 4: Create test checkout session (Foundation tier)
|
||
console.log(`\n${COLORS.blue}▶ Test 4: Create Test Checkout Session${COLORS.reset}\n`);
|
||
|
||
try {
|
||
const session = await stripe.checkout.sessions.create({
|
||
mode: 'subscription',
|
||
payment_method_types: ['card'],
|
||
line_items: [{
|
||
price: process.env.STRIPE_KOHA_5_PRICE_ID,
|
||
quantity: 1
|
||
}],
|
||
success_url: `${process.env.FRONTEND_URL || 'http://localhost:9000'}/koha/success.html?session_id={CHECKOUT_SESSION_ID}`,
|
||
cancel_url: `${process.env.FRONTEND_URL || 'http://localhost:9000'}/koha.html`,
|
||
metadata: {
|
||
frequency: 'monthly',
|
||
tier: '5',
|
||
test: 'true'
|
||
},
|
||
customer_email: 'test@example.com'
|
||
});
|
||
|
||
log(COLORS.green, '✓', `Checkout session created: ${session.id}`);
|
||
console.log(` Status: ${session.status}`);
|
||
console.log(` Amount: ${session.amount_total / 100} ${session.currency.toUpperCase()}`);
|
||
console.log(` URL: ${session.url.substring(0, 60)}...`);
|
||
|
||
// Clean up test session
|
||
await stripe.checkout.sessions.expire(session.id);
|
||
log(COLORS.cyan, 'ℹ', 'Test session expired (cleanup)');
|
||
|
||
} catch (error) {
|
||
log(COLORS.red, '✗', `Failed to create checkout session: ${error.message}`);
|
||
allTestsPassed = false;
|
||
}
|
||
|
||
// Test 5: Create test one-time donation checkout
|
||
console.log(`\n${COLORS.blue}▶ Test 5: Create One-Time Donation Checkout${COLORS.reset}\n`);
|
||
|
||
try {
|
||
const oneTimeSession = await stripe.checkout.sessions.create({
|
||
mode: 'payment',
|
||
payment_method_types: ['card'],
|
||
line_items: [{
|
||
price_data: {
|
||
currency: 'nzd',
|
||
product: process.env.STRIPE_KOHA_PRODUCT_ID,
|
||
unit_amount: 2500, // $25.00 NZD
|
||
},
|
||
quantity: 1
|
||
}],
|
||
success_url: `${process.env.FRONTEND_URL || 'http://localhost:9000'}/koha/success.html?session_id={CHECKOUT_SESSION_ID}`,
|
||
cancel_url: `${process.env.FRONTEND_URL || 'http://localhost:9000'}/koha.html`,
|
||
metadata: {
|
||
frequency: 'one_time',
|
||
amount: '2500',
|
||
test: 'true'
|
||
},
|
||
customer_email: 'test@example.com'
|
||
});
|
||
|
||
log(COLORS.green, '✓', `One-time donation session created: ${oneTimeSession.id}`);
|
||
console.log(` Status: ${oneTimeSession.status}`);
|
||
console.log(` Amount: ${oneTimeSession.amount_total / 100} ${oneTimeSession.currency.toUpperCase()}`);
|
||
|
||
// Clean up test session
|
||
await stripe.checkout.sessions.expire(oneTimeSession.id);
|
||
log(COLORS.cyan, 'ℹ', 'Test session expired (cleanup)');
|
||
|
||
} catch (error) {
|
||
log(COLORS.red, '✗', `Failed to create one-time donation: ${error.message}`);
|
||
allTestsPassed = false;
|
||
}
|
||
|
||
// Summary
|
||
console.log('\n' + '═'.repeat(60));
|
||
if (allTestsPassed) {
|
||
log(COLORS.green, '✅', 'All integration tests passed!');
|
||
console.log('\n📋 Next steps:');
|
||
console.log(' 1. Start local server: npm start');
|
||
console.log(' 2. Test donation form at: http://localhost:9000/koha.html');
|
||
console.log(' 3. Use test card: 4242 4242 4242 4242');
|
||
console.log(' 4. Set up webhooks: ./scripts/stripe-webhook-setup.sh');
|
||
} else {
|
||
log(COLORS.red, '❌', 'Some tests failed. Please fix issues above.');
|
||
}
|
||
console.log('═'.repeat(60) + '\n');
|
||
|
||
} catch (error) {
|
||
log(COLORS.red, '✗', `Test suite error: ${error.message}`);
|
||
console.error('\nFull error:', error);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// Run tests
|
||
testStripeIntegration();
|