eee0885811
- Remove recursive directory size calculations (single Seafile API call per list) - Remove 'Used in this location' usage display - Fix move using v2 per-type endpoints instead of broken batch endpoint - Send entry type from frontend for correct move routing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
71 lines
2.7 KiB
TypeScript
71 lines
2.7 KiB
TypeScript
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
|
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
|
import Stripe from 'https://esm.sh/stripe@14?target=deno';
|
|
|
|
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, { apiVersion: '2023-10-16' });
|
|
const webhookSecret = Deno.env.get('STRIPE_WEBHOOK_SECRET')!;
|
|
|
|
serve(async (req) => {
|
|
const body = await req.text();
|
|
const sig = req.headers.get('stripe-signature');
|
|
|
|
let event: Stripe.Event;
|
|
try {
|
|
event = await stripe.webhooks.constructEventAsync(body, sig!, webhookSecret);
|
|
} catch (err) {
|
|
console.error('Webhook signature failed:', err.message);
|
|
return new Response(`Webhook Error: ${err.message}`, { status: 400 });
|
|
}
|
|
|
|
const supabase = createClient(
|
|
Deno.env.get('SUPABASE_URL')!,
|
|
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
|
|
);
|
|
|
|
// Helper: retrieve fee from a payment intent and mark invoice paid
|
|
async function markPaid(paymentIntentId: string, invoice_id: string) {
|
|
let stripe_fee: number | null = null;
|
|
try {
|
|
const pi = await stripe.paymentIntents.retrieve(paymentIntentId, {
|
|
expand: ['latest_charge.balance_transaction'],
|
|
});
|
|
const charge = pi.latest_charge as Stripe.Charge | null;
|
|
const balanceTx = charge?.balance_transaction as Stripe.BalanceTransaction | null;
|
|
if (balanceTx?.fee != null) stripe_fee = balanceTx.fee / 100;
|
|
} catch (err) {
|
|
console.error('Failed to retrieve Stripe fee:', err.message);
|
|
}
|
|
|
|
const updateData: Record<string, unknown> = {
|
|
status: 'paid',
|
|
paid_at: new Date().toISOString(),
|
|
};
|
|
if (stripe_fee !== null) updateData.stripe_fee = stripe_fee;
|
|
await supabase.from('invoices').update(updateData).eq('id', invoice_id);
|
|
}
|
|
|
|
// Card payments: session completes with payment_status = 'paid' immediately
|
|
if (event.type === 'checkout.session.completed') {
|
|
const session = event.data.object as Stripe.Checkout.Session;
|
|
const invoice_id = session.metadata?.invoice_id;
|
|
const paymentIntentId = session.payment_intent as string | null;
|
|
|
|
// Only mark paid here for instant payment methods (card).
|
|
// ACH sessions complete with payment_status 'unpaid' — let payment_intent.succeeded handle those.
|
|
if (invoice_id && paymentIntentId && session.payment_status === 'paid') {
|
|
await markPaid(paymentIntentId, invoice_id);
|
|
}
|
|
}
|
|
|
|
// ACH / async payment methods: payment_intent.succeeded fires when money actually settles
|
|
if (event.type === 'payment_intent.succeeded') {
|
|
const pi = event.data.object as Stripe.PaymentIntent;
|
|
const invoice_id = pi.metadata?.invoice_id;
|
|
if (invoice_id) {
|
|
await markPaid(pi.id, invoice_id);
|
|
}
|
|
}
|
|
|
|
return new Response(JSON.stringify({ received: true }), { status: 200 });
|
|
});
|