-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #548 from Pinelab-studio/feat/checkout-started
Klaviyo: included checkout started mutation and emitting event to Klaviyo
- Loading branch information
Showing
12 changed files
with
181 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "@pinelab/vendure-plugin-klaviyo", | ||
"version": "1.3.2", | ||
"version": "1.4.0", | ||
"description": "An extensible plugin for sending placed orders to the Klaviyo marketing platform.", | ||
"author": "Martijn van de Brug <[email protected]>", | ||
"homepage": "https://pinelab-plugins.com/", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { gql } from 'graphql-tag'; | ||
|
||
export const shopApiExtensions = gql` | ||
extend type Mutation { | ||
""" | ||
This mutation indicates that a customer has started the checkout process. | ||
The frontend should call this mutation. It will make the Klaviyo plugin emit a CheckoutStartedEvent. | ||
""" | ||
klaviyoCheckoutStarted: Boolean! | ||
} | ||
`; |
29 changes: 29 additions & 0 deletions
29
packages/vendure-plugin-klaviyo/src/api/klaviyo-shop-resolver.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Mutation, Resolver } from '@nestjs/graphql'; | ||
import { | ||
ActiveOrderService, | ||
Ctx, | ||
EventBus, | ||
RequestContext, | ||
} from '@vendure/core'; | ||
import { CheckoutStartedEvent } from '../service/checkout-started-event'; | ||
|
||
@Resolver() | ||
export class KlaviyoShopResolver { | ||
constructor( | ||
private readonly activeOrderService: ActiveOrderService, | ||
private readonly eventBus: EventBus | ||
) {} | ||
|
||
@Mutation() | ||
async klaviyoCheckoutStarted(@Ctx() ctx: RequestContext): Promise<boolean> { | ||
const activeOrder = await this.activeOrderService.getActiveOrder( | ||
ctx, | ||
undefined | ||
); | ||
if (activeOrder) { | ||
await this.eventBus.publish(new CheckoutStartedEvent(ctx, activeOrder)); | ||
return true; | ||
} | ||
return false; | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
packages/vendure-plugin-klaviyo/src/event-handler/checkout-started-event-handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { | ||
KlaviyoEventHandler, | ||
KlaviyoGenericEvent, | ||
} from '../event-handler/klaviyo-event-handler'; | ||
import { EntityHydrator, Logger } from '@vendure/core'; | ||
import { CheckoutStartedEvent } from '../service/checkout-started-event'; | ||
import { loggerCtx } from '../constants'; | ||
|
||
/** | ||
* Sends an event to Klavyio when a checkout has started and the order has a customer email address. | ||
*/ | ||
export const startedCheckoutHandler: KlaviyoEventHandler<CheckoutStartedEvent> = | ||
{ | ||
vendureEvent: CheckoutStartedEvent, | ||
mapToKlaviyoEvent: async ({ ctx, order }, injector) => { | ||
await injector.get(EntityHydrator).hydrate(ctx, order, { | ||
relations: ['customer', 'lines.productVariant'], | ||
}); | ||
if (!order.customer?.emailAddress) { | ||
return false; | ||
} | ||
const address = order.billingAddress?.streetLine1 | ||
? order.billingAddress | ||
: order.shippingAddress; | ||
const event: KlaviyoGenericEvent = { | ||
eventName: 'Checkout Started', | ||
uniqueId: order.code, | ||
customProperties: { | ||
orderCode: order.code, | ||
orderItems: order.lines.map((line) => ({ | ||
productName: line.productVariant.name, | ||
quantity: line.quantity, | ||
})), | ||
}, | ||
profile: { | ||
emailAddress: order.customer.emailAddress, | ||
externalId: order.customer.id.toString(), | ||
firstName: order.customer.firstName, | ||
lastName: order.customer.lastName, | ||
phoneNumber: order.customer.phoneNumber, | ||
address: { | ||
address1: address?.streetLine1, | ||
address2: address?.streetLine2, | ||
city: address?.city, | ||
postalCode: address?.postalCode, | ||
countryCode: address.countryCode, | ||
}, | ||
}, | ||
}; | ||
Logger.info( | ||
`Sent '${event.eventName}' to Klaviyo for order ${order.code}`, | ||
loggerCtx | ||
); | ||
return event; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
export * from './klaviyo.plugin'; | ||
export * from './event-handler/klaviyo-event-handler'; | ||
export * from './klaviyo.service'; | ||
export * from './service/klaviyo.service'; | ||
export * from './event-handler/default-order-placed-event-handler'; | ||
export * from './event-handler/checkout-started-event-handler'; | ||
export * from './util/to-klaviyo-money'; | ||
export * from './service/checkout-started-event'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
packages/vendure-plugin-klaviyo/src/service/checkout-started-event.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Order, RequestContext, VendureEvent } from '@vendure/core'; | ||
|
||
/** | ||
* Event indicating that a checkout has started for this order | ||
*/ | ||
export class CheckoutStartedEvent extends VendureEvent { | ||
constructor(public ctx: RequestContext, public order: Order) { | ||
super(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { DefaultLogger, LogLevel, mergeConfig } from '@vendure/core'; | ||
import { DefaultLogger, EventBus, LogLevel, mergeConfig } from '@vendure/core'; | ||
import { | ||
createTestEnvironment, | ||
registerInitializer, | ||
|
@@ -11,11 +11,13 @@ import { EventCreateQueryV2 } from 'klaviyo-api'; | |
import nock from 'nock'; | ||
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; | ||
import { initialData } from '../../test/src/initial-data'; | ||
import { createSettledOrder } from '../../test/src/shop-utils'; | ||
import { addItem, createSettledOrder } from '../../test/src/shop-utils'; | ||
import { testPaymentMethod } from '../../test/src/test-payment-method'; | ||
import { defaultOrderPlacedEventHandler, KlaviyoPlugin } from '../src'; | ||
import { mockOrderPlacedHandler } from './mock-order-placed-handler'; | ||
import { mockCustomEventHandler } from './mock-custom-event-handler'; | ||
import { CheckoutStartedEvent, startedCheckoutHandler } from '../src/'; | ||
import gql from 'graphql-tag'; | ||
|
||
let server: TestServer; | ||
let adminClient: SimpleGraphQLClient; | ||
|
@@ -30,6 +32,7 @@ beforeAll(async () => { | |
apiKey: 'some_private_api_key', | ||
eventHandlers: [ | ||
defaultOrderPlacedEventHandler, | ||
startedCheckoutHandler, | ||
mockOrderPlacedHandler, | ||
mockCustomEventHandler, | ||
], | ||
|
@@ -184,4 +187,45 @@ describe('Klaviyo', () => { | |
(customEvent?.data.attributes.properties as any).customTestEventProp | ||
).toEqual('some information'); | ||
}); | ||
|
||
it('Emits CheckoutStartedEvent on calling checkoutStarted() mutation', async () => { | ||
// Create active order | ||
await shopClient.asUserWithCredentials( | ||
'[email protected]', | ||
'test' | ||
); | ||
await addItem(shopClient, 'T_1', 1); | ||
// Mock API response | ||
nock('https://a.klaviyo.com/api/') | ||
.post('/events/', (reqBody) => { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
klaviyoRequests.push(reqBody); | ||
return true; | ||
}) | ||
.reply(200, {}) | ||
.persist(); | ||
const events: CheckoutStartedEvent[] = []; | ||
server.app | ||
.get(EventBus) | ||
.ofType(CheckoutStartedEvent) | ||
.subscribe((e) => events.push(e)); | ||
await shopClient.query( | ||
gql` | ||
mutation { | ||
klaviyoCheckoutStarted | ||
} | ||
` | ||
); | ||
// Give worker some time to send event to klaviyo | ||
await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
const checkoutStartedEvent = klaviyoRequests.find( | ||
(r) => | ||
r.data.attributes.metric.data.attributes.name === 'Checkout Started' | ||
); | ||
expect(events[0].order.id).toBeDefined(); | ||
const profile = checkoutStartedEvent?.data.attributes.profile.data | ||
.attributes as any; | ||
expect(profile.email).toBe('[email protected]'); | ||
expect(checkoutStartedEvent).toBeDefined(); | ||
}); | ||
}); |