Skip to content

Commit

Permalink
fix(subscription): Stripeに顧客を新規作成する前にAPIに存在確認をするようにした
Browse files Browse the repository at this point in the history
ついでにログいっぱい増やした
  • Loading branch information
nafu-at committed Oct 20, 2024
1 parent 71571c8 commit c6c47ef
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 2 deletions.
20 changes: 18 additions & 2 deletions packages/backend/src/server/StripeWebhookServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class StripeWebhookServerService {
private globalEventService: GlobalEventService,
private loggerService: LoggerService,
) {
this.logger = this.loggerService.getLogger('server', 'gray', false);
this.logger = this.loggerService.getLogger('subscription:webhook');
}

@bindThis
Expand All @@ -50,22 +50,27 @@ export class StripeWebhookServerService {
);

fastify.post('/webhook', { config: { rawBody: true }, bodyLimit: 1024 * 64 }, async (request, reply) => {
/* サブスクリプションの機能が無効にされていてもWebhookは処理するようにする。
const instance = await this.metaService.fetch(true);
if (!(instance.enableSubscriptions)) {
return reply.code(503);
}
*/
if (!(this.config.stripe && this.config.stripe.secretKey && this.config.stripe.webhookSecret)) {
this.logger.error('The Stripe webhook configuration is not set correctly.');
return reply.code(503);
}

const body = request.rawBody;
if (!body) {
this.logger.error('Request body from Stripe webhook is empty.');
return reply.code(400);
}

// Retrieve the event by verifying the signature using the raw body and secret.
const signature = request.headers['stripe-signature'];
if (!signature) { // Check if signature exists.
this.logger.error('Webhook does not contain a signature.');
return reply.code(400);
}

Expand All @@ -74,6 +79,7 @@ export class StripeWebhookServerService {
try {
event = stripe.webhooks.constructEvent(body, signature, this.config.stripe.webhookSecret);
} catch (err) {
this.logger.error('Webhook signature verification or event parsing failed.', { error: err });
return reply.code(400);
}

Expand All @@ -83,6 +89,7 @@ export class StripeWebhookServerService {
const userProfile = await this.userProfilesRepository.findOneBy({ stripeCustomerId: customer });

if (!userProfile) {
this.logger.warn(`CustomerId: "${customer}" has no user profile found.`);
return reply.code(400);
}
reply.code(204); // Stripeへの応答を設定
Expand All @@ -98,6 +105,7 @@ export class StripeWebhookServerService {
const user = await this.usersRepository.findOneByOrFail({ id: userProfile.userId });

if (user.stripeSubscriptionId != null) {
this.logger.info(`Subscription already exists for user ID ${user.id}. No processing is needed.`);
return; // 既にサブスクリプションが存在する場合は何もしない
}

Expand All @@ -106,6 +114,7 @@ export class StripeWebhookServerService {
// ユーザーにロールが割り当てられていない場合、ロールを割り当てる
if (!roles.some((role) => role.id === subscriptionPlan.roleId)) {
await this.roleService.assign(userProfile.userId, subscriptionPlan.roleId);
this.logger.info(`${userProfile.userId} has been assigned the role "${subscriptionPlan.roleId}" by the subscription creation event.`);
}
});
}
Expand Down Expand Up @@ -141,12 +150,14 @@ export class StripeWebhookServerService {
for (const role of roles) {
if (roleIds.includes(role.id) && role.id !== subscriptionPlan.roleId) {
await this.roleService.unassign(user.id, role.id); // 他のサブスクリプションプランのロールが割り当てられている場合、ロールを解除する
this.logger.info(`${user.id} has been unassigned the role "${role.id}" by the subscription update event.`);
}
}

// ユーザーにロールが割り当てられていない場合、ロールを割り当てる
if (!roles.some((role) => role.id === subscriptionPlan.roleId)) {
await this.roleService.assign(user.id, subscriptionPlan.roleId);
this.logger.info(`${user.id} has been assigned the role "${subscriptionPlan.roleId}" by the subscription update event.`);
}
});
} else if (subscriptionPlan.id !== user.subscriptionPlanId) { // サブスクリプションプランが変更された場合
Expand All @@ -155,18 +166,21 @@ export class StripeWebhookServerService {
// 旧サブスクリプションプランのロールが割り当てられている場合、ロールを解除する
if (roles.some((role) => role.id === oldSubscriptionPlan.roleId)) {
await this.roleService.unassign(user.id, oldSubscriptionPlan.roleId);
this.logger.info(`${user.id} has been unassigned the role "${oldSubscriptionPlan.roleId}" by the subscription update event.`);
}

// 新しいサブスクリプションプランのロールが割り当てられていない場合、ロールを割り当てる
if (!roles.some((role) => role.id === subscriptionPlan.roleId)) {
await this.roleService.assign(user.id, subscriptionPlan.roleId);
this.logger.info(`${user.id} has been assigned the role "${subscriptionPlan.roleId}" by the subscription update event.`);
}
});
} else if (previousData && previousData.status) { // サブスクリプションステータスが変更された場合
await this.roleService.getUserRoles(user.id).then(async (roles) => {
// ユーザーにロールが割り当てられていない場合、ロールを割り当てる
if (!roles.some((role) => role.id === subscriptionPlan.roleId)) {
await this.roleService.assign(user.id, subscriptionPlan.roleId);
this.logger.info(`${user.id} has been assigned the role "${subscriptionPlan.roleId}" by the subscription update event.`);
}
});
}
Expand Down Expand Up @@ -202,6 +216,7 @@ export class StripeWebhookServerService {
await this.roleService.getUserRoles(userProfile.userId).then(async (roles) => {
if (roles.some((role) => role.id === subscriptionPlan.roleId)) {
await this.roleService.unassign(userProfile.userId, subscriptionPlan.roleId);
this.logger.info(`${userProfile.userId} has been unassigned the role "${subscriptionPlan.roleId}" by the subscription deletion event.`);
}
});

Expand All @@ -222,6 +237,7 @@ export class StripeWebhookServerService {

default:
// Unhandled event type.
this.logger.warn(`Unhandled event type: ${event.type}`);
return reply.code(400);
}
});
Expand Down
18 changes: 18 additions & 0 deletions packages/backend/src/server/api/endpoints/subscription/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { MetaService } from '@/core/MetaService.js';
import type { Config } from '@/config.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js';
import { LoggerService } from "@/core/LoggerService.js";

