always create new subscription

This commit is contained in:
m5r 2021-10-02 00:19:06 +02:00
parent 0d7e0ba1b4
commit dd9d15d042
7 changed files with 169 additions and 143 deletions

View File

@ -1,7 +1,7 @@
import type { BlitzApiHandler } from "blitz"; import type { BlitzApiHandler } from "blitz";
import { cancelPaddleSubscription } from "../../../integrations/paddle"; import { cancelPaddleSubscription } from "integrations/paddle";
import appLogger from "../../../integrations/logger"; import appLogger from "integrations/logger";
const logger = appLogger.child({ route: "/api/debug/cancel-subscription" }); const logger = appLogger.child({ route: "/api/debug/cancel-subscription" });

View File

@ -0,0 +1,29 @@
import type { BlitzApiHandler } from "blitz";
import { getPayments } from "integrations/paddle";
import appLogger from "integrations/logger";
import db from "db";
const logger = appLogger.child({ route: "/api/debug/cancel-subscription" });
const cancelSubscriptionHandler: BlitzApiHandler = async (req, res) => {
const { organizationId } = req.body;
logger.debug(`fetching payments for organizationId="${organizationId}"`);
const subscriptions = await db.subscription.findMany({ where: { organizationId } });
if (subscriptions.length === 0) {
res.status(200).send([]);
}
console.log("subscriptions", subscriptions);
const paymentsBySubscription = await Promise.all(
subscriptions.map((subscription) => getPayments({ subscriptionId: subscription.paddleSubscriptionId })),
);
const payments = paymentsBySubscription.flat();
const result = Array.from(payments).sort((a, b) => b.payout_date.localeCompare(a.payout_date));
logger.debug(result);
res.status(200).send(result);
};
export default cancelSubscriptionHandler;

View File

@ -0,0 +1,18 @@
import type { BlitzApiHandler } from "blitz";
import db from "db";
import appLogger from "integrations/logger";
const logger = appLogger.child({ route: "/api/debug/get-subscription" });
const cancelSubscriptionHandler: BlitzApiHandler = async (req, res) => {
const { organizationId } = req.body;
logger.debug(`fetching subscription for organizationId="${organizationId}"`);
const subscription = await db.subscription.findFirst({ where: { organizationId } });
console.debug(subscription);
res.status(200).send(subscription);
};
export default cancelSubscriptionHandler;

View File

