list payments
This commit is contained in:
parent
5172ab11e7
commit
c5f135fdcc
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
<tr key={payment.id}>
|
? payments.map((payment) => (
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
<tr key={payment.id}>
|
||||||
<time>{payment.date.toDateString()}</time>
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||||
</td>
|
<time>{new Date(payment.payout_date).toDateString()}</time>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
</td>
|
||||||
{payment.description}
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
</td>
|
{payment.amount} {payment.currency}
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
</td>
|
||||||
{payment.amount}
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
</td>
|
{payment.is_paid === 1 ? "Paid" : "Not paid yet"}
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
</td>
|
||||||
<a
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
href={payment.href}
|
{typeof payment.receipt_url !== "undefined" ? (
|
||||||
className="text-primary-600 hover:text-primary-900"
|
<a
|
||||||
>
|
href={payment.receipt_url}
|
||||||
View receipt
|
target="_blank"
|
||||||
</a>
|
rel="noopener noreferrer"
|
||||||
</td>
|
className="text-primary-600 hover:text-primary-900"
|
||||||
</tr>
|
>
|
||||||
))}
|
View receipt
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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,
|
||||||
|
20
app/settings/queries/get-payments.ts
Normal file
20
app/settings/queries/get-payments.ts
Normal 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));
|
||||||
|
});
|
@ -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 } });
|
||||||
}
|
});
|
||||||
|
@ -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, {
|
||||||
...data,
|
json: {
|
||||||
vendor_id,
|
...data,
|
||||||
vendor_auth_code,
|
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<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;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user