Skip to content

Commit

Permalink
feat(stripe-subscription): wip
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnvdbrug committed Sep 8, 2023
1 parent 3379faf commit 9823ad2
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 54 deletions.
3 changes: 1 addition & 2 deletions packages/vendure-plugin-stripe-subscription/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ plugins: [
6. Start the Vendure server and login to the admin UI
7. Go to `Settings > Subscriptions` and create a Schedule.
8. Create a variant and select a schedule in the variant detail screen in the admin UI.
9. Create a payment method with the code `stripe-subscription-payment` and select `stripe-subscription` as handler. **
Your payment method MUST have 'stripe-subscription' in the code field**
9. Create a payment method with the code `stripe-subscription-payment` and select `stripe-subscription` as handler. You can (and should) have only 1 payment method with the Stripe Subscription handler per channel.
10. Set your API key from Stripe in the apiKey field.
11. Get the webhook secret from you Stripe dashboard and save it on the payment method.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export class StripeSubscriptionController {
}
// Validate signature
const { stripeClient } =
await this.stripeSubscriptionService.getStripeHandler(ctx, order.id);
await this.stripeSubscriptionService.getStripeContext(ctx);
if (!this.options?.disableWebhookSignatureChecking) {
stripeClient.validateWebhookSignature(request.rawBody, signature);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,10 @@ import {
Logger,
PaymentMethodHandler,
SettlePaymentResult,
UserInputError,
} from '@vendure/core';
import { RequestContext } from '@vendure/core/dist/api/common/request-context';
import { Order, Payment, PaymentMethod } from '@vendure/core/dist/entity';
import {
CancelPaymentErrorResult,
CancelPaymentResult,
} from '@vendure/core/dist/config/payment/payment-method-handler';
import {
OrderLineWithSubscriptionFields,
OrderWithSubscriptionFields,
} from './subscription-custom-fields';
import { StripeSubscriptionService } from './stripe-subscription.service';
import { StripeClient } from './stripe.client';
import { loggerCtx } from '../constants';
import { printMoney } from './pricing.helper';
import { StripeSubscriptionService } from './stripe-subscription.service';

let service: StripeSubscriptionService;
export const stripeSubscriptionHandler = new PaymentMethodHandler({
Expand Down Expand Up @@ -111,7 +99,7 @@ export const stripeSubscriptionHandler = new PaymentMethodHandler({
payment,
args
): Promise<CreateRefundResult> {
const { stripeClient } = await service.getStripeHandler(ctx, order.id);
const { stripeClient } = await service.getStripeContext(ctx);
const refund = await stripeClient.refunds.create({
payment_intent: payment.transactionId,
amount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
OrderService,
OrderStateTransitionError,
PaginatedList,
PaymentMethod,
PaymentMethodService,
ProductVariantService,
RequestContext,
Expand Down Expand Up @@ -63,10 +64,9 @@ import { StripeSubscriptionPayment } from './stripe-subscription-payment.entity'
import { StripeInvoice } from './types/stripe-invoice';
import { StripePaymentIntent } from './types/stripe-payment-intent';

export interface StripeHandlerConfig {
paymentMethodCode: string;
export interface StripeContext {
paymentMethod: PaymentMethod;
stripeClient: StripeClient;
webhookSecret: string;
}

interface CreateSubscriptionsJob {
Expand Down Expand Up @@ -212,7 +212,7 @@ export class StripeSubscriptionService {
);
}
await this.entityHydrator.hydrate(ctx, line, { relations: ['order'] });
const { stripeClient } = await this.getStripeHandler(ctx, line.order.id);
const { stripeClient } = await this.getStripeContext(ctx);
for (const subscriptionId of line.customFields.subscriptionIds) {
try {
await stripeClient.subscriptions.update(subscriptionId, {
Expand Down Expand Up @@ -245,11 +245,9 @@ export class StripeSubscriptionService {
}

/**
*
*
*/
async getSubscriptions(ctx: RequestContext) {

}
async getSubscriptions(ctx: RequestContext) {}

async createPaymentIntent(ctx: RequestContext): Promise<string> {
let order = (await this.activeOrderService.getActiveOrder(
Expand Down Expand Up @@ -283,8 +281,10 @@ export class StripeSubscriptionService {
'Cannot create payment intent for order without shippingMethod'
);
}
const { stripeClient } = await this.getStripeHandler(ctx, order.id);
const stripeCustomer = await stripeClient.getOrCreateClient(order.customer);
const { stripeClient } = await this.getStripeContext(ctx);
const stripeCustomer = await stripeClient.getOrCreateCustomer(
order.customer
);
this.customerService
.update(ctx, {
id: order.customer.id,
Expand Down Expand Up @@ -505,7 +505,9 @@ export class StripeSubscriptionService {
object: StripePaymentIntent,
order: Order
): Promise<void> {
const { paymentMethodCode } = await this.getStripeHandler(ctx, order.id);
const {
paymentMethod: { code: paymentMethodCode },
} = await this.getStripeContext(ctx);
if (!object.customer) {
await this.logHistoryEntry(
ctx,
Expand Down Expand Up @@ -592,7 +594,7 @@ export class StripeSubscriptionService {
`Not creating subscriptions for order ${order.code}, because it doesn't have any subscription products`
);
}
const { stripeClient } = await this.getStripeHandler(ctx, order.id);
const { stripeClient } = await this.getStripeContext(ctx);
const customer = await stripeClient.customers.retrieve(stripeCustomerId);
if (!customer) {
throw Error(
Expand Down Expand Up @@ -775,32 +777,25 @@ export class StripeSubscriptionService {
}

/**
* Get the paymentMethod with the stripe handler, should be only 1!
* Get the Stripe context for the current channel.
* The Stripe context consists of the Stripe client and the Vendure payment method connected to the Stripe account
*/
async getStripeHandler(
ctx: RequestContext,
orderId: ID
): Promise<StripeHandlerConfig> {
const paymentMethodQuotes =
await this.orderService.getEligiblePaymentMethods(ctx, orderId);
const paymentMethodQuote = paymentMethodQuotes
.filter((quote) => quote.isEligible)
.find((pm) => pm.code.indexOf('stripe-subscription') > -1);
if (!paymentMethodQuote) {
async getStripeContext(ctx: RequestContext): Promise<StripeContext> {
const paymentMethods = await this.paymentMethodService.findAll(ctx, {
filter: { enabled: { eq: true } },
});
const stripePaymentMethods = paymentMethods.items.filter(
(pm) => pm.handler.code === stripeSubscriptionHandler.code
);
if (stripePaymentMethods.length > 1) {
throw new UserInputError(
`No eligible payment method found with code 'stripe-subscription'`
`Multiple payment methods found with handler 'stripe-subscription', there should only be 1 per channel!`
);
}
const paymentMethod = await this.paymentMethodService.findOne(
ctx,
paymentMethodQuote.id
);
if (
!paymentMethod ||
paymentMethod.handler.code !== stripeSubscriptionHandler.code
) {
const paymentMethod = stripePaymentMethods[0];
if (!paymentMethod) {
throw new UserInputError(
`Payment method '${paymentMethodQuote.code}' doesn't have handler '${stripeSubscriptionHandler.code}' configured.`
`No enabled payment method found with handler 'stripe-subscription'`
);
}
const apiKey = paymentMethod.handler.args.find(
Expand All @@ -819,11 +814,10 @@ export class StripeSubscriptionService {
);
}
return {
paymentMethodCode: paymentMethod.code,
paymentMethod: paymentMethod,
stripeClient: new StripeClient(webhookSecret, apiKey, {
apiVersion: null as any, // Null uses accounts default version
}),
webhookSecret,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ interface SubscriptionInput {
*/
export class StripeClient extends Stripe {
constructor(
private webhookSecret: string,
public webhookSecret: string,
apiKey: string,
config: Stripe.StripeConfig
) {
super(apiKey, config);
}

async getOrCreateClient(
async getOrCreateCustomer(
customer: CustomerWithSubscriptionFields
): Promise<Stripe.Customer> {
if (customer.customFields?.stripeSubscriptionCustomerId) {
Expand Down

0 comments on commit 9823ad2

Please sign in to comment.