export const meta = {
tags: ['subscription'],
Expand Down Expand Up @@ -46,6 +47,12 @@ export const meta = {
id: 'f1b0a9f3-9f8a-4e8c-9b4d-0d2c1b7a9c0b',
},

statusInconsistency: {
message: 'The information registered in the payment service and the information stored on the server do not match.',
code: 'STATUS_INCONSENT',
id: 'f1d204e7-276a-4277-9e7b-14f5e038c2d8',
},

unavailable: {
message: 'Subscription unavailable.',
code: 'UNAVAILABLE',
Expand Down Expand Up @@ -75,8 +82,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private subscriptionPlansRepository: SubscriptionPlansRepository,
private roleService: RoleService,
private metaService: MetaService,
private loggerService: LoggerService,
) {
super(meta, paramDef, async (ps, me) => {
const logger = this.loggerService.getLogger('subscription:create');
const instance = await this.metaService.fetch(true);
if (!(instance.enableSubscriptions)) {
throw new ApiError(meta.errors.unavailable);
Expand All @@ -101,13 +110,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-

const stripe = new Stripe(this.config.stripe.secretKey);
if (!userProfile.stripeCustomerId) {
const searchCustomer = await stripe.customers.search({ query: `email:"${userProfile.email}"` });
if (searchCustomer.data.length !== 0) {
logger.info(`User with email ${userProfile.email} is already registered in Stripe but not recorded in UserProfile.`);
throw new ApiError(meta.errors.statusInconsistency);
}

const makeCustomer = await stripe.customers.create({
email: userProfile.email,
});
await this.userProfilesRepository.update({ userId: user.id }, {
stripeCustomerId: makeCustomer.id,
});
userProfile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
logger.info(`New Stripe customer created with ID ${makeCustomer.id} and associated with user ${user.id}`);
}

const subscriptionStatus = user.subscriptionStatus;
Expand Down Expand Up @@ -136,6 +152,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
for (const role of roles) {
if (roleIds.includes(role.id)) {
await this.roleService.unassign(user.id, role.id);
logger.info(`${user.id} has been unassigned the role "${role.id}".`);
}
}
});
Expand All @@ -151,6 +168,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}

await stripe.subscriptionItems.update(subscriptionItem.id, { plan: plan.stripePriceId });
logger.info(`Subscription plan changed for user ${user.id} to plan ${plan.id}`);

return;
} else {
Expand Down

0 comments on commit c6c47ef

Please sign in to comment.