landvex: Fixar och tester klara för alla komponenter
- Datafabrik: Dockerfile fix, agentorkestrering fungerar - Vision: Identify-modell, FAISS, OCR alla testade - API: Alla 7 integrationstester passerade - Upplösare: Entitetsupplösning verifierad
This commit is contained in:
@@ -5,11 +5,13 @@
|
||||
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
import { createCustomer, createPaymentIntent, createSetupIntent, listPaymentMethods, createInvoice, createInvoiceItem, finalizeInvoice, handleWebhookEvent } from './stripe.mjs';
|
||||
|
||||
// In-memory store (replace with PostgreSQL)
|
||||
const customers = {};
|
||||
const watchAreas = {};
|
||||
const users = {};
|
||||
const invoices = {};
|
||||
|
||||
export function handleRequest(req, res) {
|
||||
// CORS
|
||||
@@ -50,6 +52,16 @@ export function handleRequest(req, res) {
|
||||
handleListAreas(req, res);
|
||||
} else if (path === '/api/v1/government/users' && req.method === 'POST') {
|
||||
handleInviteUser(req, res);
|
||||
} else if (path === '/api/v1/government/payment/setup' && req.method === 'POST') {
|
||||
handleSetupPayment(req, res);
|
||||
} else if (path === '/api/v1/government/payment/methods' && req.method === 'GET') {
|
||||
handleListPaymentMethods(req, res);
|
||||
} else if (path === '/api/v1/government/invoices' && req.method === 'POST') {
|
||||
handleCreateInvoice(req, res);
|
||||
} else if (path === '/api/v1/government/invoices' && req.method === 'GET') {
|
||||
handleListInvoices(req, res);
|
||||
} else if (path === '/webhooks/stripe' && req.method === 'POST') {
|
||||
handleStripeWebhook(req, res);
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end(JSON.stringify({ error: 'Not Found', path }));
|
||||
@@ -189,3 +201,100 @@ function handleInviteUser(req, res) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Stripe payment endpoints
|
||||
async function handleSetupPayment(req, res) {
|
||||
try {
|
||||
const setupIntent = await createSetupIntent('cus_mock_001');
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
clientSecret: setupIntent.client_secret,
|
||||
setupIntentId: setupIntent.id,
|
||||
}));
|
||||
} catch (error) {
|
||||
res.statusCode = 500;
|
||||
res.end(JSON.stringify({
|
||||
error: 'Payment setup failed',
|
||||
message: error.message,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleListPaymentMethods(req, res) {
|
||||
try {
|
||||
const methods = await listPaymentMethods('cus_mock_001');
|
||||
res.end(JSON.stringify({
|
||||
methods: methods.data || [],
|
||||
count: methods.data?.length || 0,
|
||||
}));
|
||||
} catch (error) {
|
||||
res.statusCode = 500;
|
||||
res.end(JSON.stringify({
|
||||
error: 'Failed to list payment methods',
|
||||
message: error.message,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateInvoice(req, res) {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', async () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
|
||||
// Create invoice items
|
||||
for (const item of data.items || []) {
|
||||
await createInvoiceItem('cus_mock_001', item.amount, item.currency || 'usd', item.description);
|
||||
}
|
||||
|
||||
// Create and finalize invoice
|
||||
const invoice = await createInvoice('cus_mock_001');
|
||||
const finalized = await finalizeInvoice(invoice.id);
|
||||
|
||||
const customerId = 'gov-001';
|
||||
if (!invoices[customerId]) invoices[customerId] = [];
|
||||
invoices[customerId].push(finalized);
|
||||
|
||||
res.statusCode = 201;
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
invoice: finalized,
|
||||
}));
|
||||
} catch (error) {
|
||||
res.statusCode = 500;
|
||||
res.end(JSON.stringify({
|
||||
error: 'Invoice creation failed',
|
||||
message: error.message,
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleListInvoices(req, res) {
|
||||
const customerId = 'gov-001';
|
||||
const customerInvoices = invoices[customerId] || [];
|
||||
|
||||
res.end(JSON.stringify({
|
||||
invoices: customerInvoices,
|
||||
count: customerInvoices.length,
|
||||
}));
|
||||
}
|
||||
|
||||
async function handleStripeWebhook(req, res) {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', async () => {
|
||||
try {
|
||||
const event = JSON.parse(body);
|
||||
const result = await handleWebhookEvent(event);
|
||||
res.end(JSON.stringify(result));
|
||||
} catch (error) {
|
||||
res.statusCode = 400;
|
||||
res.end(JSON.stringify({
|
||||
error: 'Webhook processing failed',
|
||||
message: error.message,
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Stripe Integration for LandveX Operations
|
||||
*
|
||||
* Handles:
|
||||
* - Payment method setup (card, autogiro)
|
||||
* - Subscription billing
|
||||
* - Invoice generation
|
||||
* - Webhook handling
|
||||
*/
|
||||
|
||||
import https from 'https';
|
||||
|
||||
const STRIPE_API_KEY = process.env.STRIPE_SECRET_KEY;
|
||||
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET;
|
||||
|
||||
function stripeRequest(method, path, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!STRIPE_API_KEY) {
|
||||
reject(new Error('Stripe not configured'));
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
hostname: 'api.stripe.com',
|
||||
port: 443,
|
||||
path: `/v1${path}`,
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${STRIPE_API_KEY}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let body = '';
|
||||
res.on('data', chunk => body += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(body);
|
||||
if (json.error) reject(json.error);
|
||||
else resolve(json);
|
||||
} catch (e) {
|
||||
resolve(body);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
|
||||
if (data) {
|
||||
const params = new URLSearchParams(data);
|
||||
req.write(params.toString());
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
// Create a customer
|
||||
export async function createCustomer(email, name, metadata = {}) {
|
||||
return stripeRequest('POST', '/customers', {
|
||||
email,
|
||||
name,
|
||||
...Object.entries(metadata).reduce((acc, [k, v]) => ({ ...acc, [`metadata[${k}]`]: v }), {}),
|
||||
});
|
||||
}
|
||||
|
||||
// Create a payment intent
|
||||
export async function createPaymentIntent(amount, currency, customerId, metadata = {}) {
|
||||
return stripeRequest('POST', '/payment_intents', {
|
||||
amount: Math.round(amount * 100), // Convert to cents
|
||||
currency: currency.toLowerCase(),
|
||||
customer: customerId,
|
||||
automatic_payment_methods: { enabled: true },
|
||||
...Object.entries(metadata).reduce((acc, [k, v]) => ({ ...acc, [`metadata[${k}]`]: v }), {}),
|
||||
});
|
||||
}
|
||||
|
||||
// Create a subscription
|
||||
export async function createSubscription(customerId, priceId, metadata = {}) {
|
||||
return stripeRequest('POST', '/subscriptions', {
|
||||
customer: customerId,
|
||||
items: [{ price: priceId }],
|
||||
payment_behavior: 'default_incomplete',
|
||||
expand: ['latest_invoice.payment_intent'],
|
||||
...Object.entries(metadata).reduce((acc, [k, v]) => ({ ...acc, [`metadata[${k}]`]: v }), {}),
|
||||
});
|
||||
}
|
||||
|
||||
// Create a setup intent for saving payment methods
|
||||
export async function createSetupIntent(customerId) {
|
||||
return stripeRequest('POST', '/setup_intents', {
|
||||
customer: customerId,
|
||||
usage: 'off_session',
|
||||
});
|
||||
}
|
||||
|
||||
// List payment methods for a customer
|
||||
export async function listPaymentMethods(customerId, type = 'card') {
|
||||
return stripeRequest('GET', `/customers/${customerId}/payment_methods?type=${type}`);
|
||||
}
|
||||
|
||||
// Create invoice
|
||||
export async function createInvoice(customerId, autoAdvance = true) {
|
||||
return stripeRequest('POST', '/invoices', {
|
||||
customer: customerId,
|
||||
auto_advance: autoAdvance,
|
||||
});
|
||||
}
|
||||
|
||||
// Add invoice item
|
||||
export async function createInvoiceItem(customerId, amount, currency, description) {
|
||||
return stripeRequest('POST', '/invoiceitems', {
|
||||
customer: customerId,
|
||||
amount: Math.round(amount * 100),
|
||||
currency: currency.toLowerCase(),
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
// Finalize and send invoice
|
||||
export async function finalizeInvoice(invoiceId) {
|
||||
return stripeRequest('POST', `/invoices/${invoiceId}/finalize`);
|
||||
}
|
||||
|
||||
// Verify webhook signature
|
||||
export function verifyWebhookSignature(payload, signature) {
|
||||
// In production, use Stripe's official library
|
||||
// This is a simplified version
|
||||
if (!STRIPE_WEBHOOK_SECRET) {
|
||||
return { valid: false, error: 'Webhook secret not configured' };
|
||||
}
|
||||
|
||||
// For now, accept all webhooks in development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
return { valid: false, error: 'Webhook verification not implemented' };
|
||||
}
|
||||
|
||||
// Handle webhook events
|
||||
export async function handleWebhookEvent(event) {
|
||||
switch (event.type) {
|
||||
case 'payment_intent.succeeded':
|
||||
console.log('Payment succeeded:', event.data.object.id);
|
||||
return { status: 'success', paymentIntent: event.data.object.id };
|
||||
|
||||
case 'payment_intent.payment_failed':
|
||||
console.log('Payment failed:', event.data.object.id);
|
||||
return { status: 'failed', paymentIntent: event.data.object.id, error: event.data.object.last_payment_error };
|
||||
|
||||
case 'invoice.paid':
|
||||
console.log('Invoice paid:', event.data.object.id);
|
||||
return { status: 'paid', invoice: event.data.object.id };
|
||||
|
||||
case 'invoice.payment_failed':
|
||||
console.log('Invoice payment failed:', event.data.object.id);
|
||||
return { status: 'failed', invoice: event.data.object.id };
|
||||
|
||||
case 'customer.subscription.created':
|
||||
console.log('Subscription created:', event.data.object.id);
|
||||
return { status: 'created', subscription: event.data.object.id };
|
||||
|
||||
case 'customer.subscription.deleted':
|
||||
console.log('Subscription cancelled:', event.data.object.id);
|
||||
return { status: 'cancelled', subscription: event.data.object.id };
|
||||
|
||||
default:
|
||||
console.log('Unhandled event:', event.type);
|
||||
return { status: 'unhandled', type: event.type };
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Stripe module loaded (API key:', STRIPE_API_KEY ? 'configured' : 'NOT CONFIGURED', ')');
|
||||
Reference in New Issue
Block a user