* return 200 asap to paddle and queue webhook received
* paddle ids to int
This commit is contained in:
42
app/settings/api/queue/subscription-cancelled.ts
Normal file
42
app/settings/api/queue/subscription-cancelled.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { NotFoundError } from "blitz";
|
||||
import { Queue } from "quirrel/blitz";
|
||||
import type { PaddleSdkSubscriptionCancelledEvent } from "@devoxa/paddle-sdk";
|
||||
|
||||
import db from "db";
|
||||
import appLogger from "integrations/logger";
|
||||
import { translateSubscriptionStatus } from "integrations/paddle";
|
||||
|
||||
const logger = appLogger.child({ queue: "subscription-cancelled" });
|
||||
|
||||
type Payload = {
|
||||
event: PaddleSdkSubscriptionCancelledEvent<{ organizationId: string }>;
|
||||
};
|
||||
|
||||
export const subscriptionCancelledQueue = Queue<Payload>("api/queue/subscription-cancelled", async ({ event }) => {
|
||||
const paddleSubscriptionId = event.subscriptionId;
|
||||
const subscription = await db.subscription.findFirst({ where: { paddleSubscriptionId } });
|
||||
if (!subscription) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const lastEventTime = event.eventTime;
|
||||
const isEventOlderThanLastUpdate = subscription.lastEventTime > lastEventTime;
|
||||
if (isEventOlderThanLastUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.subscription.update({
|
||||
where: { paddleSubscriptionId },
|
||||
data: {
|
||||
paddleSubscriptionId,
|
||||
paddlePlanId: event.productId,
|
||||
paddleCheckoutId: event.checkoutId,
|
||||
status: translateSubscriptionStatus(event.status),
|
||||
lastEventTime,
|
||||
currency: event.currency,
|
||||
unitPrice: event.unitPrice,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export default subscriptionCancelledQueue;
|
99
app/settings/api/queue/subscription-created.ts
Normal file
99
app/settings/api/queue/subscription-created.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { NotFoundError } from "blitz";
|
||||
import { Queue } from "quirrel/blitz";
|
||||
import type { PaddleSdkSubscriptionCreatedEvent } from "@devoxa/paddle-sdk";
|
||||
|
||||
import db, { MembershipRole } from "db";
|
||||
import appLogger from "integrations/logger";
|
||||
import { sendEmail } from "integrations/ses";
|
||||
import { translateSubscriptionStatus } from "integrations/paddle";
|
||||
|
||||
const logger = appLogger.child({ queue: "subscription-created" });
|
||||
|
||||
type Payload = {
|
||||
event: PaddleSdkSubscriptionCreatedEvent<{ organizationId: string }>;
|
||||
};
|
||||
|
||||
export const subscriptionCreatedQueue = Queue<Payload>("api/queue/subscription-created", async ({ event }) => {
|
||||
const { organizationId } = event.metadata;
|
||||
const organization = await db.organization.findFirst({
|
||||
where: { id: organizationId },
|
||||
include: {
|
||||
subscription: true,
|
||||
memberships: {
|
||||
include: { user: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!organization) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const orgOwner = organization.memberships.find((membership) => membership.role === MembershipRole.OWNER);
|
||||
const email = orgOwner!.user!.email;
|
||||
const paddleCheckoutId = event.checkoutId;
|
||||
const paddleSubscriptionId = event.subscriptionId;
|
||||
const planId = event.productId;
|
||||
const nextBillDate = event.nextPaymentDate;
|
||||
const status = translateSubscriptionStatus(event.status);
|
||||
const lastEventTime = event.eventTime;
|
||||
const updateUrl = event.updateUrl;
|
||||
const cancelUrl = event.cancelUrl;
|
||||
const currency = event.currency;
|
||||
const unitPrice = event.unitPrice;
|
||||
|
||||
if (!!organization.subscription) {
|
||||
await db.subscription.update({
|
||||
where: { paddleSubscriptionId: organization.subscription.paddleSubscriptionId },
|
||||
data: {
|
||||
paddleSubscriptionId,
|
||||
paddlePlanId: planId,
|
||||
paddleCheckoutId,
|
||||
nextBillDate,
|
||||
status,
|
||||
lastEventTime,
|
||||
updateUrl,
|
||||
cancelUrl,
|
||||
currency,
|
||||
unitPrice,
|
||||
},
|
||||
});
|
||||
|
||||
sendEmail({
|
||||
subject: "Welcome back to Shellphone",
|
||||
body: "Welcome back to Shellphone",
|
||||
recipients: [email],
|
||||
}).catch((error) => {
|
||||
logger.error(error);
|
||||
});
|
||||
} else {
|
||||
await db.organization.update({
|
||||
where: { id: organizationId },
|
||||
data: {
|
||||
subscription: {
|
||||
create: {
|
||||
paddleSubscriptionId,
|
||||
paddlePlanId: planId,
|
||||
paddleCheckoutId,
|
||||
nextBillDate,
|
||||
status,
|
||||
lastEventTime,
|
||||
updateUrl,
|
||||
cancelUrl,
|
||||
currency,
|
||||
unitPrice,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
sendEmail({
|
||||
subject: "Welcome to Shellphone",
|
||||
body: `Welcome to Shellphone`,
|
||||
recipients: [email],
|
||||
}).catch((error) => {
|
||||
logger.error(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default subscriptionCreatedQueue;
|
47
app/settings/api/queue/subscription-payment-succeeded.ts
Normal file
47
app/settings/api/queue/subscription-payment-succeeded.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { NotFoundError } from "blitz";
|
||||
import { Queue } from "quirrel/blitz";
|
||||
import { PaddleSdkSubscriptionPaymentSucceededEvent } from "@devoxa/paddle-sdk";
|
||||
|
||||
import db from "db";
|
||||
import appLogger from "integrations/logger";
|
||||
import type { Metadata } from "integrations/paddle";
|
||||
import { translateSubscriptionStatus } from "integrations/paddle";
|
||||
|
||||
const logger = appLogger.child({ queue: "subscription-payment-succeeded" });
|
||||
|
||||
type Payload = {
|
||||
event: PaddleSdkSubscriptionPaymentSucceededEvent<Metadata>;
|
||||
};
|
||||
|
||||
export const subscriptionPaymentSucceededQueue = Queue<Payload>(
|
||||
"api/queue/subscription-payment-succeeded",
|
||||
async ({ event }) => {
|
||||
const paddleSubscriptionId = event.subscriptionId;
|
||||
const subscription = await db.subscription.findFirst({ where: { paddleSubscriptionId } });
|
||||
if (!subscription) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const lastEventTime = event.eventTime;
|
||||
const isEventOlderThanLastUpdate = subscription.lastEventTime > lastEventTime;
|
||||
if (isEventOlderThanLastUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
await db.subscription.update({
|
||||
where: { paddleSubscriptionId },
|
||||
data: {
|
||||
paddleSubscriptionId,
|
||||
paddlePlanId: event.productId,
|
||||
paddleCheckoutId: event.checkoutId,
|
||||
nextBillDate: event.nextPaymentDate,
|
||||
status: translateSubscriptionStatus(event.status),
|
||||
lastEventTime,
|
||||
currency: event.currency,
|
||||
unitPrice: event.unitPrice,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default subscriptionPaymentSucceededQueue;
|
71
app/settings/api/queue/subscription-updated.ts
Normal file
71
app/settings/api/queue/subscription-updated.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { NotFoundError } from "blitz";
|
||||
import { Queue } from "quirrel/blitz";
|
||||
import { PaddleSdkSubscriptionUpdatedEvent } from "@devoxa/paddle-sdk";
|
||||
|
||||
import db, { MembershipRole } from "db";
|
||||
import appLogger from "integrations/logger";
|
||||
import { sendEmail } from "integrations/ses";
|
||||
import type { Metadata } from "integrations/paddle";
|
||||
import { translateSubscriptionStatus } from "integrations/paddle";
|
||||
|
||||
const logger = appLogger.child({ module: "subscription-updated" });
|
||||
|
||||
type Payload = {
|
||||
event: PaddleSdkSubscriptionUpdatedEvent<Metadata>;
|
||||
};
|
||||
|
||||
export const subscriptionUpdatedQueue = Queue<Payload>("api/queue/subscription-updated", async ({ event }) => {
|
||||
const paddleSubscriptionId = event.subscriptionId;
|
||||
const subscription = await db.subscription.findFirst({
|
||||
where: { paddleSubscriptionId },
|
||||
include: {
|
||||
organization: {
|
||||
include: {
|
||||
memberships: {
|
||||
include: { user: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!subscription) {
|
||||
throw new NotFoundError();
|
||||
}
|
||||
|
||||
const lastEventTime = event.eventTime;
|
||||
const isEventOlderThanLastUpdate = subscription.lastEventTime > lastEventTime;
|
||||
if (isEventOlderThanLastUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
const orgOwner = subscription.organization!.memberships.find(
|
||||
(membership) => membership.role === MembershipRole.OWNER,
|
||||
);
|
||||
const email = orgOwner!.user!.email;
|
||||
const planId = event.productId;
|
||||
await db.subscription.update({
|
||||
where: { paddleSubscriptionId },
|
||||
data: {
|
||||
paddleSubscriptionId,
|
||||
paddlePlanId: planId,
|
||||
paddleCheckoutId: event.checkoutId,
|
||||
nextBillDate: event.nextPaymentDate,
|
||||
status: translateSubscriptionStatus(event.status),
|
||||
lastEventTime,
|
||||
updateUrl: event.updateUrl,
|
||||
cancelUrl: event.cancelUrl,
|
||||
currency: event.currency,
|
||||
unitPrice: event.unitPrice,
|
||||
},
|
||||
});
|
||||
|
||||
sendEmail({
|
||||
subject: "Thanks for your purchase",
|
||||
body: "Thanks for your purchase",
|
||||
recipients: [email],
|
||||
}).catch((error) => {
|
||||
logger.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
export default subscriptionUpdatedQueue;
|
Reference in New Issue
Block a user