diff --git a/packages/adapter-utils/src/entitlement/entitlement.test.ts b/packages/adapter-utils/src/entitlement/entitlement.test.ts index d8f0dc5..b698d10 100644 --- a/packages/adapter-utils/src/entitlement/entitlement.test.ts +++ b/packages/adapter-utils/src/entitlement/entitlement.test.ts @@ -1,104 +1,106 @@ import { describe, it, expect, vi } from "vitest"; -import { Entitlement } from "./entitlement"; import type { - Benefit, - WebhookBenefitGrantCreatedPayload, - WebhookBenefitGrantRevokedPayload, + Benefit, + WebhookBenefitGrantCreatedPayload, + WebhookBenefitGrantRevokedPayload, } from "@polar-sh/sdk/models/components"; +import { EntitlementStrategy } from "./entitlement"; -describe("Entitlement", () => { - it("should run grant on handler", () => { - const onGrant = vi.fn(); - const onRevoke = vi.fn(); +describe("EntitlementStrategy", () => { + it("should run grant on handler", () => { + const onGrant = vi.fn(); + const onRevoke = vi.fn(); - const entitlement = Entitlement<{ test: string }>().grant(onGrant); + const entitlement = new EntitlementStrategy<{ test: string }>().grant( + onGrant, + ); - expect(entitlement).toBeDefined(); + expect(entitlement).toBeDefined(); - const payload = { - type: "benefit_grant.created", - data: { - id: "123", - createdAt: new Date(), - modifiedAt: new Date(), - isGranted: true, - benefitId: "123", - customerId: "123", - subscriptionId: "123", - orderId: "123", - userId: "123", - isRevoked: false, - properties: { test: "test" }, - customer: { - email: "test@test.com", - id: "123", - createdAt: new Date(), - modifiedAt: new Date(), - metadata: {}, - emailVerified: true, - billingAddress: { - line1: "123", - line2: "123", - city: "123", - state: "123", - postalCode: "123", - country: "123", - }, - name: "Test", - taxId: ["123"], - organizationId: "123", - avatarUrl: "123", - }, - benefit: { - id: "123", - createdAt: new Date(), - modifiedAt: new Date(), - description: "Test", - selectable: true, - slug: "test", - } as unknown as Benefit, - }, - } as WebhookBenefitGrantCreatedPayload; + const payload = { + type: "benefit_grant.created", + data: { + id: "123", + createdAt: new Date(), + modifiedAt: new Date(), + isGranted: true, + benefitId: "123", + customerId: "123", + subscriptionId: "123", + orderId: "123", + userId: "123", + isRevoked: false, + properties: { test: "test" }, + customer: { + email: "test@test.com", + id: "123", + createdAt: new Date(), + modifiedAt: new Date(), + metadata: {}, + emailVerified: true, + billingAddress: { + line1: "123", + line2: "123", + city: "123", + state: "123", + postalCode: "123", + country: "123", + }, + name: "Test", + taxId: ["123"], + organizationId: "123", + avatarUrl: "123", + }, + benefit: { + id: "123", + createdAt: new Date(), + modifiedAt: new Date(), + selectable: true, + description: "test", + } as unknown as Benefit, + }, + } as WebhookBenefitGrantCreatedPayload; - entitlement.handler("test")(payload); + entitlement.handler("test")(payload); - expect(onGrant).toHaveBeenCalledWith({ - payload, - customer: payload.data.customer, - properties: payload.data.properties, - }); + expect(onGrant).toHaveBeenCalledWith({ + payload, + customer: payload.data.customer, + properties: payload.data.properties, + }); - expect(onRevoke).not.toHaveBeenCalled(); - }); + expect(onRevoke).not.toHaveBeenCalled(); + }); - it("should run revoke on handler", () => { - const onGrant = vi.fn(); - const onRevoke = vi.fn(); + it("should run revoke on handler", () => { + const onGrant = vi.fn(); + const onRevoke = vi.fn(); - const entitlement = Entitlement<{ test: string }>() - .grant(onGrant) - .revoke(onRevoke); + const entitlement = new EntitlementStrategy<{ test: string }>() + .grant(onGrant) + .revoke(onRevoke); - const payload = { - type: "benefit_grant.revoked", - data: { - id: "123", - createdAt: new Date(), - modifiedAt: new Date(), - isGranted: false, - benefitId: "123", - customerId: "123", - }, - } as WebhookBenefitGrantRevokedPayload; + const payload = { + type: "benefit_grant.revoked", + data: { + id: "123", + createdAt: new Date(), + modifiedAt: new Date(), + isGranted: false, + benefitId: "123", + customerId: "123", + benefit: { description: "test" }, + }, + } as WebhookBenefitGrantRevokedPayload; - entitlement.handler("test")(payload); + entitlement.handler("test")(payload); - expect(onGrant).not.toHaveBeenCalled(); + expect(onGrant).not.toHaveBeenCalled(); - expect(onRevoke).toHaveBeenCalledWith({ - payload, - customer: payload.data.customer, - properties: payload.data.properties, - }); - }); + expect(onRevoke).toHaveBeenCalledWith({ + payload, + customer: payload.data.customer, + properties: payload.data.properties, + }); + }); }); diff --git a/packages/adapter-utils/src/entitlement/entitlement.ts b/packages/adapter-utils/src/entitlement/entitlement.ts index b98939d..921d237 100644 --- a/packages/adapter-utils/src/entitlement/entitlement.ts +++ b/packages/adapter-utils/src/entitlement/entitlement.ts @@ -1,81 +1,89 @@ import type { - Customer, - WebhookBenefitGrantCreatedPayload, - WebhookBenefitGrantRevokedPayload, + Customer, + WebhookBenefitGrantCreatedPayload, + WebhookBenefitGrantRevokedPayload, } from "@polar-sh/sdk/models/components"; export type EntitlementProperties = Record; export type EntitlementHandler = ( - payload: - | WebhookBenefitGrantCreatedPayload - | WebhookBenefitGrantRevokedPayload, + payload: + | WebhookBenefitGrantCreatedPayload + | WebhookBenefitGrantRevokedPayload, ) => Promise; export interface EntitlementContext { - customer: Customer; - properties: T; - payload: - | WebhookBenefitGrantCreatedPayload - | WebhookBenefitGrantRevokedPayload; + customer: Customer; + properties: T; + payload: + | WebhookBenefitGrantCreatedPayload + | WebhookBenefitGrantRevokedPayload; } export class EntitlementStrategy { - private grantCallbacks: (( - context: EntitlementContext, - ) => Promise)[] = []; - private revokeCallbacks: (( - context: EntitlementContext, - ) => Promise)[] = []; + private grantCallbacks: (( + context: EntitlementContext, + ) => Promise)[] = []; - public grant(callback: (context: EntitlementContext) => Promise) { - this.grantCallbacks.push(callback); - return this; - } + private revokeCallbacks: (( + context: EntitlementContext, + ) => Promise)[] = []; - public revoke(callback: (context: EntitlementContext) => Promise) { - this.revokeCallbacks.push(callback); - return this; - } + public grant(callback: (context: EntitlementContext) => Promise) { + this.grantCallbacks.push(callback); + return this; + } - public handler(slug: string): EntitlementHandler { - return async ( - payload: - | WebhookBenefitGrantCreatedPayload - | WebhookBenefitGrantRevokedPayload, - ) => { - if (payload.data.benefit.slug === slug) { - switch (payload.type) { - case "benefit_grant.created": - await Promise.all( - this.grantCallbacks.map((callback) => - callback({ - customer: payload.data.customer, - properties: payload.data.properties as T, - payload, - }), - ), - ); - break; - case "benefit_grant.revoked": - await Promise.all( - this.revokeCallbacks.map((callback) => - callback({ - customer: payload.data.customer, - properties: payload.data.properties as T, - payload, - }), - ), - ); - break; - } - } - }; - } + public revoke(callback: (context: EntitlementContext) => Promise) { + this.revokeCallbacks.push(callback); + return this; + } + + public handler(slug: string): EntitlementHandler { + return async ( + payload: + | WebhookBenefitGrantCreatedPayload + | WebhookBenefitGrantRevokedPayload, + ) => { + if (payload.data.benefit.description === slug) { + switch (payload.type) { + case "benefit_grant.created": + await Promise.all( + this.grantCallbacks.map((callback) => + callback({ + customer: payload.data.customer, + properties: payload.data.properties as T, + payload, + }), + ), + ); + break; + case "benefit_grant.revoked": + await Promise.all( + this.revokeCallbacks.map((callback) => + callback({ + customer: payload.data.customer, + properties: payload.data.properties as T, + payload, + }), + ), + ); + break; + } + } + }; + } } -export const Entitlement = < - T extends EntitlementProperties = EntitlementProperties, ->() => { - return new EntitlementStrategy(); -}; +export class Entitlements { + static handlers = [] as EntitlementHandler[]; + + static use( + slug: string, + strategy: EntitlementStrategy, + ) { + this.handlers.push(strategy.handler(slug)); + + return this; + } +} diff --git a/packages/adapter-utils/src/entitlements/Figma/Figma.test.ts b/packages/adapter-utils/src/entitlements/Figma/Figma.test.ts deleted file mode 100644 index db76f83..0000000 --- a/packages/adapter-utils/src/entitlements/Figma/Figma.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { it, describe, expect } from "vitest"; -import { FigmaTeam, type FigmaTeamEntitlementProperties } from "./Figma"; -import type { EntitlementContext } from "../../entitlement/entitlement"; -import type { - Benefit, - WebhookBenefitGrantCreatedPayload, -} from "@polar-sh/sdk/models/components"; -import type { Customer } from "@polar-sh/sdk/models/components"; - -describe("FigmaTeam", () => { - it("should grant access to a team", async () => { - const payload = { - type: "benefit_grant.created", - data: { - id: "123", - createdAt: new Date(), - modifiedAt: new Date(), - isGranted: true, - benefitId: "123", - customerId: "123", - subscriptionId: "123", - orderId: "123", - userId: "123", - isRevoked: false, - customer: { - email: "test@test.com", - } as Customer, - properties: { - figmaTeamId: "123", - }, - benefit: { - id: "123", - } as Benefit, - }, - } as WebhookBenefitGrantCreatedPayload; - - const entitlement = FigmaTeam(payload); - - expect(entitlement).toBeDefined(); - }); -}); diff --git a/packages/adapter-utils/src/entitlements/Figma/Figma.ts b/packages/adapter-utils/src/entitlements/Figma/Figma.ts deleted file mode 100644 index 0b0fee5..0000000 --- a/packages/adapter-utils/src/entitlements/Figma/Figma.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Entitlement } from "../../entitlement/entitlement"; - -export interface FigmaTeamEntitlementProperties { - [key: string]: string; - figmaTeamId: string; -} - -export const FigmaTeam = Entitlement() - .grant(async (context) => { - /** figma.team.addMember(context.properties.figmaTeamId, context.customer.email) */ - }) - .revoke(async (context) => { - /** figma.team.removeMember(context.properties.figmaTeamId, context.customer.email) */ - }) - .handler("figma-team"); diff --git a/packages/adapter-utils/src/webhooks/webhooks.test.ts b/packages/adapter-utils/src/webhooks/webhooks.test.ts index 5d617dc..178a38c 100644 --- a/packages/adapter-utils/src/webhooks/webhooks.test.ts +++ b/packages/adapter-utils/src/webhooks/webhooks.test.ts @@ -1,253 +1,297 @@ +import { + EntitlementProperties, + Entitlements, + EntitlementStrategy, +} from "../entitlement/entitlement"; import { handleWebhookPayload } from "./webhooks"; import { describe, expect, it, vi } from "vitest"; describe("webhooks", () => { - it("should handle webhook payload", async () => { - const onPayload = vi.fn(); - - await handleWebhookPayload({ type: "checkout.created", data: {} } as any, { - webhookSecret: "test", - onPayload, - }); - - expect(onPayload).toHaveBeenCalledWith({ - type: "checkout.created", - data: {}, - }); - }); - - it("should handle webhook payload with checkout created", async () => { - const onCheckoutCreated = vi.fn(); - - await handleWebhookPayload({ type: "checkout.created", data: {} } as any, { - webhookSecret: "test", - onCheckoutCreated, - }); - expect(onCheckoutCreated).toHaveBeenCalledWith({ - type: "checkout.created", - data: {}, - }); - }); - - it("should handle webhook payload with checkout updated", async () => { - const onCheckoutUpdated = vi.fn(); - - await handleWebhookPayload({ type: "checkout.updated", data: {} } as any, { - webhookSecret: "test", - onCheckoutUpdated, - }); - expect(onCheckoutUpdated).toHaveBeenCalledWith({ - type: "checkout.updated", - data: {}, - }); - }); - - it("should handle webhook payload with order created", async () => { - const onOrderCreated = vi.fn(); - - await handleWebhookPayload({ type: "order.created", data: {} } as any, { - webhookSecret: "test", - onOrderCreated, - }); - expect(onOrderCreated).toHaveBeenCalledWith({ - type: "order.created", - data: {}, - }); - }); - - it("should handle webhook payload with subscription created", async () => { - const onSubscriptionCreated = vi.fn(); - - await handleWebhookPayload( - { type: "subscription.created", data: {} } as any, - { - webhookSecret: "test", - onSubscriptionCreated, - }, - ); - expect(onSubscriptionCreated).toHaveBeenCalledWith({ - type: "subscription.created", - data: {}, - }); - }); - - it("should handle webhook payload with subscription updated", async () => { - const onSubscriptionUpdated = vi.fn(); - - await handleWebhookPayload( - { type: "subscription.updated", data: {} } as any, - { - webhookSecret: "test", - onSubscriptionUpdated, - }, - ); - expect(onSubscriptionUpdated).toHaveBeenCalledWith({ - type: "subscription.updated", - data: {}, - }); - }); - - it("should handle webhook payload with subscription active", async () => { - const onSubscriptionActive = vi.fn(); - - await handleWebhookPayload( - { type: "subscription.active", data: {} } as any, - { - webhookSecret: "test", - onSubscriptionActive, - }, - ); - expect(onSubscriptionActive).toHaveBeenCalledWith({ - type: "subscription.active", - data: {}, - }); - }); - - it("should handle webhook payload with subscription canceled", async () => { - const onSubscriptionCanceled = vi.fn(); - - await handleWebhookPayload( - { type: "subscription.canceled", data: {} } as any, - { - webhookSecret: "test", - onSubscriptionCanceled, - }, - ); - expect(onSubscriptionCanceled).toHaveBeenCalledWith({ - type: "subscription.canceled", - data: {}, - }); - }); - - it("should handle webhook payload with subscription revoked", async () => { - const onSubscriptionRevoked = vi.fn(); - - await handleWebhookPayload( - { type: "subscription.revoked", data: {} } as any, - { - webhookSecret: "test", - onSubscriptionRevoked, - }, - ); - expect(onSubscriptionRevoked).toHaveBeenCalledWith({ - type: "subscription.revoked", - data: {}, - }); - }); - - it("should handle webhook payload with product created", async () => { - const onProductCreated = vi.fn(); - - await handleWebhookPayload({ type: "product.created", data: {} } as any, { - webhookSecret: "test", - onProductCreated, - }); - expect(onProductCreated).toHaveBeenCalledWith({ - type: "product.created", - data: {}, - }); - }); - - it("should handle webhook payload with product updated", async () => { - const onProductUpdated = vi.fn(); - - await handleWebhookPayload({ type: "product.updated", data: {} } as any, { - webhookSecret: "test", - onProductUpdated, - }); - expect(onProductUpdated).toHaveBeenCalledWith({ - type: "product.updated", - data: {}, - }); - }); - - it("should handle webhook payload with organization updated", async () => { - const onOrganizationUpdated = vi.fn(); - - await handleWebhookPayload( - { type: "organization.updated", data: {} } as any, - { - webhookSecret: "test", - onOrganizationUpdated, - }, - ); - expect(onOrganizationUpdated).toHaveBeenCalledWith({ - type: "organization.updated", - data: {}, - }); - }); - - it("should handle webhook payload with benefit created", async () => { - const onBenefitCreated = vi.fn(); - - await handleWebhookPayload({ type: "benefit.created", data: {} } as any, { - webhookSecret: "test", - onBenefitCreated, - }); - expect(onBenefitCreated).toHaveBeenCalledWith({ - type: "benefit.created", - data: {}, - }); - }); - - it("should handle webhook payload with benefit updated", async () => { - const onBenefitUpdated = vi.fn(); - - await handleWebhookPayload({ type: "benefit.updated", data: {} } as any, { - webhookSecret: "test", - onBenefitUpdated, - }); - expect(onBenefitUpdated).toHaveBeenCalledWith({ - type: "benefit.updated", - data: {}, - }); - }); - - it("should handle webhook payload with benefit grant created", async () => { - const onBenefitGrantCreated = vi.fn(); - - await handleWebhookPayload( - { type: "benefit_grant.created", data: {} } as any, - { - webhookSecret: "test", - onBenefitGrantCreated, - }, - ); - expect(onBenefitGrantCreated).toHaveBeenCalledWith({ - type: "benefit_grant.created", - data: {}, - }); - }); - - it("should handle webhook payload with benefit grant updated", async () => { - const onBenefitGrantUpdated = vi.fn(); - - await handleWebhookPayload( - { type: "benefit_grant.updated", data: {} } as any, - { - webhookSecret: "test", - onBenefitGrantUpdated, - }, - ); - expect(onBenefitGrantUpdated).toHaveBeenCalledWith({ - type: "benefit_grant.updated", - data: {}, - }); - }); - - it("should handle webhook payload with benefit grant revoked", async () => { - const onBenefitGrantRevoked = vi.fn(); - - await handleWebhookPayload( - { type: "benefit_grant.revoked", data: {} } as any, - { - webhookSecret: "test", - onBenefitGrantRevoked, - }, - ); - expect(onBenefitGrantRevoked).toHaveBeenCalledWith({ - type: "benefit_grant.revoked", - data: {}, - }); - }); + it("should handle webhook payload", async () => { + const onPayload = vi.fn(); + + await handleWebhookPayload({ type: "checkout.created", data: {} } as any, { + webhookSecret: "test", + onPayload, + }); + + expect(onPayload).toHaveBeenCalledWith({ + type: "checkout.created", + data: {}, + }); + }); + + it("should handle webhook payload with checkout created", async () => { + const onCheckoutCreated = vi.fn(); + + await handleWebhookPayload({ type: "checkout.created", data: {} } as any, { + webhookSecret: "test", + onCheckoutCreated, + }); + expect(onCheckoutCreated).toHaveBeenCalledWith({ + type: "checkout.created", + data: {}, + }); + }); + + it("should handle webhook payload with checkout updated", async () => { + const onCheckoutUpdated = vi.fn(); + + await handleWebhookPayload({ type: "checkout.updated", data: {} } as any, { + webhookSecret: "test", + onCheckoutUpdated, + }); + expect(onCheckoutUpdated).toHaveBeenCalledWith({ + type: "checkout.updated", + data: {}, + }); + }); + + it("should handle webhook payload with order created", async () => { + const onOrderCreated = vi.fn(); + + await handleWebhookPayload({ type: "order.created", data: {} } as any, { + webhookSecret: "test", + onOrderCreated, + }); + expect(onOrderCreated).toHaveBeenCalledWith({ + type: "order.created", + data: {}, + }); + }); + + it("should handle webhook payload with subscription created", async () => { + const onSubscriptionCreated = vi.fn(); + + await handleWebhookPayload( + { type: "subscription.created", data: {} } as any, + { + webhookSecret: "test", + onSubscriptionCreated, + }, + ); + expect(onSubscriptionCreated).toHaveBeenCalledWith({ + type: "subscription.created", + data: {}, + }); + }); + + it("should handle webhook payload with subscription updated", async () => { + const onSubscriptionUpdated = vi.fn(); + + await handleWebhookPayload( + { type: "subscription.updated", data: {} } as any, + { + webhookSecret: "test", + onSubscriptionUpdated, + }, + ); + expect(onSubscriptionUpdated).toHaveBeenCalledWith({ + type: "subscription.updated", + data: {}, + }); + }); + + it("should handle webhook payload with subscription active", async () => { + const onSubscriptionActive = vi.fn(); + + await handleWebhookPayload( + { type: "subscription.active", data: {} } as any, + { + webhookSecret: "test", + onSubscriptionActive, + }, + ); + expect(onSubscriptionActive).toHaveBeenCalledWith({ + type: "subscription.active", + data: {}, + }); + }); + + it("should handle webhook payload with subscription canceled", async () => { + const onSubscriptionCanceled = vi.fn(); + + await handleWebhookPayload( + { type: "subscription.canceled", data: {} } as any, + { + webhookSecret: "test", + onSubscriptionCanceled, + }, + ); + expect(onSubscriptionCanceled).toHaveBeenCalledWith({ + type: "subscription.canceled", + data: {}, + }); + }); + + it("should handle webhook payload with subscription revoked", async () => { + const onSubscriptionRevoked = vi.fn(); + + await handleWebhookPayload( + { type: "subscription.revoked", data: {} } as any, + { + webhookSecret: "test", + onSubscriptionRevoked, + }, + ); + expect(onSubscriptionRevoked).toHaveBeenCalledWith({ + type: "subscription.revoked", + data: {}, + }); + }); + + it("should handle webhook payload with product created", async () => { + const onProductCreated = vi.fn(); + + await handleWebhookPayload({ type: "product.created", data: {} } as any, { + webhookSecret: "test", + onProductCreated, + }); + expect(onProductCreated).toHaveBeenCalledWith({ + type: "product.created", + data: {}, + }); + }); + + it("should handle webhook payload with product updated", async () => { + const onProductUpdated = vi.fn(); + + await handleWebhookPayload({ type: "product.updated", data: {} } as any, { + webhookSecret: "test", + onProductUpdated, + }); + expect(onProductUpdated).toHaveBeenCalledWith({ + type: "product.updated", + data: {}, + }); + }); + + it("should handle webhook payload with organization updated", async () => { + const onOrganizationUpdated = vi.fn(); + + await handleWebhookPayload( + { type: "organization.updated", data: {} } as any, + { + webhookSecret: "test", + onOrganizationUpdated, + }, + ); + expect(onOrganizationUpdated).toHaveBeenCalledWith({ + type: "organization.updated", + data: {}, + }); + }); + + it("should handle webhook payload with benefit created", async () => { + const onBenefitCreated = vi.fn(); + + await handleWebhookPayload({ type: "benefit.created", data: {} } as any, { + webhookSecret: "test", + onBenefitCreated, + }); + expect(onBenefitCreated).toHaveBeenCalledWith({ + type: "benefit.created", + data: {}, + }); + }); + + it("should handle webhook payload with benefit updated", async () => { + const onBenefitUpdated = vi.fn(); + + await handleWebhookPayload({ type: "benefit.updated", data: {} } as any, { + webhookSecret: "test", + onBenefitUpdated, + }); + expect(onBenefitUpdated).toHaveBeenCalledWith({ + type: "benefit.updated", + data: {}, + }); + }); + + it("should handle webhook payload with benefit grant created", async () => { + const onBenefitGrantCreated = vi.fn(); + + await handleWebhookPayload( + { type: "benefit_grant.created", data: {} } as any, + { + webhookSecret: "test", + onBenefitGrantCreated, + }, + ); + expect(onBenefitGrantCreated).toHaveBeenCalledWith({ + type: "benefit_grant.created", + data: {}, + }); + }); + + it("should handle webhook payload with benefit grant updated", async () => { + const onBenefitGrantUpdated = vi.fn(); + + await handleWebhookPayload( + { type: "benefit_grant.updated", data: {} } as any, + { + webhookSecret: "test", + onBenefitGrantUpdated, + }, + ); + expect(onBenefitGrantUpdated).toHaveBeenCalledWith({ + type: "benefit_grant.updated", + data: {}, + }); + }); + + it("should handle webhook payload with benefit grant revoked", async () => { + const onBenefitGrantRevoked = vi.fn(); + + await handleWebhookPayload( + { type: "benefit_grant.revoked", data: {} } as any, + { + webhookSecret: "test", + onBenefitGrantRevoked, + }, + ); + expect(onBenefitGrantRevoked).toHaveBeenCalledWith({ + type: "benefit_grant.revoked", + data: {}, + }); + }); + + it("should run entitlement grant & revoke methods when applicable", async () => { + const onGrant = vi.fn(); + const onRevoke = vi.fn(); + + const entitlementStrategy = new EntitlementStrategy() + .grant(onGrant) + .revoke(onRevoke); + + await handleWebhookPayload( + { + type: "benefit_grant.created", + data: { + customer: {}, + benefit: { description: "benefit-1", properties: {} }, + properties: {}, + }, + } as any, + { + webhookSecret: "test", + entitlements: Entitlements.use("benefit-1", entitlementStrategy), + }, + ); + + expect(onGrant).toHaveBeenCalledWith({ + customer: {}, + properties: {}, + payload: { + type: "benefit_grant.created", + data: { + customer: {}, + benefit: { description: "benefit-1", properties: {} }, + properties: {}, + }, + }, + }); + + expect(onRevoke).not.toHaveBeenCalled(); + }); }); diff --git a/packages/adapter-utils/src/webhooks/webhooks.ts b/packages/adapter-utils/src/webhooks/webhooks.ts index 4d5432c..9d9fb05 100644 --- a/packages/adapter-utils/src/webhooks/webhooks.ts +++ b/packages/adapter-utils/src/webhooks/webhooks.ts @@ -17,9 +17,11 @@ import type { WebhookBenefitGrantUpdatedPayload, WebhookBenefitGrantRevokedPayload, } from "@polar-sh/sdk/models/components"; +import { Entitlements } from "../entitlement/entitlement"; export interface WebhooksConfig { webhookSecret: string; + entitlements?: typeof Entitlements; onPayload?: (payload: ReturnType) => Promise; onCheckoutCreated?: (payload: WebhookCheckoutCreatedPayload) => Promise; onCheckoutUpdated?: (payload: WebhookCheckoutUpdatedPayload) => Promise; @@ -59,7 +61,7 @@ export interface WebhooksConfig { export const handleWebhookPayload = async ( payload: ReturnType, - { webhookSecret, onPayload, ...eventHandlers }: WebhooksConfig, + { webhookSecret, entitlements, onPayload, ...eventHandlers }: WebhooksConfig, ) => { const promises: Promise[] = []; @@ -149,5 +151,15 @@ export const handleWebhookPayload = async ( } } + switch (payload.type) { + case "benefit_grant.created": + case "benefit_grant.revoked": + if (entitlements) { + for (const handler of entitlements.handlers) { + promises.push(handler(payload)); + } + } + } + return Promise.all(promises); };