Browse Source

chore(api): add more logs to polar integration

pull/52/head
isra el 1 year ago
parent
commit
2e924f277a
  1. 3
      api/src/billing/billing.controller.ts
  2. 2
      api/src/billing/billing.module.ts
  3. 51
      api/src/billing/billing.service.ts
  4. 24
      api/src/billing/schemas/polar-webhook-payload.schema.ts

3
api/src/billing/billing.controller.ts

@ -46,6 +46,9 @@ export class BillingController {
req.headers, req.headers,
) )
// store the payload in the database
await this.billingService.storePolarWebhookPayload(payload)
// Handle Polar.sh webhook events // Handle Polar.sh webhook events
switch (payload.type) { switch (payload.type) {
case 'subscription.created': case 'subscription.created':

2
api/src/billing/billing.module.ts

@ -9,12 +9,14 @@ import { MongooseModule } from '@nestjs/mongoose'
import { AuthModule } from 'src/auth/auth.module' import { AuthModule } from 'src/auth/auth.module'
import { UsersModule } from 'src/users/users.module' import { UsersModule } from 'src/users/users.module'
import { GatewayModule } from 'src/gateway/gateway.module' import { GatewayModule } from 'src/gateway/gateway.module'
import { PolarWebhookPayload, PolarWebhookPayloadSchema } from './schemas/polar-webhook-payload.schema'
@Module({ @Module({
imports: [ imports: [
MongooseModule.forFeature([ MongooseModule.forFeature([
{ name: Plan.name, schema: PlanSchema }, { name: Plan.name, schema: PlanSchema },
{ name: Subscription.name, schema: SubscriptionSchema }, { name: Subscription.name, schema: SubscriptionSchema },
{ name: PolarWebhookPayload.name, schema: PolarWebhookPayloadSchema },
]), ]),
AuthModule, AuthModule,
UsersModule, UsersModule,

51
api/src/billing/billing.service.ts

@ -1,6 +1,6 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common' import { HttpException, HttpStatus, Injectable } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose' import { InjectModel } from '@nestjs/mongoose'
import { Model } from 'mongoose'
import { Model, Types } from 'mongoose'
import { Plan, PlanDocument } from './schemas/plan.schema' import { Plan, PlanDocument } from './schemas/plan.schema'
import { import {
Subscription, Subscription,
@ -12,6 +12,7 @@ import { CheckoutResponseDTO, PlanDTO } from './billing.dto'
import { SMSDocument } from 'src/gateway/schemas/sms.schema' import { SMSDocument } from 'src/gateway/schemas/sms.schema'
import { SMS } from 'src/gateway/schemas/sms.schema' import { SMS } from 'src/gateway/schemas/sms.schema'
import { validateEvent } from '@polar-sh/sdk/webhooks' import { validateEvent } from '@polar-sh/sdk/webhooks'
import { PolarWebhookPayload, PolarWebhookPayloadDocument } from './schemas/polar-webhook-payload.schema'
@Injectable() @Injectable()
export class BillingService { export class BillingService {
@ -23,6 +24,8 @@ export class BillingService {
private subscriptionModel: Model<SubscriptionDocument>, private subscriptionModel: Model<SubscriptionDocument>,
@InjectModel(User.name) private userModel: Model<UserDocument>, @InjectModel(User.name) private userModel: Model<UserDocument>,
@InjectModel(SMS.name) private smsModel: Model<SMSDocument>, @InjectModel(SMS.name) private smsModel: Model<SMSDocument>,
@InjectModel(PolarWebhookPayload.name)
private polarWebhookPayloadModel: Model<PolarWebhookPayloadDocument>,
) { ) {
this.initializePlans() this.initializePlans()
this.polarApi = new Polar({ this.polarApi = new Polar({
@ -228,11 +231,11 @@ export class BillingService {
newPlanName?: string newPlanName?: string
newPlanPolarProductId?: string newPlanPolarProductId?: string
}) { }) {
// switch the subscription to the new one
// deactivate the current active subscription
// activate the new subscription if it exists or create a new one
console.log(`Switching plan for user: ${userId}`);
// Convert userId to ObjectId
const userObjectId = new Types.ObjectId(userId);
// get the plan from the polarProductId
let plan: PlanDocument let plan: PlanDocument
if (newPlanPolarProductId) { if (newPlanPolarProductId) {
plan = await this.planModel.findOne({ plan = await this.planModel.findOne({
@ -246,18 +249,24 @@ export class BillingService {
throw new Error('Plan not found') throw new Error('Plan not found')
} }
// if any of the subscriptions that are not the new plan are active, deactivate them
await this.subscriptionModel.updateMany(
{ user: userId, plan: { $ne: plan._id }, isActive: true },
console.log(`Found plan: ${plan.name}`);
// Deactivate current active subscriptions
const result = await this.subscriptionModel.updateMany(
{ user: userObjectId, plan: { $ne: plan._id }, isActive: true },
{ isActive: false, endDate: new Date() }, { isActive: false, endDate: new Date() },
) )
console.log(`Deactivated subscriptions: ${result.modifiedCount}`);
// create or update the new subscription
await this.subscriptionModel.updateOne(
{ user: userId, plan: plan._id },
// Create or update the new subscription
const updateResult = await this.subscriptionModel.updateOne(
{ user: userObjectId, plan: plan._id },
{ isActive: true }, { isActive: true },
{ upsert: true }, { upsert: true },
) )
console.log(`Updated or created subscription: ${updateResult.upsertedCount > 0 ? 'Created' : 'Updated'}`);
return { success: true, plan: plan.name };
} }
async canPerformAction( async canPerformAction(
@ -405,6 +414,9 @@ export class BillingService {
'webhook-signature': headers['webhook-signature'] ?? '', 'webhook-signature': headers['webhook-signature'] ?? '',
} }
console.log('webhookHeaders')
console.log(webhookHeaders)
try { try {
const webhookPayload = validateEvent( const webhookPayload = validateEvent(
payload, payload,
@ -413,7 +425,24 @@ export class BillingService {
) )
return webhookPayload return webhookPayload
} catch (error) { } catch (error) {
console.log('failed to validate polar webhook payload')
console.error(error)
throw new Error('Invalid webhook payload') throw new Error('Invalid webhook payload')
} }
} }
async storePolarWebhookPayload(payload: any) {
const userId = payload.data?.metadata?.userId || payload.data?.userId
const eventType = payload.type
const name = payload.data?.customer?.name
const email = payload.data?.customer?.email
await this.polarWebhookPayloadModel.create({
userId,
eventType,
name,
email,
payload,
})
}
} }

24
api/src/billing/schemas/polar-webhook-payload.schema.ts

@ -0,0 +1,24 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
import { Document } from 'mongoose'
export type PolarWebhookPayloadDocument = PolarWebhookPayload & Document
@Schema({ timestamps: true })
export class PolarWebhookPayload {
@Prop()
userId: string
@Prop()
eventType: string
@Prop()
name: string
@Prop()
email: string
@Prop({ type: Object })
payload: Record<string, any>
}
export const PolarWebhookPayloadSchema = SchemaFactory.createForClass(PolarWebhookPayload)
Loading…
Cancel
Save