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

Storing failed and successful payments for Stripe Plugin #241

Merged
merged 14 commits into from
Aug 16, 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
1 change: 1 addition & 0 deletions packages/vendure-plugin-stripe-subscription/codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ generates:
plugins:
- typescript
- typescript-operations
- typed-document-node
config:
avoidOptionals: false
scalars:
Expand Down
2 changes: 1 addition & 1 deletion packages/vendure-plugin-stripe-subscription/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pinelab/vendure-plugin-stripe-subscription",
"version": "1.1.1",
"version": "1.2.0",
"description": "Vendure plugin for selling subscriptions via Stripe",
"author": "Martijn van de Brug <[email protected]>",
"homepage": "https://pinelab-plugins.com/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ const _scalar = gql`
scalar DateTime
`;

const _interface = gql`
interface Node {
id: ID!
createdAt: DateTime
}
interface PaginatedList {
items: [Node!]!
totalItems: Int!
}
`;

const sharedTypes = gql`
enum SubscriptionInterval {
week
Expand All @@ -18,7 +29,10 @@ const sharedTypes = gql`
time_of_purchase
fixed_startdate
}
type StripeSubscriptionSchedule {
"""
For codegen to work this must implement Node
"""
type StripeSubscriptionSchedule implements Node {
id: ID!
createdAt: DateTime
updatedAt: DateTime
Expand All @@ -35,6 +49,21 @@ const sharedTypes = gql`
useProration: Boolean
autoRenew: Boolean
}
"""
For codegen to work this must implement Node
"""
type StripeSubscriptionPayment implements Node {
id: ID!
createdAt: DateTime
updatedAt: DateTime
collectionMethod: String
charge: Int
currency: String
orderCode: String
channelId: ID
eventType: String
subscriptionId: String
}
input UpsertStripeSubscriptionScheduleInput {
id: ID
name: String!
Expand Down Expand Up @@ -113,9 +142,39 @@ export const adminSchemaExtensions = gql`
STRIPE_SUBSCRIPTION_NOTIFICATION
}

"""
For codegen to work this must be non-empty
"""
input StripeSubscriptionPaymentListOptions {
skip: Int
}

"""
For codegen to work this must be non-empty
"""
input StripeSubscriptionScheduleListOptions {
skip: Int
}

type StripeSubscriptionPaymentList implements PaginatedList {
items: [StripeSubscriptionPayment!]!
totalItems: Int!
}

type StripeSubscriptionScheduleList implements PaginatedList {
items: [StripeSubscriptionSchedule!]!
totalItems: Int!
}

extend type Query {
stripeSubscriptionSchedules: [StripeSubscriptionSchedule!]!
stripeSubscriptionSchedules(
options: StripeSubscriptionScheduleListOptions
): StripeSubscriptionScheduleList!
stripeSubscriptionPayments(
options: StripeSubscriptionPaymentListOptions
): StripeSubscriptionPaymentList!
}

extend type Mutation {
upsertStripeSubscriptionSchedule(
input: UpsertStripeSubscriptionScheduleInput!
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Injectable } from '@nestjs/common';
import {
ID,
ListQueryBuilder,
RequestContext,
TransactionalConnection,
UserInputError,
} from '@vendure/core';
import {
StripeSubscriptionSchedule,
StripeSubscriptionScheduleList,
StripeSubscriptionScheduleListOptions,
SubscriptionStartMoment,
UpsertStripeSubscriptionScheduleInput,
} from '../ui/generated/graphql';
Expand All @@ -15,18 +18,24 @@ import { Schedule } from './schedule.entity';

@Injectable()
export class ScheduleService {
constructor(private connection: TransactionalConnection) {}
constructor(
private listQueryBuilder: ListQueryBuilder,
private connection: TransactionalConnection
) {}

async getSchedules(
ctx: RequestContext
): Promise<StripeSubscriptionSchedule[]> {
const schedules = await this.connection
.getRepository(ctx, Schedule)
.find({ where: { channelId: String(ctx.channelId) } });

return schedules.map((schedule) => {
return cloneSchedule(ctx, schedule);
});
ctx: RequestContext,
options: StripeSubscriptionScheduleListOptions
): Promise<StripeSubscriptionScheduleList> {
return this.listQueryBuilder
.build(Schedule, options, { ctx })
.getManyAndCount()
.then(([items, totalItems]) => ({
items: items.map((schedule) => {
return cloneSchedule(ctx, schedule);
}),
totalItems,
}));
}

async upsert(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { DeepPartial } from '@vendure/common/lib/shared-types';
import { VendureEntity, ID } from '@vendure/core';
import { Column, Entity } from 'typeorm';

@Entity()
export class StripeSubscriptionPayment extends VendureEntity {
constructor(input?: DeepPartial<StripeSubscriptionPayment>) {
super(input);
}

@Column({ nullable: true })
invoiceId!: string;

@Column({ nullable: true })
collectionMethod!: string;

@Column({ nullable: true })
eventType!: string;

@Column({ nullable: true })
charge!: number;

@Column({ nullable: true })
currency!: string;

@Column({ nullable: true })
orderCode!: string;

@Column({ nullable: true })
channelId!: string;

@Column({ nullable: true })
subscriptionId!: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,24 @@ import { Request } from 'express';
import { loggerCtx, PLUGIN_INIT_OPTIONS } from '../constants';
import { StripeSubscriptionPluginOptions } from '../stripe-subscription.plugin';
import {
StripeSubscriptionPaymentList,
StripeSubscriptionPaymentListOptions,
StripeSubscriptionPricing,
StripeSubscriptionPricingInput,
StripeSubscriptionSchedule,
StripeSubscriptionScheduleList,
StripeSubscriptionScheduleListOptions,
UpsertStripeSubscriptionScheduleInput,
} from '../ui/generated/graphql';
import { ScheduleService } from './schedule.service';
import { StripeSubscriptionService } from './stripe-subscription.service';
import { IncomingStripeWebhook } from './stripe.types';
import {
OrderLineWithSubscriptionFields,
VariantWithSubscriptionFields,
} from './subscription-custom-fields';
import { StripeInvoice } from './types/stripe-invoice';
import { StripePaymentIntent } from './types/stripe-payment-intent';
import { IncomingStripeWebhook } from './types/stripe.types';

export type RequestWithRawBody = Request & { rawBody: any };

Expand Down Expand Up @@ -141,14 +147,27 @@ export class AdminPriceIncludesTaxResolver {

@Resolver()
export class AdminResolver {
constructor(private scheduleService: ScheduleService) {}
constructor(
private stripeSubscriptionService: StripeSubscriptionService,
private scheduleService: ScheduleService
) {}

@Allow(Permission.ReadSettings)
@Query()
async stripeSubscriptionSchedules(
@Ctx() ctx: RequestContext
): Promise<StripeSubscriptionSchedule[]> {
return this.scheduleService.getSchedules(ctx);
@Ctx() ctx: RequestContext,
@Args('options') options: StripeSubscriptionScheduleListOptions
): Promise<StripeSubscriptionScheduleList> {
return this.scheduleService.getSchedules(ctx, options);
}

@Allow(Permission.ReadSettings)
@Query()
async stripeSubscriptionPayments(
@Ctx() ctx: RequestContext,
@Args('options') options: StripeSubscriptionPaymentListOptions
): Promise<StripeSubscriptionPaymentList> {
return this.stripeSubscriptionService.getPaymentEvents(ctx, options);
}

@Allow(Permission.UpdateSettings)
Expand Down Expand Up @@ -188,11 +207,11 @@ export class StripeSubscriptionController {
Logger.info(`Incoming webhook ${body.type}`, loggerCtx);
// Validate if metadata present
const orderCode =
body.data.object.metadata?.orderCode ||
body.data.object.lines?.data[0]?.metadata.orderCode;
body.data.object.metadata?.orderCode ??
(body.data.object as StripeInvoice).lines?.data[0]?.metadata.orderCode;
const channelToken =
body.data.object.metadata?.channelToken ||
body.data.object.lines?.data[0]?.metadata.channelToken;
body.data.object.metadata?.channelToken ??
(body.data.object as StripeInvoice).lines?.data[0]?.metadata.channelToken;
if (
body.type !== 'payment_intent.succeeded' &&
body.type !== 'invoice.payment_failed' &&
Expand Down Expand Up @@ -234,21 +253,45 @@ export class StripeSubscriptionController {
if (body.type === 'payment_intent.succeeded') {
await this.stripeSubscriptionService.handlePaymentIntentSucceeded(
ctx,
body,
body.data.object as StripePaymentIntent,
order
);
} else if (body.type === 'invoice.payment_succeeded') {
const invoiceObject = body.data.object as StripeInvoice;
await this.stripeSubscriptionService.handleInvoicePaymentSucceeded(
ctx,
body,
invoiceObject,
order
);
await this.stripeSubscriptionService.savePaymentEvent(
ctx,
body.type,
invoiceObject
);
} else if (body.type === 'invoice.payment_failed') {
const invoiceObject = body.data.object as StripeInvoice;
await this.stripeSubscriptionService.handleInvoicePaymentFailed(
ctx,
invoiceObject,
order
);
await this.stripeSubscriptionService.savePaymentEvent(
ctx,
body.type,
invoiceObject
);
} else if (body.type === 'invoice.payment_action_required') {
const invoiceObject = body.data.object as StripeInvoice;
await this.stripeSubscriptionService.handleInvoicePaymentFailed(
ctx,
body,
invoiceObject,
order
);
await this.stripeSubscriptionService.savePaymentEvent(
ctx,
body.type,
invoiceObject
);
}
Logger.info(`Successfully handled webhook ${body.type}`, loggerCtx);
} catch (error) {
Expand Down
Loading
Loading