list payments

This commit is contained in:
m5r 2021-10-01 00:59:35 +02:00
parent 5172ab11e7
commit c5f135fdcc
6 changed files with 110 additions and 59 deletions

View File

@ -67,6 +67,8 @@ export default async function webhook(req: BlitzApiRequest, res: BlitzApiRespons
} }
const alertName = req.body.alert_name; const alertName = req.body.alert_name;
logger.info(`Received ${alertName} webhook`);
logger.info(req.body);
if (isSupportedWebhook(alertName)) { if (isSupportedWebhook(alertName)) {
return handlers[alertName](req, res); return handlers[alertName](req, res);
} }

View File

@ -1,28 +1,8 @@
const payments = [ import useSubscription from "../../hooks/use-subscription";
{
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: "",
},
];
export default function BillingHistory() { export default function BillingHistory() {
const { payments } = useSubscription();
return ( return (
<section> <section>
<div 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">
@ -46,17 +26,14 @@ export default function BillingHistory() {
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"
> >
Description 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"
> >
Amount Status
</th> </th>
{/*
`relative` is added here due to a weird bug in Safari that causes `sr-only` headings to introduce overflow on the body on mobile.
*/}
<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"
@ -66,27 +43,33 @@ export default function BillingHistory() {
</tr> </tr>
</thead> </thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{payments.map((payment) => ( {typeof payments !== "undefined"
? 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>{payment.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.description} {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.amount} {payment.is_paid === 1 ? "Paid" : "Not paid yet"}
</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" ? (
<a <a
href={payment.href} href={payment.receipt_url}
target="_blank"
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}
</td> </td>
</tr> </tr>
))} ))
: null}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -3,6 +3,7 @@ import { useQuery, useMutation, useRouter, useSession } from "blitz";
import type { Subscription } from "db"; import type { Subscription } from "db";
import getSubscription from "../queries/get-subscription"; import getSubscription from "../queries/get-subscription";
import getPayments from "../queries/get-payments";
import usePaddle from "./use-paddle"; import usePaddle from "./use-paddle";
import useCurrentUser from "../../core/hooks/use-current-user"; import useCurrentUser from "../../core/hooks/use-current-user";
import updateSubscription from "../mutations/update-subscription"; import updateSubscription from "../mutations/update-subscription";
@ -14,7 +15,8 @@ type Params = {
export default function useSubscription({ initialData }: Params = {}) { export default function useSubscription({ initialData }: Params = {}) {
const session = useSession(); const session = useSession();
const { user } = useCurrentUser(); 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 [updateSubscriptionMutation] = useMutation(updateSubscription);
const router = useRouter(); const router = useRouter();
@ -49,7 +51,7 @@ export default function useSubscription({ initialData }: Params = {}) {
email: user.email, email: user.email,
product: planId, product: planId,
allowQuantity: false, allowQuantity: false,
passthrough: JSON.stringify({ orgId: session.orgId }), passthrough: JSON.stringify({ organizationId: session.orgId }),
coupon: "", coupon: "",
}; };
@ -93,6 +95,7 @@ export default function useSubscription({ initialData }: Params = {}) {
return { return {
subscription, subscription,
payments,
subscribe, subscribe,
updatePaymentMethod, updatePaymentMethod,
cancelSubscription, cancelSubscription,

View File

@ -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));
});

View File

@ -1,9 +1,9 @@
import type { Ctx } from "blitz"; import { resolver } from "blitz";
import db from "db"; 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; if (!session.orgId) return null;
return db.subscription.findFirst({ where: { organizationId: session.orgId } }); return db.subscription.findFirst({ where: { organizationId: session.orgId } });
} });

View File

@ -12,12 +12,55 @@ const client = got.extend({
async function request<T>(path: string, data: any) { async function request<T>(path: string, data: any) {
return client.post<T>(path, { return client.post<T>(path, {
json: {
...data, ...data,
vendor_id, vendor_id,
vendor_auth_code, 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<PaymentsResponse>("subscription/payments", { subscription_id: subscriptionId });
console.log("body", typeof body);
if (!body.success) {
throw new Error(body.error.message);
}
return body.response;
}
type UpdateSubscriptionPlanParams = { type UpdateSubscriptionPlanParams = {
subscriptionId: string; subscriptionId: string;
planId: string; planId: string;
@ -25,7 +68,7 @@ type UpdateSubscriptionPlanParams = {
}; };
export async function updateSubscriptionPlan({ subscriptionId, planId, prorate = true }: 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, subscription_id: subscriptionId,
plan_id: planId, plan_id: planId,
prorate, prorate,
@ -35,7 +78,7 @@ export async function updateSubscriptionPlan({ subscriptionId, planId, prorate =
} }
export async function cancelPaddleSubscription({ subscriptionId }: { subscriptionId: string }) { 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; return body;
} }