From c5f135fdcc8da8e1701ef8c9b5aa897c30a7d0d9 Mon Sep 17 00:00:00 2001 From: m5r Date: Fri, 1 Oct 2021 00:59:35 +0200 Subject: [PATCH] list payments --- app/settings/api/webhook/subscription.ts | 2 + .../components/billing/billing-history.tsx | 81 ++++++++----------- app/settings/hooks/use-subscription.ts | 7 +- app/settings/queries/get-payments.ts | 20 +++++ app/settings/queries/get-subscription.ts | 6 +- integrations/paddle.ts | 53 ++++++++++-- 6 files changed, 110 insertions(+), 59 deletions(-) create mode 100644 app/settings/queries/get-payments.ts diff --git a/app/settings/api/webhook/subscription.ts b/app/settings/api/webhook/subscription.ts index 86f9c17..1edb515 100644 --- a/app/settings/api/webhook/subscription.ts +++ b/app/settings/api/webhook/subscription.ts @@ -67,6 +67,8 @@ export default async function webhook(req: BlitzApiRequest, res: BlitzApiRespons } const alertName = req.body.alert_name; + logger.info(`Received ${alertName} webhook`); + logger.info(req.body); if (isSupportedWebhook(alertName)) { return handlers[alertName](req, res); } diff --git a/app/settings/components/billing/billing-history.tsx b/app/settings/components/billing/billing-history.tsx index f2da704..1035195 100644 --- a/app/settings/components/billing/billing-history.tsx +++ b/app/settings/components/billing/billing-history.tsx @@ -1,28 +1,8 @@ -const payments = [ - { - id: 1, - date: new Date(), - description: "", - amount: "340 USD", - href: "", - }, - { - id: 1, - date: new Date(), - description: "", - amount: "340 USD", - href: "", - }, - { - id: 1, - date: new Date(), - description: "", - amount: "340 USD", - href: "", - }, -]; +import useSubscription from "../../hooks/use-subscription"; export default function BillingHistory() { + const { payments } = useSubscription(); + return (
@@ -46,17 +26,14 @@ export default function BillingHistory() { scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider" > - Description + Amount - Amount + Status - {/* - `relative` is added here due to a weird bug in Safari that causes `sr-only` headings to introduce overflow on the body on mobile. - */} - {payments.map((payment) => ( - - - - - - {payment.description} - - - {payment.amount} - - - - View receipt - - - - ))} + {typeof payments !== "undefined" + ? payments.map((payment) => ( + + + + + + {payment.amount} {payment.currency} + + + {payment.is_paid === 1 ? "Paid" : "Not paid yet"} + + + {typeof payment.receipt_url !== "undefined" ? ( + + View receipt + + ) : null} + + + )) + : null}
diff --git a/app/settings/hooks/use-subscription.ts b/app/settings/hooks/use-subscription.ts index ed247e6..af88a6e 100644 --- a/app/settings/hooks/use-subscription.ts +++ b/app/settings/hooks/use-subscription.ts @@ -3,6 +3,7 @@ import { useQuery, useMutation, useRouter, useSession } from "blitz"; import type { Subscription } from "db"; import getSubscription from "../queries/get-subscription"; +import getPayments from "../queries/get-payments"; import usePaddle from "./use-paddle"; import useCurrentUser from "../../core/hooks/use-current-user"; import updateSubscription from "../mutations/update-subscription"; @@ -14,7 +15,8 @@ type Params = { export default function useSubscription({ initialData }: Params = {}) { const session = useSession(); const { user } = useCurrentUser(); - const [subscription] = useQuery(getSubscription, null, { enabled: Boolean(session.orgId), initialData }); + const [subscription] = useQuery(getSubscription, null, { initialData }); + const [payments] = useQuery(getPayments, null); const [updateSubscriptionMutation] = useMutation(updateSubscription); const router = useRouter(); @@ -49,7 +51,7 @@ export default function useSubscription({ initialData }: Params = {}) { email: user.email, product: planId, allowQuantity: false, - passthrough: JSON.stringify({ orgId: session.orgId }), + passthrough: JSON.stringify({ organizationId: session.orgId }), coupon: "", }; @@ -93,6 +95,7 @@ export default function useSubscription({ initialData }: Params = {}) { return { subscription, + payments, subscribe, updatePaymentMethod, cancelSubscription, diff --git a/app/settings/queries/get-payments.ts b/app/settings/queries/get-payments.ts new file mode 100644 index 0000000..2fbc496 --- /dev/null +++ b/app/settings/queries/get-payments.ts @@ -0,0 +1,20 @@ +import { resolver } from "blitz"; + +import db, { SubscriptionStatus } from "db"; +import { getPayments } from "integrations/paddle"; + +export default resolver.pipe(resolver.authorize(), async (_ = null, { session }) => { + if (!session.orgId) { + return []; + } + + const subscription = await db.subscription.findFirst({ + where: { organizationId: session.orgId, status: SubscriptionStatus.active }, + }); + if (!subscription) { + return []; + } + + const payments = await getPayments({ subscriptionId: subscription.paddleSubscriptionId }); + return payments.sort((a, b) => b.payout_date.localeCompare(a.payout_date)); +}); diff --git a/app/settings/queries/get-subscription.ts b/app/settings/queries/get-subscription.ts index 39e2e3e..b11d9bc 100644 --- a/app/settings/queries/get-subscription.ts +++ b/app/settings/queries/get-subscription.ts @@ -1,9 +1,9 @@ -import type { Ctx } from "blitz"; +import { resolver } from "blitz"; import db from "db"; -export default async function getCurrentUser(_ = null, { session }: Ctx) { +export default resolver.pipe(resolver.authorize(), async (_ = null, { session }) => { if (!session.orgId) return null; return db.subscription.findFirst({ where: { organizationId: session.orgId } }); -} +}); diff --git a/integrations/paddle.ts b/integrations/paddle.ts index 75972ea..e04d82e 100644 --- a/integrations/paddle.ts +++ b/integrations/paddle.ts @@ -12,12 +12,55 @@ const client = got.extend({ async function request(path: string, data: any) { return client.post(path, { - ...data, - vendor_id, - vendor_auth_code, + json: { + ...data, + vendor_id, + vendor_auth_code, + }, + responseType: "json", }); } +type GetPaymentsParams = { + subscriptionId: string; +}; + +export async function getPayments({ subscriptionId }: GetPaymentsParams) { + type Payment = { + id: number; + subscription_id: number; + amount: number; + currency: string; + payout_date: string; + is_paid: number; + is_one_off_charge: boolean; + receipt_url?: string; + }; + + type PaymentsSuccessResponse = { + success: true; + response: Payment[]; + }; + + type PaymentsErrorResponse = { + success: false; + error: { + code: number; + message: string; + }; + }; + + type PaymentsResponse = PaymentsSuccessResponse | PaymentsErrorResponse; + + const { body } = await request("subscription/payments", { subscription_id: subscriptionId }); + console.log("body", typeof body); + if (!body.success) { + throw new Error(body.error.message); + } + + return body.response; +} + type UpdateSubscriptionPlanParams = { subscriptionId: string; planId: string; @@ -25,7 +68,7 @@ type UpdateSubscriptionPlanParams = { }; export async function updateSubscriptionPlan({ subscriptionId, planId, prorate = true }: UpdateSubscriptionPlanParams) { - const { body } = await request("/subscription/users/update", { + const { body } = await request("subscription/users/update", { subscription_id: subscriptionId, plan_id: planId, prorate, @@ -35,7 +78,7 @@ export async function updateSubscriptionPlan({ subscriptionId, planId, prorate = } export async function cancelPaddleSubscription({ subscriptionId }: { subscriptionId: string }) { - const { body } = await request("/subscription/users_cancel", { subscription_id: subscriptionId }); + const { body } = await request("subscription/users_cancel", { subscription_id: subscriptionId }); return body; }