made checkout lock fail open
This commit is contained in:
@@ -73,10 +73,12 @@ export const handleCheckoutSessionMetadataV2 = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Clear checkout session lock now that customer_product rows exist
|
// Clear checkout session lock now that customer_product rows exist
|
||||||
await checkoutSessionLock.clear({
|
const lockCustomerId =
|
||||||
ctx,
|
updatedDeferredData.billingContext.fullCustomer.id ??
|
||||||
customerId: ctx.fullCustomer?.id ?? "",
|
updatedDeferredData.billingContext.fullCustomer.internal_id;
|
||||||
});
|
if (lockCustomerId) {
|
||||||
|
await checkoutSessionLock.clear({ ctx, customerId: lockCustomerId });
|
||||||
|
}
|
||||||
|
|
||||||
// Queue customer.products.updated webhook (mirrors executeBillingPlan)
|
// Queue customer.products.updated webhook (mirrors executeBillingPlan)
|
||||||
await billingPlanToSendProductsUpdated({
|
await billingPlanToSendProductsUpdated({
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { ms } from "@autumn/shared";
|
|
||||||
import { createStripeCli } from "@/external/connect/createStripeCli";
|
import { createStripeCli } from "@/external/connect/createStripeCli";
|
||||||
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
||||||
import { CacheManager } from "@/utils/cacheUtils/CacheManager";
|
import { CacheManager } from "@/utils/cacheUtils/CacheManager";
|
||||||
|
|
||||||
const CHECKOUT_LOCK_TTL_SECONDS = ms.minutes(2);
|
const CHECKOUT_LOCK_TTL_SECONDS = 2 * 60;
|
||||||
|
|
||||||
interface CheckoutSessionLockData {
|
interface CheckoutSessionLockData {
|
||||||
paramsHash: string;
|
paramsHash: string;
|
||||||
@@ -26,9 +25,14 @@ const get = async ({
|
|||||||
ctx: AutumnContext;
|
ctx: AutumnContext;
|
||||||
customerId: string;
|
customerId: string;
|
||||||
}): Promise<CheckoutSessionLockData | null> => {
|
}): Promise<CheckoutSessionLockData | null> => {
|
||||||
return CacheManager.getJson<CheckoutSessionLockData>(
|
try {
|
||||||
buildKey({ ctx, customerId }),
|
return await CacheManager.getJson<CheckoutSessionLockData>(
|
||||||
);
|
buildKey({ ctx, customerId }),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
ctx.logger.error(`Failed to get checkout session lock: ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const set = async ({
|
const set = async ({
|
||||||
@@ -40,11 +44,15 @@ const set = async ({
|
|||||||
customerId: string;
|
customerId: string;
|
||||||
data: CheckoutSessionLockData;
|
data: CheckoutSessionLockData;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
await CacheManager.setJson(
|
try {
|
||||||
buildKey({ ctx, customerId }),
|
await CacheManager.setJson(
|
||||||
data,
|
buildKey({ ctx, customerId }),
|
||||||
CHECKOUT_LOCK_TTL_SECONDS,
|
data,
|
||||||
);
|
CHECKOUT_LOCK_TTL_SECONDS,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
ctx.logger.error(`Failed to set checkout session lock: ${error}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const clear = async ({
|
const clear = async ({
|
||||||
@@ -54,7 +62,11 @@ const clear = async ({
|
|||||||
ctx: AutumnContext;
|
ctx: AutumnContext;
|
||||||
customerId: string;
|
customerId: string;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
await CacheManager.del(buildKey({ ctx, customerId }));
|
try {
|
||||||
|
await CacheManager.del(buildKey({ ctx, customerId }));
|
||||||
|
} catch (error) {
|
||||||
|
ctx.logger.error(`Failed to clear checkout session lock: ${error}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Expire the old Stripe Checkout session then delete the Redis lock. */
|
/** Expire the old Stripe Checkout session then delete the Redis lock. */
|
||||||
@@ -73,8 +85,10 @@ const expireAndClear = async ({
|
|||||||
env: ctx.env,
|
env: ctx.env,
|
||||||
});
|
});
|
||||||
await stripeCli.checkout.sessions.expire(checkoutSessionId);
|
await stripeCli.checkout.sessions.expire(checkoutSessionId);
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Session may already be expired / completed
|
ctx.logger.error(
|
||||||
|
`Failed to expire checkout session ${checkoutSessionId}: ${error}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await clear({ ctx, customerId });
|
await clear({ ctx, customerId });
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ const deterministicStringify = (value: unknown): string => {
|
|||||||
return `[${value.map(deterministicStringify).join(",")}]`;
|
return `[${value.map(deterministicStringify).join(",")}]`;
|
||||||
|
|
||||||
const obj = value as Record<string, unknown>;
|
const obj = value as Record<string, unknown>;
|
||||||
|
|
||||||
|
if (typeof obj.toJSON === "function")
|
||||||
|
return deterministicStringify(obj.toJSON());
|
||||||
|
|
||||||
const sortedKeys = Object.keys(obj)
|
const sortedKeys = Object.keys(obj)
|
||||||
.filter((k) => obj[k] !== undefined)
|
.filter((k) => obj[k] !== undefined)
|
||||||
.sort();
|
.sort();
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ export interface StripeBillingPlanResult {
|
|||||||
deferred?: boolean;
|
deferred?: boolean;
|
||||||
stripeInvoice?: Stripe.Invoice;
|
stripeInvoice?: Stripe.Invoice;
|
||||||
stripeSubscription?: Stripe.Subscription;
|
stripeSubscription?: Stripe.Subscription;
|
||||||
stripeCheckoutSession?: Partial<Stripe.Checkout.Session>;
|
stripeCheckoutSession?:
|
||||||
|
| Stripe.Checkout.Session
|
||||||
|
| (Pick<Stripe.Checkout.Session, "id"> & { url?: string | null });
|
||||||
stripeInvoiceItems?: Stripe.InvoiceItem[];
|
stripeInvoiceItems?: Stripe.InvoiceItem[];
|
||||||
requiredAction?: {
|
requiredAction?: {
|
||||||
code: PaymentFailureCode;
|
code: PaymentFailureCode;
|
||||||
|
|||||||
Reference in New Issue
Block a user