* Apply patch [skip ci] * Empty commit to trigger CI * Apply patch [skip ci] * Apply patch [skip ci] * Apply patch [skip ci] * Apply patch [skip ci] * Apply patch [skip ci] * Apply patch [skip ci] * Apply patch [skip ci] * Apply patch [skip ci] * Apply patch [skip ci] * Apply patch [skip ci] --------- Co-authored-by: open-swe-dev-andrinoid[bot] <open-swe-dev-andrinoid@users.noreply.github.com>
102 lines
3.2 KiB
TypeScript
102 lines
3.2 KiB
TypeScript
import { define } from "../../../utils.ts";
|
|
import {
|
|
createPaymentTransaction,
|
|
getTransactionByStripeId,
|
|
} from "../../../services/paymentService.ts";
|
|
import Stripe from "stripe";
|
|
|
|
export const handler = define.handlers({
|
|
async POST(ctx) {
|
|
try {
|
|
const raw = await ctx.req.text();
|
|
const sandbox = Deno.env.get("DISABLE_EXTERNAL_APIS") === "true" ||
|
|
Deno.env.get("SANDBOX_MODE") === "true";
|
|
|
|
if (sandbox) {
|
|
// Accept simulated event: { type, data: { id, amount, lease_id, payment_method } }
|
|
const { type, data } = JSON.parse(raw || "{}");
|
|
if (type === "payment_intent.succeeded" && data?.id) {
|
|
const existing = await getTransactionByStripeId(data.id);
|
|
if (!existing) {
|
|
const amountNumber = typeof data.amount === "number"
|
|
? data.amount
|
|
: Number(data.amount);
|
|
const amount = Number.isFinite(amountNumber) ? amountNumber : 0;
|
|
await createPaymentTransaction(
|
|
data.lease_id,
|
|
amount,
|
|
Date.now(),
|
|
data.payment_method || "ach",
|
|
"completed",
|
|
data.id,
|
|
);
|
|
}
|
|
}
|
|
return new Response(JSON.stringify({ received: true }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
// Production Stripe verification
|
|
const secret = Deno.env.get("STRIPE_WEBHOOK_SECRET");
|
|
const sigHeader = ctx.req.headers.get("stripe-signature");
|
|
if (!secret || !sigHeader) {
|
|
return new Response(JSON.stringify({ error: "Missing signature" }), {
|
|
status: 400,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
|
|
let event;
|
|
try {
|
|
event = Stripe.webhooks.constructEvent(
|
|
raw,
|
|
sigHeader,
|
|
secret,
|
|
);
|
|
} catch (err) {
|
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
return new Response(
|
|
JSON.stringify({
|
|
error: `Webhook verification failed: ${errorMessage}`,
|
|
}),
|
|
{
|
|
status: 400,
|
|
headers: { "Content-Type": "application/json" },
|
|
},
|
|
);
|
|
}
|
|
if (event?.type === "payment_intent.succeeded") {
|
|
const obj = event.data?.object ?? {};
|
|
const stripeId = obj.id as string;
|
|
const amountCents = obj.amount_received ?? obj.amount ?? 0;
|
|
const amount = Math.round(Number(amountCents)) / 100;
|
|
const leaseId = obj.metadata?.lease_id ?? "";
|
|
const method = obj.payment_method_types?.[0] ?? "card";
|
|
const existing = await getTransactionByStripeId(stripeId);
|
|
if (!existing) {
|
|
await createPaymentTransaction(
|
|
leaseId,
|
|
amount,
|
|
Date.now(),
|
|
method,
|
|
"completed",
|
|
stripeId,
|
|
);
|
|
}
|
|
}
|
|
|
|
return new Response(JSON.stringify({ received: true }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
} catch (err) {
|
|
return new Response(JSON.stringify({ error: String(err) }), {
|
|
status: 500,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
},
|
|
});
|