Skip to content

Commit

Permalink
handle session idempotency & fix days_until_due issue
Browse files Browse the repository at this point in the history
  • Loading branch information
kasparkallas authored Nov 2, 2023
1 parent f677da3 commit a5cea92
Show file tree
Hide file tree
Showing 8 changed files with 36 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class CreateSessionData {
@ApiProperty() senderAddress: Address;
@ApiProperty() receiverAddress: Address;
@ApiProperty() email: string;
@ApiProperty() idempotencyKey: string;

static schema: z.ZodType<CreateSessionData> = z
.object({
Expand All @@ -35,6 +36,7 @@ export class CreateSessionData {
senderAddress: AddressSchema,
receiverAddress: AddressSchema,
email: z.string().trim().max(320).email(),
idempotencyKey: z.string(),
})
.strip();
}
Expand Down Expand Up @@ -76,7 +78,7 @@ export class CheckoutSessionController {
jobId: jobId,
// Remove finished job ASAP in case a new fresh job is triggered.
removeOnComplete: false,
removeOnFail: true,
removeOnFail: false,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AddressSchema, CreateSessionData } from './checkout-session.controller'
import { SuperfluidStripeConverterService } from 'src/superfluid-stripe-converter/superfluid-stripe-converter.service';
import { DEFAULT_PAGING } from 'src/stripe-module-config';
import { z } from 'zod';
import { SECONDS_IN_A_DAY, mapTimePeriodToSeconds } from './time-period';

export const CHECKOUT_SESSION_JOB_NAME = 'checkout-session';

Expand Down Expand Up @@ -156,13 +157,14 @@ export class CheckoutSessionProcesser extends WorkerHost {
// Note that we are creating a Stripe Subscription here that will send invoices and e-mails to the user.
// There could be scenarios where someone was using the checkout widget to pay for an existing subscription.
// Then we wouldn't want to create a new subscription here...
const daysUntilDue = requireUpfrontTransfer ? 0 : mapTimePeriodToSeconds(price.recurring!.interval) / SECONDS_IN_A_DAY
const subscriptionsCreateParams: Stripe.SubscriptionCreateParams = {
customer: customerId,
collection_method: 'send_invoice',
// Note that there is also 1 hour "draft" period.
// Sending an invoice to the user straight away is likely not preferrable for A LOT of scenarios.
// Consider a better solution or an environment variable here!
days_until_due: requireUpfrontTransfer ? 0 : undefined, // When undefined, the default behaviour should be to set it as the last day of the first subscription period.
days_until_due: Number(daysUntilDue),
currency: currency,
items: [
{
Expand Down
21 changes: 21 additions & 0 deletions apps/backend/src/checkout-session/time-period.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const timePeriods = ["day", "week", "month", "year"] as const;

export const SECONDS_IN_A_DAY = 86_400n; // 60 * 60 * 24 seconds in a day

// TODO(KK): get this from the widget repo but might need to handle dual packages first
export const mapTimePeriodToSeconds = (timePeriod: TimePeriod): bigint => {
switch (timePeriod) {
case "day":
return SECONDS_IN_A_DAY;
case "week":
return 604800n; // 60 * 60 * 24 * 7 seconds in a week
case "month":
return 2628000n; // 60 * 60 * 24 * 30 seconds in a month (approximation)
case "year":
return 31536000n; // 60 * 60 * 24 * 365 seconds in a year (approximation)
default:
throw new Error(`Invalid time period: ${timePeriod}`);
}
};

export type TimePeriod = (typeof timePeriods)[number];
2 changes: 2 additions & 0 deletions apps/backend/src/stripe-listener/stripe-listener.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export class StripeListenerModule {
repeat: {
pattern: '*/3 * * * *', // Repeat every minute. Check with: https://crontab.guru/
},
removeOnComplete: 50,
removeOnFail: 50
}, // options
);
logger.debug('onModuleInit');
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "CommonJS",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
Expand Down
9 changes: 4 additions & 5 deletions apps/frontend/src/components/SuperfluidWidgetProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ import { useModal } from 'connectkit';
import { useMemo, useState } from 'react';
import { useAccount, useMutation } from 'wagmi';

import getConfig from 'next/config';

const { publicRuntimeConfig } = getConfig();

type Props = {
productId: string;
// setInitialChainId: (chainId: number | undefined) => void;
Expand Down Expand Up @@ -55,7 +51,7 @@ export default function SupefluidWidgetProvider({
const [paymentOption, setPaymentOption] = useState<PaymentOption | undefined>();
const { address: accountAddress } = useAccount();

const eventListeners = useMemo<EventListeners>(
const eventListeners = useMemo<EventListeners>(
() => ({
onPaymentOptionUpdate: (paymentOption) => setPaymentOption(paymentOption),
onRouteChange: (arg) => {
Expand All @@ -68,6 +64,7 @@ export default function SupefluidWidgetProvider({
senderAddress: accountAddress,
receiverAddress: paymentOption.receiverAddress,
email: email,
idempotencyKey: idempotencyKey
};
createSession(data);
}
Expand All @@ -88,3 +85,5 @@ export default function SupefluidWidgetProvider({
/>
);
}

const idempotencyKey = Math.random().toString(20).substr(2, 8); // Random key, generated once per front-end initialization.
2 changes: 1 addition & 1 deletion apps/frontend/src/internalConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type InternalConfig = {

const internalConfig: InternalConfig = {
getApiKey() {
const apiKey = ensureDefined(process.env.INTERNAL_API_KEY, 'INTERNAL_API_KEY');
const apiKey = ensureDefined(process.env.STRIPE_SECRET_KEY ?? process.env.INTERNAL_API_KEY, "STRIPE_SECRET_KEY or INTERNAL_API_KEY");
return apiKey;
},
getBackendBaseUrl() {
Expand Down
3 changes: 1 addition & 2 deletions apps/frontend/src/pages/api/create-session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import internalConfig from '@/internalConfig';
import type { NextApiRequest, NextApiResponse } from 'next';
import createClient from 'openapi-fetch';

// import { } "@/backend-openapi-client"

export type CreateSessionData = {
productId: string;
chainId: number;
superTokenAddress: string;
senderAddress: string;
receiverAddress: string;
email: string;
idempotencyKey: string;
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
Expand Down

0 comments on commit a5cea92

Please sign in to comment.