Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Stripe types and checks #264

Merged
merged 4 commits into from
Jul 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
SITE_WIDTH_STYLES,
} from "@/utils/constants.ts";
import Logo from "./Logo.tsx";
import { stripe } from "../utils/payments.ts";
import { stripe } from "@/utils/payments.ts";
import { Discord, GitHub } from "./Icons.tsx";

interface NavProps extends JSX.HTMLAttributes<HTMLElement> {
Expand Down
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.2.3",
"twind-preset-tailwind/": "https://esm.sh/@twind/preset-tailwind@1.1.4/",
"std/": "https://deno.land/std@0.188.0/",
"stripe": "https://esm.sh/stripe@12.6.0",
"stripe": "./stripe.ts",
"feed": "https://esm.sh/feed@4.2.2",
"fresh_charts/": "https://deno.land/x/fresh_charts@0.2.2/",
"kv_oauth": "https://deno.land/x/deno_kv_oauth@v0.2.5/mod.ts",
Expand Down
4 changes: 3 additions & 1 deletion routes/account/manage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { redirect } from "@/utils/redirect.ts";
// deno-lint-ignore no-explicit-any
export const handler: Handlers<any, AccountState> = {
async GET(req, ctx) {
if (stripe === undefined) return ctx.renderNotFound();
if (stripe === undefined || ctx.state.user.stripeCustomerId === undefined) {
return ctx.renderNotFound();
}

const { url } = await stripe.billingPortal.sessions.create({
customer: ctx.state.user.stripeCustomerId,
Expand Down
2 changes: 1 addition & 1 deletion routes/api/stripe-webhooks.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import type { Handlers } from "$fresh/server.ts";
import { stripe } from "@/utils/payments.ts";
import { Stripe } from "stripe";
import Stripe from "stripe";
import { getUserByStripeCustomer, updateUser } from "@/utils/db.ts";

const cryptoProvider = Stripe.createSubtleCryptoProvider();
Expand Down
26 changes: 20 additions & 6 deletions routes/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type { Handlers, PageProps } from "$fresh/server.ts";
import Head from "@/components/Head.tsx";
import type { State } from "@/routes/_middleware.ts";
import { BUTTON_STYLES } from "@/utils/constants.ts";
import { formatAmountForDisplay, stripe } from "@/utils/payments.ts";
import {
formatAmountForDisplay,
isProductWithPrice,
stripe,
StripProductWithPrice,
} from "@/utils/payments.ts";
import Stripe from "stripe";
import { ComponentChild } from "preact";
import { getUserBySession, type User } from "@/utils/db.ts";
Expand All @@ -14,11 +19,11 @@ interface PricingPageData extends State {
}

function comparePrices(
productA: Stripe.Product,
productB: Stripe.Product,
productA: StripProductWithPrice,
productB: StripProductWithPrice,
) {
return ((productA.default_price as Stripe.Price).unit_amount || 0) -
((productB.default_price as Stripe.Price).unit_amount || 0);
return (productA.default_price.unit_amount || 0) -
(productB.default_price.unit_amount || 0);
}

export const handler: Handlers<PricingPageData, State> = {
Expand All @@ -29,7 +34,16 @@ export const handler: Handlers<PricingPageData, State> = {
expand: ["data.default_price"],
active: true,
});
const products = data.sort(comparePrices);

const productsWithPrice = data.filter(isProductWithPrice);

if (productsWithPrice.length !== data.length) {
throw new Error(
"Not all products have a default price. Please run the `deno task init:stripe` as the README instructs.",
);
}

const products = productsWithPrice.sort(comparePrices);

const user = ctx.state.sessionId
? await getUserBySession(ctx.state.sessionId)
Expand Down
7 changes: 7 additions & 0 deletions stripe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.

// Default types for Stripe don't yet work: https://github.com/stripe-samples/stripe-node-deno-samples/issues/2
// @deno-types="npm:stripe@12.6.0"
import Stripe from "https://esm.sh/stripe@12.6.0";

export default Stripe;
16 changes: 16 additions & 0 deletions utils/payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@ if (stripe) {
);
}

/**
* We assume that the product has a default price.
* The official types allow for the default_price to be `undefined | null | string`
*/
export type StripProductWithPrice = Stripe.Product & {
default_price: Stripe.Price;
};

export function isProductWithPrice(
product: Stripe.Product,
): product is StripProductWithPrice {
return product.default_price !== undefined &&
product.default_price !== null &&
typeof product.default_price !== "string";
}

export function formatAmountForDisplay(
amount: number,
currency: string,
Expand Down