Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added drop in pass trigger #210

Merged
merged 6 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ Minor changes to associated `membership` to the invoice on trigger/created_invoi
## 2.4.2

- Added membership `phone`, `address`, and `plan` fields

## 2.5.0

- Added trigger/drop_in_pass_purchased
- This trigger fires when a visitor purchases a drop in pass
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cobot-zapier",
"version": "2.4.2",
"version": "2.5.0",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion src/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const AUTHORIZE_URL = `${BASE_URL}/oauth/authorize`;
const ACCESS_TOKEN_URL = `${BASE_URL}/oauth/access_token`;
const TEST_AUTH_URL = `${BASE_URL}/api/user`;
const scopes =
"read read_admins read_bookings read_events read_external_bookings read_invoices read_contacts read_memberships read_resources read_spaces read_user write_activities write_subscriptions";
"read read_admins read_bookings read_drop_in_passes read_events read_external_bookings read_invoices read_contacts read_memberships read_resources read_spaces read_user write_activities write_subscriptions";

const getAccessToken = async (
z: ZObject,
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import triggerEventPublished from "./triggers/triggerEventPublished";
import triggerInvoiceCreated from "./triggers/triggerInvoiceCreated";
import triggerMembershipCancelled from "./triggers/triggerMembershipCancelled";
import triggerMembershipCancellationDateReached from "./triggers/triggerMembershipCancellationDateReached";
import triggerDropInPassPurchased from "./triggers/triggerDropInPassPurchased";

const { version } = require("../package.json");

Expand All @@ -38,6 +39,7 @@ export default {
[triggerEventPublished.key]: triggerEventPublished,
[triggerInvoiceCreated.key]: triggerInvoiceCreated,
[triggerExternalBooking.key]: triggerExternalBooking,
[triggerDropInPassPurchased.key]: triggerDropInPassPurchased,
[triggerMembershipCancellationDateReached.key]:
triggerMembershipCancellationDateReached,
// Lists for dropdowns
Expand Down
110 changes: 110 additions & 0 deletions src/test/triggers/triggerDropInPassPurchased.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { createAppTester } from "zapier-platform-core";
import * as nock from "nock";
import App from "../../index";
import {
prepareBundle,
prepareMocksForWebhookSubscribeTest,
} from "../utils/prepareMocksForWebhookSubscribeTest";
import triggerDropInPassPurchased from "../../triggers/triggerDropInPassPurchased";
import { HookTrigger } from "../../types/trigger";
import {
UserApiResponse,
DropInPassApiResponse,
} from "../../types/api-responses";

const appTester = createAppTester(App);
nock.disableNetConnect();
const trigger = App.triggers[triggerDropInPassPurchased.key] as HookTrigger;

afterEach(() => nock.cleanAll());

describe("triggerDropInPassPurchased", () => {
it("creates new webhook through CM API upon subscribe", async () => {
const bundle = prepareMocksForWebhookSubscribeTest(
triggerDropInPassPurchased.key,
);
const subscribe = trigger.operation.performSubscribe;

const result = await appTester(subscribe as any, bundle as any);

expect(result).toMatchInlineSnapshot(`
{
"url": "https://trial.cobot.me/api/event/callback",
}
`);
});

it("lists recent purchased drop in passes", async () => {
langalex marked this conversation as resolved.
Show resolved Hide resolved
const bundle = prepareBundle();
const userResponse: UserApiResponse = {
included: [{ id: "space-1", attributes: { subdomain: "trial" } }],
};
const imageItem = {
url: "https://www.example.com/image.png",
width: 100,
height: 100,
};
const dropInPassResponse: DropInPassApiResponse = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming. this is not a (full) response but data for one pass.

id: "1",
attributes: {
name: "Day Pass",
validOn: "2012-04-12",
email: "[email protected]",
phone: "+1 555 683 4463",
taxId: "DE12345",
onboardingInstructions: "coffee please",
price: {
gross: "10.0",
currency: "EUR",
net: "8.4",
taxes: [],
},
comments: "coffee please",
billingAddress: {
name: "Joe Doe",
company: "Acme Inc.",
fullAddress: "2 Coworking Road",
},
timeAvailability: [
{
from: "01:00",
to: "05:00",
weekdays: [1, 2, 5],
},
],
},
};

const scope = nock("https://api.cobot.me");
scope.get("/user?include=adminOf").reply(200, userResponse);
scope
.get(/\/spaces\/space-1\/drop_in_passes/)
.reply(200, { data: [dropInPassResponse] });

const listRecentDropInPasses = trigger.operation.performList;

const results = await appTester(
listRecentDropInPasses as any,
bundle as any,
);

expect(results).toStrictEqual([
{
id: "1",
dropInPassName: "Day Pass",
validOn: "2012-04-12",
email: "[email protected]",
phone: "+1 555 683 4463",
taxId: "DE12345",
grossPrice: "10.0 EUR",
netPrice: "8.4 EUR",
comments: "coffee please",
billingAddress: {
name: "Joe Doe",
company: "Acme Inc.",
fullAddress: "2 Coworking Road",
},
},
]);
});
});
80 changes: 80 additions & 0 deletions src/triggers/triggerDropInPassPurchased.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { ZObject } from "zapier-platform-core";
import { KontentBundle } from "../types/kontentBundle";
import {
apiCallUrl,
subscribeHook,
unsubscribeHook,
listRecentDropInPasses,
} from "../utils/api";
import { SubscribeBundleInputType } from "../types/subscribeType";
import { getSubdomainField } from "../fields/getSudomainsField";
import { dropInPassSample } from "../utils/samples";
import { DropInPassOutput } from "../types/outputs";
import { DropInPassApiResponse } from "../types/api-responses";
import { apiResponseToDropInPassOutput } from "../utils/api-to-output";
import { HookTrigger } from "../types/trigger";

const hookLabel = "Drop in Pass Purchased";
const event = "created_drop_in_pass";

async function subscribeHookExecute(
z: ZObject,
bundle: KontentBundle<SubscribeBundleInputType>,
) {
return subscribeHook(z, bundle, {
event,
callback_url: bundle.targetUrl ?? "",
});
}

async function unsubscribeHookExecute(
z: ZObject,
bundle: KontentBundle<SubscribeBundleInputType>,
) {
const webhook = bundle.subscribeData;
return unsubscribeHook(z, bundle, webhook?.id ?? "");
}

async function parsePayload(
z: ZObject,
bundle: KontentBundle<{}>,
): Promise<DropInPassOutput[]> {
if (bundle.cleanedRequest) {
const dropInPass = (await apiCallUrl(
z,
bundle.cleanedRequest.url,
)) as DropInPassApiResponse;
return [apiResponseToDropInPassOutput(dropInPass)];
} else {
return [];
}
}

const trigger: HookTrigger = {
key: event,
noun: hookLabel,
display: {
label: hookLabel,
description: "Triggers when an user has bought a drop in pass",
},
operation: {
type: "hook",

inputFields: [getSubdomainField()],

performSubscribe: subscribeHookExecute,
performUnsubscribe: unsubscribeHookExecute,

perform: parsePayload,
performList: async (
z: ZObject,
bundle: KontentBundle<SubscribeBundleInputType>,
): Promise<DropInPassOutput[]> => {
const dropInPasses = await listRecentDropInPasses(z, bundle);
return dropInPasses.map(apiResponseToDropInPassOutput);
},

sample: dropInPassSample,
},
};
export default trigger;
20 changes: 20 additions & 0 deletions src/types/api-responses.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,23 @@ type ResourceApiResponse = {
type UserApiResponse = {
included: { id: string; attributes: { subdomain: string } }[];
};

export type DropInPassApiResponse = {
id: string;
attributes: {
name: string;
validOn: string;
email: string;
phone: string;
onboardingInstructions: string;
taxId: string;
price: Amount;
comments: string;
billingAddress: Address;
timeAvailability: {
from: string;
to: string;
weekdays: number[];
}[];
};
};
17 changes: 17 additions & 0 deletions src/types/outputs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,20 @@ export type InvoiceOutput = BaseInvoiceProperties & {
membership?: InvoiceMembershipOutput;
contact?: InvoiceContactOutput;
};

export type DropInPassOutput = {
id: string;
dropInPassName: string;
validOn: string;
email: string;
phone: string;
taxId: string;
grossPrice: string;
netPrice: string;
comments: string;
billingAddress: {
name: string | null;
company: string | null;
fullAddress: string | null;
};
};
22 changes: 22 additions & 0 deletions src/utils/api-to-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import {
MembershipApiResponse,
ContactApiResponse,
InvoiceApiResponse,
DropInPassApiResponse,
} from "../types/api-responses";
import {
BookingOutput,
EventOutput,
ExternalBookingOutput,
MembershipOutput,
InvoiceOutput,
DropInPassOutput,
} from "../types/outputs";
import { ZObject } from "zapier-platform-core";
import { get } from "lodash";
Expand Down Expand Up @@ -157,3 +159,23 @@ export function apiResponseToExternalBookingOutput(
const timeToIso8601 = (time: string): string => {
return new Date(time).toISOString();
};

export function apiResponseToDropInPassOutput(
dropInPass: DropInPassApiResponse,
): DropInPassOutput {
const attrs = dropInPass.attributes;
const grossPrice = `${attrs.price.gross} ${attrs.price.currency}`;
const netPrice = `${attrs.price.net} ${attrs.price.currency}`;
return {
id: dropInPass.id,
dropInPassName: attrs.name,
validOn: attrs.validOn,
email: attrs.email,
phone: attrs.phone,
taxId: attrs.taxId,
grossPrice,
netPrice,
comments: attrs.comments,
billingAddress: attrs.billingAddress,
};
}
25 changes: 25 additions & 0 deletions src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
UserApiResponse,
InvoiceApiResponse,
BookingApi2Response,
DropInPassApiResponse,
} from "../types/api-responses";
import { InvoiceMembershipOutput } from "../types/outputs";

Expand Down Expand Up @@ -344,3 +345,27 @@ export const getDateRange = (useISODate = false): [string, string] => {
}
return [lastMonth.toISODate(), nextMonth.toISODate()];
};

export const listRecentDropInPasses = async (
z: ZObject,
bundle: KontentBundle<SubscribeBundleInputType>,
): Promise<DropInPassApiResponse[]> => {
const subdomain = bundle.inputData.subdomain;
const space = await spaceForSubdomain(z, subdomain);
if (!space) return [];
const url = `https://api.cobot.me/spaces/${space.id}/drop_in_passes`;
const [from, to] = getDateRange();
const response = await z.request({
url,
method: "GET",
headers: {
Accept: "application/vnd.api+json",
},
params: {
"filter[valid_on_from]": from,
"filter[valid_on_to]": to,
"filter[sortOrder]": "desc",
},
});
return response.data.data as DropInPassApiResponse[];
};
18 changes: 18 additions & 0 deletions src/utils/samples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ExternalBookingOutput,
MembershipOutput,
InvoiceOutput,
DropInPassOutput,
} from "../types/outputs";

export const bookingSample: BookingOutput = {
Expand Down Expand Up @@ -164,3 +165,20 @@ export const invoiceSample: InvoiceOutput = {
],
},
};

export const dropInPassSample: DropInPassOutput = {
id: "14c12f62ac8df98d29de357180d673e1",
dropInPassName: "Day Pass",
validOn: "2012-04-12",
email: "[email protected]",
phone: "+1 555 683 4463",
taxId: "DE12345",
grossPrice: "10.0 EUR",
netPrice: "8.4 EUR",
comments: "coffee please",
billingAddress: {
name: "Joe Doe",
company: "Acme Inc.",
fullAddress: "2 Coworking Road",
},
};
Loading