@ -30,34 +30,27 @@ export const subscriptionCreatedQueue = Queue<Payload>("api/queue/subscription-c
const orgOwner = organization.memberships.find((membership) => membership.role === MembershipRole.OWNER); const orgOwner = organization.memberships.find((membership) => membership.role === MembershipRole.OWNER);
const email = orgOwner!.user!.email; const email = orgOwner!.user!.email;
const paddleCheckoutId = event.checkoutId; await db.organization.update({
const paddleSubscriptionId = event.subscriptionId; where: { id: organizationId },
const planId = event.productId; data: {
const nextBillDate = event.nextPaymentDate; subscription: {
const status = translateSubscriptionStatus(event.status); create: {
const lastEventTime = event.eventTime; paddleSubscriptionId: event.subscriptionId,
const updateUrl = event.updateUrl; paddlePlanId: event.productId,
const cancelUrl = event.cancelUrl; paddleCheckoutId: event.checkoutId,
const currency = event.currency; nextBillDate: event.nextPaymentDate,
const unitPrice = event.unitPrice; status: translateSubscriptionStatus(event.status),
lastEventTime: event.eventTime,
updateUrl: event.updateUrl,
cancelUrl: event.cancelUrl,
currency: event.currency,
unitPrice: event.unitPrice,
},
},
},
});
if (!!organization.subscription) { 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({ sendEmail({
subject: "Welcome back to Shellphone", subject: "Welcome back to Shellphone",
body: "Welcome back to Shellphone", body: "Welcome back to Shellphone",
@ -66,26 +59,6 @@ export const subscriptionCreatedQueue = Queue<Payload>("api/queue/subscription-c
logger.error(error); logger.error(error);
}); });
} else { } else {
await db.organization.update({
where: { id: organizationId },
data: {
subscription: {
create: {
paddleSubscriptionId,
paddlePlanId: planId,
paddleCheckoutId,
nextBillDate,
status,
lastEventTime,
updateUrl,
cancelUrl,
currency,
unitPrice,
},
},
},
});
sendEmail({ sendEmail({
subject: "Welcome to Shellphone", subject: "Welcome to Shellphone",
body: `Welcome to Shellphone`, body: `Welcome to Shellphone`,

View File

@ -3,76 +3,78 @@ import useSubscription from "../../hooks/use-subscription";
export default function BillingHistory() { export default function BillingHistory() {
const { payments } = useSubscription(); const { payments } = useSubscription();
if (payments.length === 0) {
return null;
}
return ( return (
<section> <section className="bg-white pt-6 shadow sm:rounded-md sm:overflow-hidden">
<div className="bg-white pt-6 shadow sm:rounded-md sm:overflow-hidden"> <div className="px-4 sm:px-6">
<div className="px-4 sm:px-6"> <h2 className="text-lg leading-6 font-medium text-gray-900">Billing history</h2>
<h2 className="text-lg leading-6 font-medium text-gray-900">Billing history</h2> </div>
</div> <div className="mt-6 flex flex-col">
<div className="mt-6 flex flex-col"> <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8"> <div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"> <div className="overflow-hidden border-t border-gray-200">
<div className="overflow-hidden border-t border-gray-200"> <table className="min-w-full divide-y divide-gray-200">
<table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50">
<thead className="bg-gray-50"> <tr>
<tr> <th
<th scope="col"
scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
> Date
Date </th>
</th> <th
<th scope="col"
scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
> Amount
Amount </th>
</th> <th
<th scope="col"
scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
> Status
Status </th>
</th> <th
<th scope="col"
scope="col" className="relative px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
className="relative px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" >
> <span className="sr-only">View receipt</span>
<span className="sr-only">View receipt</span> </th>
</th> </tr>
</tr> </thead>
</thead> <tbody className="bg-white divide-y divide-gray-200">
<tbody className="bg-white divide-y divide-gray-200"> {typeof payments !== "undefined"
{typeof payments !== "undefined" ? payments.map((payment) => (
? payments.map((payment) => ( <tr key={payment.id}>
<tr key={payment.id}> <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"> <time>{new Date(payment.payout_date).toDateString()}</time>
<time>{new Date(payment.payout_date).toDateString()}</time> </td>
</td> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {payment.amount} {payment.currency}
{payment.amount} {payment.currency} </td>
</td> <td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> {payment.is_paid === 1 ? "Paid" : "Upcoming"}
{payment.is_paid === 1 ? "Paid" : "Upcoming"} </td>
</td> <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> {typeof payment.receipt_url !== "undefined" ? (
{typeof payment.receipt_url !== "undefined" ? ( <a
<a href={payment.receipt_url}
href={payment.receipt_url} target="_blank"
target="_blank" rel="noopener noreferrer"
rel="noopener noreferrer" className="text-primary-600 hover:text-primary-900"
className="text-primary-600 hover:text-primary-900" >
> View receipt
View receipt </a>
</a> ) : null}
) : null} </td>
</td> </tr>
</tr> ))
)) : null}
: null} </tbody>
</tbody> </table>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -31,37 +31,36 @@ const Billing: BlitzPage<Props> = (props) => {
*/ */
useRequireOnboarding(); useRequireOnboarding();
const { subscription, cancelSubscription, updatePaymentMethod } = useSubscription({ const { subscription, cancelSubscription, updatePaymentMethod, payments } = useSubscription({
initialData: props.subscription, initialData: props.subscription,
}); });
if (!subscription) {
return (
<>
<Plans />
<p className="text-sm text-gray-500">Prices include all applicable sales taxes.</p>
</>
);
}
return ( return (
<> <>
<SettingsSection> {subscription ? (
<PaddleLink <SettingsSection>
onClick={() => updatePaymentMethod({ updateUrl: subscription.updateUrl })} <p>Current plan: {subscription.paddlePlanId}</p>
text="Update payment method"
/>
<PaddleLink
onClick={() => cancelSubscription({ cancelUrl: subscription.cancelUrl })}
text="Cancel subscription"
/>
</SettingsSection>
<BillingHistory /> <PaddleLink
onClick={() => updatePaymentMethod({ updateUrl: subscription.updateUrl })}
text="Update payment method"
/>
<PaddleLink
onClick={() => cancelSubscription({ cancelUrl: subscription.cancelUrl })}
text="Cancel subscription"
/>
</SettingsSection>
) : null}
<div className="hidden lg:block lg:py-3"> {payments.length > 0 ? (
<Divider /> <>
</div> <BillingHistory />
<div className="hidden lg:block lg:py-3">
<Divider />
</div>
</>
) : null}
<Plans /> <Plans />
<p className="text-sm text-gray-500">Prices include all applicable sales taxes.</p> <p className="text-sm text-gray-500">Prices include all applicable sales taxes.</p>

View File

@ -1,9 +1,14 @@
import { resolver } from "blitz"; import { resolver } from "blitz";
import db from "db"; import db, { SubscriptionStatus } from "db";
export default resolver.pipe(resolver.authorize(), async (_ = null, { session }) => { export default resolver.pipe(resolver.authorize(), async (_ = null, { session }) => {
if (!session.orgId) return null; if (!session.orgId) return null;
return db.subscription.findFirst({ where: { organizationId: session.orgId } }); return db.subscription.findFirst({
where: {
organizationId: session.orgId,
status: { not: SubscriptionStatus.deleted },
},
});
}); });