always create new subscription
This commit is contained in:
parent
0d7e0ba1b4
commit
dd9d15d042
app
api/debug
settings
api/queue
components/billing
pages/settings
queries
@ -1,7 +1,7 @@
|
||||
import type { BlitzApiHandler } from "blitz";
|
||||
|
||||
import { cancelPaddleSubscription } from "../../../integrations/paddle";
|
||||
import appLogger from "../../../integrations/logger";
|
||||
import { cancelPaddleSubscription } from "integrations/paddle";
|
||||
import appLogger from "integrations/logger";
|
||||
|
||||
const logger = appLogger.child({ route: "/api/debug/cancel-subscription" });
|
||||
|
||||
|
29
app/api/debug/get-payments.ts
Normal file
29
app/api/debug/get-payments.ts
Normal 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;
|
18
app/api/debug/get-subscription.ts
Normal file
18
app/api/debug/get-subscription.ts
Normal 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;
|
@ -30,34 +30,27 @@ export const subscriptionCreatedQueue = Queue<Payload>("api/queue/subscription-c
|
||||
|
||||
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;
|
||||
await db.organization.update({
|
||||
where: { id: organizationId },
|
||||
data: {
|
||||
subscription: {
|
||||
create: {
|
||||
paddleSubscriptionId: event.subscriptionId,
|
||||
paddlePlanId: event.productId,
|
||||
paddleCheckoutId: event.checkoutId,
|
||||
nextBillDate: event.nextPaymentDate,
|
||||
status: translateSubscriptionStatus(event.status),
|
||||
lastEventTime: event.eventTime,
|
||||
updateUrl: event.updateUrl,
|
||||
cancelUrl: event.cancelUrl,
|
||||
currency: event.currency,
|
||||
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",
|
||||
@ -66,26 +59,6 @@ export const subscriptionCreatedQueue = Queue<Payload>("api/queue/subscription-c
|
||||
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`,
|
||||
|
@ -3,76 +3,78 @@ import useSubscription from "../../hooks/use-subscription";
|
||||
export default function BillingHistory() {
|
||||
const { payments } = useSubscription();
|
||||
|
||||
if (payments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<div className="bg-white pt-6 shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div className="px-4 sm:px-6">
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Billing history</h2>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col">
|
||||
<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="overflow-hidden border-t border-gray-200">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Date
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Amount
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
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>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{typeof payments !== "undefined"
|
||||
? payments.map((payment) => (
|
||||
<tr key={payment.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
<time>{new Date(payment.payout_date).toDateString()}</time>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{payment.amount} {payment.currency}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{payment.is_paid === 1 ? "Paid" : "Upcoming"}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
{typeof payment.receipt_url !== "undefined" ? (
|
||||
<a
|
||||
href={payment.receipt_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary-600 hover:text-primary-900"
|
||||
>
|
||||
View receipt
|
||||
</a>
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<section className="bg-white pt-6 shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div className="px-4 sm:px-6">
|
||||
<h2 className="text-lg leading-6 font-medium text-gray-900">Billing history</h2>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col">
|
||||
<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="overflow-hidden border-t border-gray-200">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Date
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Amount
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
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>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{typeof payments !== "undefined"
|
||||
? payments.map((payment) => (
|
||||
<tr key={payment.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
<time>{new Date(payment.payout_date).toDateString()}</time>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{payment.amount} {payment.currency}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{payment.is_paid === 1 ? "Paid" : "Upcoming"}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
{typeof payment.receipt_url !== "undefined" ? (
|
||||
<a
|
||||
href={payment.receipt_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary-600 hover:text-primary-900"
|
||||
>
|
||||
View receipt
|
||||
</a>
|
||||
) : null}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
: null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,37 +31,36 @@ const Billing: BlitzPage<Props> = (props) => {
|
||||
*/
|
||||
|
||||
useRequireOnboarding();
|
||||
const { subscription, cancelSubscription, updatePaymentMethod } = useSubscription({
|
||||
const { subscription, cancelSubscription, updatePaymentMethod, payments } = useSubscription({
|
||||
initialData: props.subscription,
|
||||
});
|
||||
|
||||
if (!subscription) {
|
||||
return (
|
||||
<>
|
||||
<Plans />
|
||||
<p className="text-sm text-gray-500">Prices include all applicable sales taxes.</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSection>
|
||||
<PaddleLink
|
||||
onClick={() => updatePaymentMethod({ updateUrl: subscription.updateUrl })}
|
||||
text="Update payment method"
|
||||
/>
|
||||
<PaddleLink
|
||||
onClick={() => cancelSubscription({ cancelUrl: subscription.cancelUrl })}
|
||||
text="Cancel subscription"
|
||||
/>
|
||||
</SettingsSection>
|
||||
{subscription ? (
|
||||
<SettingsSection>
|
||||
<p>Current plan: {subscription.paddlePlanId}</p>
|
||||
|
||||
<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">
|
||||
<Divider />
|
||||
</div>
|
||||
{payments.length > 0 ? (
|
||||
<>
|
||||
<BillingHistory />
|
||||
|
||||
<div className="hidden lg:block lg:py-3">
|
||||
<Divider />
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Plans />
|
||||
<p className="text-sm text-gray-500">Prices include all applicable sales taxes.</p>
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { resolver } from "blitz";
|
||||
|
||||
import db from "db";
|
||||
import db, { SubscriptionStatus } from "db";
|
||||
|
||||
export default resolver.pipe(resolver.authorize(), async (_ = null, { session }) => {
|
||||
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 },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user