@segment/analytics-browser-actions-friendbuy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +21 -0
- package/src/__tests__/index.test.ts +38 -0
- package/src/generated-types.ts +8 -0
- package/src/index.ts +91 -0
- package/src/trackCustomEvent/__tests__/index.test.ts +98 -0
- package/src/trackCustomEvent/generated-types.ts +30 -0
- package/src/trackCustomEvent/index.ts +49 -0
- package/src/trackCustomer/__tests__/index.test.ts +196 -0
- package/src/trackCustomer/generated-types.ts +74 -0
- package/src/trackCustomer/index.ts +51 -0
- package/src/trackPage/__tests__/index.test.ts +63 -0
- package/src/trackPage/generated-types.ts +16 -0
- package/src/trackPage/index.ts +58 -0
- package/src/trackPurchase/__tests__/index.test.ts +211 -0
- package/src/trackPurchase/generated-types.ts +91 -0
- package/src/trackPurchase/index.ts +85 -0
- package/src/trackSignUp/__tests__/index.test.ts +92 -0
- package/src/trackSignUp/generated-types.ts +62 -0
- package/src/trackSignUp/index.ts +52 -0
- package/src/types.ts +3 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Analytics, Context } from '@segment/analytics-next'
|
|
2
|
+
import friendbuyDestination from '../../index'
|
|
3
|
+
import trackPageObject, { trackPageDefaultSubscription, trackPageFields } from '../index'
|
|
4
|
+
|
|
5
|
+
import { loadScript } from '@segment/browser-destination-runtime/load-script'
|
|
6
|
+
jest.mock('@segment/browser-destination-runtime/load-script')
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
// Prevent friendbuy.js and campaigns.js from being loaded.
|
|
9
|
+
;(loadScript as jest.Mock).mockResolvedValue(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('Friendbuy.trackPage', () => {
|
|
13
|
+
const subscriptions = [
|
|
14
|
+
{
|
|
15
|
+
partnerAction: 'trackPage',
|
|
16
|
+
name: trackPageObject.title,
|
|
17
|
+
enabled: true,
|
|
18
|
+
subscribe: trackPageDefaultSubscription,
|
|
19
|
+
mapping: Object.fromEntries(Object.entries(trackPageFields).map(([name, value]) => [name, value.default]))
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
test('all fields', async () => {
|
|
24
|
+
const merchantId = '1993d0f1-8206-4336-8c88-64e170f2419e'
|
|
25
|
+
const name = 'Page Name'
|
|
26
|
+
const category = 'Page Category'
|
|
27
|
+
const title = 'Page Title'
|
|
28
|
+
|
|
29
|
+
const [trackPage] = await friendbuyDestination({
|
|
30
|
+
merchantId,
|
|
31
|
+
subscriptions
|
|
32
|
+
})
|
|
33
|
+
expect(trackPage).toBeDefined()
|
|
34
|
+
|
|
35
|
+
await trackPage.load(Context.system(), {} as Analytics)
|
|
36
|
+
|
|
37
|
+
jest.spyOn(window.friendbuyAPI as any, 'push')
|
|
38
|
+
|
|
39
|
+
const context = new Context({
|
|
40
|
+
type: 'page',
|
|
41
|
+
name,
|
|
42
|
+
category,
|
|
43
|
+
properties: {
|
|
44
|
+
title
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
// console.log('context', JSON.stringify(context, null, 2))
|
|
48
|
+
|
|
49
|
+
trackPage.page?.(context)
|
|
50
|
+
|
|
51
|
+
// console.log('trackSignUp request', JSON.stringify(window.friendbuyAPI.push.mock.calls[0], null, 2))
|
|
52
|
+
expect(window.friendbuyAPI?.push).toHaveBeenCalledWith([
|
|
53
|
+
'track',
|
|
54
|
+
'page',
|
|
55
|
+
{
|
|
56
|
+
name,
|
|
57
|
+
category,
|
|
58
|
+
title
|
|
59
|
+
},
|
|
60
|
+
true
|
|
61
|
+
])
|
|
62
|
+
})
|
|
63
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { InputField } from '@segment/actions-core'
|
|
2
|
+
import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
|
|
3
|
+
|
|
4
|
+
import type { FriendbuyAPI } from '../types'
|
|
5
|
+
import type { Settings } from '../generated-types'
|
|
6
|
+
import type { Payload } from './generated-types'
|
|
7
|
+
import { createFriendbuyPayload } from '@segment/actions-shared'
|
|
8
|
+
|
|
9
|
+
export const trackPageDefaultSubscription = 'type = "page"'
|
|
10
|
+
|
|
11
|
+
// https://segment.com/docs/connections/spec/page/
|
|
12
|
+
export const trackPageFields: Record<string, InputField> = {
|
|
13
|
+
name: {
|
|
14
|
+
label: 'Page Name',
|
|
15
|
+
description: 'The page name.',
|
|
16
|
+
type: 'string',
|
|
17
|
+
required: false,
|
|
18
|
+
default: { '@path': '$.name' }
|
|
19
|
+
},
|
|
20
|
+
category: {
|
|
21
|
+
label: 'Page Category',
|
|
22
|
+
description: 'The page category.',
|
|
23
|
+
type: 'string',
|
|
24
|
+
required: false,
|
|
25
|
+
default: { '@path': '$.category' }
|
|
26
|
+
},
|
|
27
|
+
title: {
|
|
28
|
+
label: 'Page Title',
|
|
29
|
+
description: 'The page title.',
|
|
30
|
+
type: 'string',
|
|
31
|
+
required: false,
|
|
32
|
+
default: { '@path': '$.properties.title' }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const action: BrowserActionDefinition<Settings, FriendbuyAPI, Payload> = {
|
|
37
|
+
title: 'Track Page',
|
|
38
|
+
description:
|
|
39
|
+
'Record when a customer visits a new page. Allow Friendbuy widget targeting by Page Name instead of URL.',
|
|
40
|
+
defaultSubscription: trackPageDefaultSubscription,
|
|
41
|
+
platform: 'web',
|
|
42
|
+
fields: trackPageFields,
|
|
43
|
+
|
|
44
|
+
perform: (friendbuyAPI, data) => {
|
|
45
|
+
// If the page name is not defined then track the page with the name
|
|
46
|
+
// "undefined". This is intended to allow merchants to target their widgets
|
|
47
|
+
// using page names when not all of their `analytics.page` calls include the
|
|
48
|
+
// page name.
|
|
49
|
+
const friendbuyPayload = createFriendbuyPayload([
|
|
50
|
+
['name', data.payload.name || 'undefined'],
|
|
51
|
+
['category', data.payload.category],
|
|
52
|
+
['title', data.payload.title]
|
|
53
|
+
])
|
|
54
|
+
friendbuyAPI.push(['track', 'page', friendbuyPayload, true])
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default action
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Analytics, Context, JSONValue } from '@segment/analytics-next'
|
|
2
|
+
import friendbuyDestination from '../../index'
|
|
3
|
+
import trackPurchaseObject, { browserTrackPurchaseFields, trackPurchaseDefaultSubscription } from '../index'
|
|
4
|
+
|
|
5
|
+
import { loadScript } from '@segment/browser-destination-runtime/load-script'
|
|
6
|
+
jest.mock('@segment/browser-destination-runtime/load-script')
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
// Prevent friendbuy.js and campaigns.js from being loaded.
|
|
9
|
+
;(loadScript as jest.Mock).mockResolvedValue(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('Friendbuy.trackPurchase', () => {
|
|
13
|
+
const subscriptions = [
|
|
14
|
+
{
|
|
15
|
+
partnerAction: 'trackPurchase',
|
|
16
|
+
name: trackPurchaseObject.title,
|
|
17
|
+
enabled: true,
|
|
18
|
+
subscribe: trackPurchaseDefaultSubscription,
|
|
19
|
+
mapping: Object.fromEntries(
|
|
20
|
+
Object.entries(browserTrackPurchaseFields).map(([name, value]) => [name, value.default])
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
test('all fields', async () => {
|
|
26
|
+
const orderId = 'my order'
|
|
27
|
+
const products = [
|
|
28
|
+
{ sku: 'sku1', name: 'shorts', price: 19.99, quantity: 2 },
|
|
29
|
+
{ price: 5.99 },
|
|
30
|
+
{
|
|
31
|
+
sku: 'sku3',
|
|
32
|
+
name: 'tshirt',
|
|
33
|
+
price: 24.99,
|
|
34
|
+
description: 'Black T-Shirt',
|
|
35
|
+
category: 'shirts',
|
|
36
|
+
url: 'https://example.com/sku3',
|
|
37
|
+
image_url: 'https://example.com/sku3/image.jpg'
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
const merchantId = '1993d0f1-8206-4336-8c88-64e170f2419e'
|
|
42
|
+
const userId = 'john-doe-12345'
|
|
43
|
+
const anonymousId = 'cbce64f6-a45a-4d9c-a63d-4c7b42773276'
|
|
44
|
+
const currency = 'USD'
|
|
45
|
+
const coupon = 'coupon-xyzzy'
|
|
46
|
+
const attributionId = '878123ed-0439-4a73-b2fe-72c4f09ec64f'
|
|
47
|
+
const referralCode = 'ref-plugh'
|
|
48
|
+
const giftCardCodes = ['card-a', 'card-b']
|
|
49
|
+
const friendbuyAttributes = { promotion: 'fall 2021' }
|
|
50
|
+
const email = 'john.doe@example.com'
|
|
51
|
+
const name = 'John Doe'
|
|
52
|
+
|
|
53
|
+
const [trackPurchase] = await friendbuyDestination({
|
|
54
|
+
merchantId,
|
|
55
|
+
subscriptions
|
|
56
|
+
})
|
|
57
|
+
// console.log('trackPurchase', JSON.stringify(trackPurchase, null, 2), trackPurchase)
|
|
58
|
+
expect(trackPurchase).toBeDefined()
|
|
59
|
+
|
|
60
|
+
await trackPurchase.load(Context.system(), {} as Analytics)
|
|
61
|
+
|
|
62
|
+
// console.log(window.friendbuyAPI)
|
|
63
|
+
jest.spyOn(window.friendbuyAPI as any, 'push')
|
|
64
|
+
|
|
65
|
+
const expectedProducts = products.map((p) => {
|
|
66
|
+
p = { sku: 'unknown', name: 'unknown', quantity: 1, ...p }
|
|
67
|
+
if (p.image_url) {
|
|
68
|
+
p.imageUrl = p.image_url
|
|
69
|
+
delete p.image_url
|
|
70
|
+
}
|
|
71
|
+
return p
|
|
72
|
+
})
|
|
73
|
+
const amount = expectedProducts.reduce((acc, p) => acc + p.price * p.quantity, 0)
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
// all fields
|
|
77
|
+
const context1 = new Context({
|
|
78
|
+
type: 'track',
|
|
79
|
+
event: 'Order Completed',
|
|
80
|
+
userId,
|
|
81
|
+
anonymousId,
|
|
82
|
+
properties: {
|
|
83
|
+
order_id: orderId,
|
|
84
|
+
revenue: amount,
|
|
85
|
+
subtotal: amount + 1,
|
|
86
|
+
total: amount + 2,
|
|
87
|
+
currency,
|
|
88
|
+
coupon,
|
|
89
|
+
attributionId,
|
|
90
|
+
referralCode,
|
|
91
|
+
giftCardCodes,
|
|
92
|
+
products: products as JSONValue,
|
|
93
|
+
email,
|
|
94
|
+
name,
|
|
95
|
+
friendbuyAttributes
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
// console.log('context1', JSON.stringify(context1, null, 2))
|
|
99
|
+
|
|
100
|
+
trackPurchase.track?.(context1)
|
|
101
|
+
|
|
102
|
+
// console.log('trackPurchase request', JSON.stringify(window.friendbuyAPI.push.mock.calls[0], null, 2))
|
|
103
|
+
expect(window.friendbuyAPI?.push).toHaveBeenNthCalledWith(1, [
|
|
104
|
+
'track',
|
|
105
|
+
'purchase',
|
|
106
|
+
{
|
|
107
|
+
id: orderId,
|
|
108
|
+
amount: amount + 2, // amount defaults to total
|
|
109
|
+
currency,
|
|
110
|
+
couponCode: coupon,
|
|
111
|
+
attributionId,
|
|
112
|
+
referralCode,
|
|
113
|
+
giftCardCodes,
|
|
114
|
+
customer: { id: userId, anonymousId, email, name },
|
|
115
|
+
products: expectedProducts,
|
|
116
|
+
...friendbuyAttributes
|
|
117
|
+
},
|
|
118
|
+
true
|
|
119
|
+
])
|
|
120
|
+
expect(window.friendbuyAPI.push.mock.calls[0][0][2].products[1].quantity).toBe(1)
|
|
121
|
+
expect(window.friendbuyAPI.push.mock.calls[0][0][2].products[2].imageUrl).toBe(products[2].image_url)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
// minimal event
|
|
126
|
+
const context2 = new Context({
|
|
127
|
+
type: 'track',
|
|
128
|
+
event: 'Order Completed',
|
|
129
|
+
properties: {
|
|
130
|
+
order_id: orderId,
|
|
131
|
+
total: amount,
|
|
132
|
+
currency
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
trackPurchase.track?.(context2)
|
|
137
|
+
|
|
138
|
+
expect(window.friendbuyAPI?.push).toHaveBeenNthCalledWith(2, [
|
|
139
|
+
'track',
|
|
140
|
+
'purchase',
|
|
141
|
+
{
|
|
142
|
+
id: orderId,
|
|
143
|
+
amount,
|
|
144
|
+
currency
|
|
145
|
+
},
|
|
146
|
+
true
|
|
147
|
+
])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
// customer is dropped if no userId/customerId
|
|
152
|
+
// giftCardCodes is dropped if list is empty
|
|
153
|
+
const context3 = new Context({
|
|
154
|
+
type: 'track',
|
|
155
|
+
event: 'Order Completed',
|
|
156
|
+
properties: {
|
|
157
|
+
order_id: orderId,
|
|
158
|
+
total: amount,
|
|
159
|
+
currency,
|
|
160
|
+
giftCardCodes: [],
|
|
161
|
+
email,
|
|
162
|
+
name
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
trackPurchase.track?.(context3)
|
|
167
|
+
|
|
168
|
+
expect(window.friendbuyAPI?.push).toHaveBeenNthCalledWith(3, [
|
|
169
|
+
'track',
|
|
170
|
+
'purchase',
|
|
171
|
+
{
|
|
172
|
+
id: orderId,
|
|
173
|
+
amount,
|
|
174
|
+
currency
|
|
175
|
+
},
|
|
176
|
+
true
|
|
177
|
+
])
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
{
|
|
181
|
+
// enjoined fields are converted
|
|
182
|
+
const context4 = new Context({
|
|
183
|
+
type: 'track',
|
|
184
|
+
event: 'Order Completed',
|
|
185
|
+
properties: {
|
|
186
|
+
order_id: 12345,
|
|
187
|
+
total: '129.50',
|
|
188
|
+
currency,
|
|
189
|
+
products: [{ sku: 99999, quantity: '2', price: '64.75' }],
|
|
190
|
+
customerId: 1138,
|
|
191
|
+
age: '30'
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
trackPurchase.track?.(context4)
|
|
196
|
+
|
|
197
|
+
expect(window.friendbuyAPI?.push).toHaveBeenNthCalledWith(4, [
|
|
198
|
+
'track',
|
|
199
|
+
'purchase',
|
|
200
|
+
{
|
|
201
|
+
id: '12345',
|
|
202
|
+
amount: 129.5,
|
|
203
|
+
currency,
|
|
204
|
+
products: [{ name: 'unknown', sku: '99999', quantity: 2, price: 64.75 }],
|
|
205
|
+
customer: { id: '1138', age: 30 }
|
|
206
|
+
},
|
|
207
|
+
true
|
|
208
|
+
])
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Generated file. DO NOT MODIFY IT BY HAND.
|
|
2
|
+
|
|
3
|
+
export interface Payload {
|
|
4
|
+
/**
|
|
5
|
+
* The order ID.
|
|
6
|
+
*/
|
|
7
|
+
orderId: string
|
|
8
|
+
/**
|
|
9
|
+
* Purchase amount to be considered when evaluating reward rules.
|
|
10
|
+
*/
|
|
11
|
+
amount: number
|
|
12
|
+
/**
|
|
13
|
+
* The currency of the purchase amount.
|
|
14
|
+
*/
|
|
15
|
+
currency: string
|
|
16
|
+
/**
|
|
17
|
+
* The coupon code of any coupon redeemed with the order.
|
|
18
|
+
*/
|
|
19
|
+
coupon?: string
|
|
20
|
+
/**
|
|
21
|
+
* Friendbuy attribution ID that associates the purchase with the advocate who referred the purchaser.
|
|
22
|
+
*/
|
|
23
|
+
attributionId?: string
|
|
24
|
+
/**
|
|
25
|
+
* Friendbuy referral code that associates the purchase with the advocate who referred the purchaser.
|
|
26
|
+
*/
|
|
27
|
+
referralCode?: string
|
|
28
|
+
/**
|
|
29
|
+
* An array of gift card codes applied to the order.
|
|
30
|
+
*/
|
|
31
|
+
giftCardCodes?: string[]
|
|
32
|
+
/**
|
|
33
|
+
* Products purchased.
|
|
34
|
+
*/
|
|
35
|
+
products?: {
|
|
36
|
+
sku?: string
|
|
37
|
+
name?: string
|
|
38
|
+
quantity?: number
|
|
39
|
+
price: number
|
|
40
|
+
description?: string
|
|
41
|
+
category?: string
|
|
42
|
+
url?: string
|
|
43
|
+
image_url?: string
|
|
44
|
+
}[]
|
|
45
|
+
/**
|
|
46
|
+
* The user's customer ID.
|
|
47
|
+
*/
|
|
48
|
+
customerId?: string
|
|
49
|
+
/**
|
|
50
|
+
* The user's anonymous ID.
|
|
51
|
+
*/
|
|
52
|
+
anonymousId?: string
|
|
53
|
+
/**
|
|
54
|
+
* The user's email address.
|
|
55
|
+
*/
|
|
56
|
+
email?: string
|
|
57
|
+
/**
|
|
58
|
+
* Flag to indicate whether the user is a new customer.
|
|
59
|
+
*/
|
|
60
|
+
isNewCustomer?: boolean
|
|
61
|
+
/**
|
|
62
|
+
* The status of the user in your loyalty program. Valid values are "in", "out", or "blocked".
|
|
63
|
+
*/
|
|
64
|
+
loyaltyStatus?: string
|
|
65
|
+
/**
|
|
66
|
+
* The user's given name.
|
|
67
|
+
*/
|
|
68
|
+
firstName?: string
|
|
69
|
+
/**
|
|
70
|
+
* The user's surname.
|
|
71
|
+
*/
|
|
72
|
+
lastName?: string
|
|
73
|
+
/**
|
|
74
|
+
* The user's full name.
|
|
75
|
+
*/
|
|
76
|
+
name?: string
|
|
77
|
+
/**
|
|
78
|
+
* The user's age.
|
|
79
|
+
*/
|
|
80
|
+
age?: number
|
|
81
|
+
/**
|
|
82
|
+
* The user's birthday in the format "YYYY-MM-DD", or "0000-MM-DD" to omit the year.
|
|
83
|
+
*/
|
|
84
|
+
birthday?: string
|
|
85
|
+
/**
|
|
86
|
+
* Custom attributes to send to Friendbuy. You should pass an object whose keys are the names of the custom attributes and whose values are strings. Non-string-valued attributes will be dropped.
|
|
87
|
+
*/
|
|
88
|
+
friendbuyAttributes?: {
|
|
89
|
+
[k: string]: unknown
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { BrowserActionDefinition } from '@segment/browser-destination-runtime/types'
|
|
2
|
+
|
|
3
|
+
import type { FriendbuyAPI } from '../types'
|
|
4
|
+
import type { Settings } from '../generated-types'
|
|
5
|
+
import type { Payload } from './generated-types'
|
|
6
|
+
import type { AnalyticsPayload, ConvertFun, EventMap } from '@segment/actions-shared'
|
|
7
|
+
|
|
8
|
+
import { COPY, ROOT, mapEvent } from '@segment/actions-shared'
|
|
9
|
+
import { trackPurchaseFields } from '@segment/actions-shared'
|
|
10
|
+
import {
|
|
11
|
+
addName,
|
|
12
|
+
enjoinInteger,
|
|
13
|
+
enjoinNumber,
|
|
14
|
+
enjoinString,
|
|
15
|
+
parseDate,
|
|
16
|
+
removeCustomerIfNoId
|
|
17
|
+
} from '@segment/actions-shared'
|
|
18
|
+
|
|
19
|
+
export const browserTrackPurchaseFields = trackPurchaseFields({})
|
|
20
|
+
|
|
21
|
+
// see https://segment.com/docs/config-api/fql/
|
|
22
|
+
export const trackPurchaseDefaultSubscription = 'event = "Order Completed"'
|
|
23
|
+
|
|
24
|
+
const trackPurchasePub: EventMap = {
|
|
25
|
+
fields: {
|
|
26
|
+
orderId: { name: 'id', convert: enjoinString as ConvertFun },
|
|
27
|
+
amount: { convert: enjoinNumber as ConvertFun },
|
|
28
|
+
currency: COPY,
|
|
29
|
+
coupon: { name: 'couponCode' },
|
|
30
|
+
attributionId: COPY,
|
|
31
|
+
referralCode: COPY,
|
|
32
|
+
giftCardCodes: {
|
|
33
|
+
type: 'array'
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
products: {
|
|
37
|
+
type: 'array',
|
|
38
|
+
defaultObject: { sku: 'unknown', name: 'unknown', quantity: 1 },
|
|
39
|
+
fields: {
|
|
40
|
+
sku: { convert: enjoinString as ConvertFun },
|
|
41
|
+
name: COPY,
|
|
42
|
+
quantity: { convert: enjoinInteger as ConvertFun },
|
|
43
|
+
price: { convert: enjoinNumber as ConvertFun },
|
|
44
|
+
description: COPY,
|
|
45
|
+
category: COPY,
|
|
46
|
+
url: COPY,
|
|
47
|
+
image_url: { name: 'imageUrl' }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// CUSTOMER FIELDS
|
|
52
|
+
customerId: { name: ['customer', 'id'], convert: enjoinString as ConvertFun },
|
|
53
|
+
anonymousId: { name: ['customer', 'anonymousId'] },
|
|
54
|
+
email: { name: ['customer', 'email'] },
|
|
55
|
+
isNewCustomer: { name: ['customer', 'isNewCustomer'] },
|
|
56
|
+
loyaltyStatus: { name: ['customer', 'loyaltyStatus'] },
|
|
57
|
+
firstName: { name: ['customer', 'firstName'] },
|
|
58
|
+
lastName: { name: ['customer', 'lastName'] },
|
|
59
|
+
name: { name: ['customer', 'name'] },
|
|
60
|
+
age: { name: ['customer', 'age'], convert: enjoinInteger as ConvertFun },
|
|
61
|
+
// fbt-merchant-api complains about birthday being an object but passes it anyway.
|
|
62
|
+
birthday: { name: ['customer', 'birthday'], convert: parseDate as ConvertFun }
|
|
63
|
+
},
|
|
64
|
+
unmappedFieldObject: ROOT,
|
|
65
|
+
// Documentation of 2021-12-03 claims that both customer.id and
|
|
66
|
+
// customer.email are required, but experimentation shows only customer.id
|
|
67
|
+
// is required by pub trackPurchase.
|
|
68
|
+
finalize: removeCustomerIfNoId
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const action: BrowserActionDefinition<Settings, FriendbuyAPI, Payload> = {
|
|
72
|
+
title: 'Track Purchase',
|
|
73
|
+
description: 'Record when a customer makes a purchase.',
|
|
74
|
+
defaultSubscription: trackPurchaseDefaultSubscription,
|
|
75
|
+
platform: 'web',
|
|
76
|
+
fields: browserTrackPurchaseFields,
|
|
77
|
+
|
|
78
|
+
perform: (friendbuyAPI, { payload }) => {
|
|
79
|
+
addName(payload)
|
|
80
|
+
const friendbuyPayload = mapEvent(trackPurchasePub, payload as unknown as AnalyticsPayload)
|
|
81
|
+
friendbuyAPI.push(['track', 'purchase', friendbuyPayload, true])
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default action
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Analytics, Context } from '@segment/analytics-next'
|
|
2
|
+
import friendbuyDestination from '../../index'
|
|
3
|
+
import trackSignUpObject, { browserTrackSignUpFields, trackSignUpDefaultSubscription } from '../index'
|
|
4
|
+
|
|
5
|
+
import { loadScript } from '@segment/browser-destination-runtime/load-script'
|
|
6
|
+
jest.mock('@segment/browser-destination-runtime/load-script')
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
// Prevent friendbuy.js and campaigns.js from being loaded.
|
|
9
|
+
;(loadScript as jest.Mock).mockResolvedValue(true)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
describe('Friendbuy.trackSignUp', () => {
|
|
13
|
+
const subscriptions = [
|
|
14
|
+
{
|
|
15
|
+
partnerAction: 'trackSignUp',
|
|
16
|
+
name: trackSignUpObject.title,
|
|
17
|
+
enabled: true,
|
|
18
|
+
subscribe: trackSignUpDefaultSubscription,
|
|
19
|
+
mapping: Object.fromEntries(
|
|
20
|
+
Object.entries(browserTrackSignUpFields).map(([name, value]) => [name, value.default])
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
test('all fields', async () => {
|
|
26
|
+
const merchantId = '1993d0f1-8206-4336-8c88-64e170f2419e'
|
|
27
|
+
const userId = 'john-doe-12345'
|
|
28
|
+
const anonymousId = '6afc2ff2-cf54-414f-9a99-b3adb054ae31'
|
|
29
|
+
const email = 'john.doe@example.com'
|
|
30
|
+
const isNewCustomer = false
|
|
31
|
+
const loyaltyStatus = 'in'
|
|
32
|
+
const firstName = 'John'
|
|
33
|
+
const lastName = 'Doe'
|
|
34
|
+
const name = `${firstName} ${lastName}`
|
|
35
|
+
const age = 42
|
|
36
|
+
const birthday = '0000-12-31'
|
|
37
|
+
const friendbuyAttributes = { custom1: 'custom1', custom2: 'custom2' }
|
|
38
|
+
|
|
39
|
+
const [trackSignUp] = await friendbuyDestination({
|
|
40
|
+
merchantId,
|
|
41
|
+
subscriptions
|
|
42
|
+
})
|
|
43
|
+
// console.log('trackSignUp', JSON.stringify(trackSignUp, null, 2), trackSignUp)
|
|
44
|
+
expect(trackSignUp).toBeDefined()
|
|
45
|
+
|
|
46
|
+
await trackSignUp.load(Context.system(), {} as Analytics)
|
|
47
|
+
|
|
48
|
+
// console.log(window.friendbuyAPI)
|
|
49
|
+
jest.spyOn(window.friendbuyAPI as any, 'push')
|
|
50
|
+
|
|
51
|
+
const context = new Context({
|
|
52
|
+
type: 'track',
|
|
53
|
+
event: 'Signed Up',
|
|
54
|
+
userId,
|
|
55
|
+
anonymousId,
|
|
56
|
+
properties: {
|
|
57
|
+
email,
|
|
58
|
+
isNewCustomer,
|
|
59
|
+
loyaltyStatus,
|
|
60
|
+
firstName,
|
|
61
|
+
lastName,
|
|
62
|
+
name,
|
|
63
|
+
age,
|
|
64
|
+
birthday,
|
|
65
|
+
friendbuyAttributes
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
// console.log('context', JSON.stringify(context, null, 2))
|
|
69
|
+
|
|
70
|
+
trackSignUp.track?.(context)
|
|
71
|
+
|
|
72
|
+
// console.log('trackSignUp request', JSON.stringify(window.friendbuyAPI.push.mock.calls[0], null, 2))
|
|
73
|
+
expect(window.friendbuyAPI?.push).toHaveBeenCalledWith([
|
|
74
|
+
'track',
|
|
75
|
+
'sign_up',
|
|
76
|
+
{
|
|
77
|
+
id: userId,
|
|
78
|
+
email,
|
|
79
|
+
isNewCustomer,
|
|
80
|
+
loyaltyStatus,
|
|
81
|
+
firstName,
|
|
82
|
+
lastName,
|
|
83
|
+
name,
|
|
84
|
+
age,
|
|
85
|
+
anonymousId,
|
|
86
|
+
birthday: { month: 12, day: 31 },
|
|
87
|
+
...friendbuyAttributes
|
|
88
|
+
},
|
|
89
|
+
true
|
|
90
|
+
])
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Generated file. DO NOT MODIFY IT BY HAND.
|
|
2
|
+
|
|
3
|
+
export interface Payload {
|
|
4
|
+
/**
|
|
5
|
+
* The user's customer ID.
|
|
6
|
+
*/
|
|
7
|
+
customerId: string
|
|
8
|
+
/**
|
|
9
|
+
* The user's anonymous ID.
|
|
10
|
+
*/
|
|
11
|
+
anonymousId?: string
|
|
12
|
+
/**
|
|
13
|
+
* The user's email address.
|
|
14
|
+
*/
|
|
15
|
+
email: string
|
|
16
|
+
/**
|
|
17
|
+
* Flag to indicate whether the user is a new customer.
|
|
18
|
+
*/
|
|
19
|
+
isNewCustomer?: boolean
|
|
20
|
+
/**
|
|
21
|
+
* The status of the user in your loyalty program. Valid values are "in", "out", or "blocked".
|
|
22
|
+
*/
|
|
23
|
+
loyaltyStatus?: string
|
|
24
|
+
/**
|
|
25
|
+
* The user's given name.
|
|
26
|
+
*/
|
|
27
|
+
firstName?: string
|
|
28
|
+
/**
|
|
29
|
+
* The user's surname.
|
|
30
|
+
*/
|
|
31
|
+
lastName?: string
|
|
32
|
+
/**
|
|
33
|
+
* The user's full name.
|
|
34
|
+
*/
|
|
35
|
+
name?: string
|
|
36
|
+
/**
|
|
37
|
+
* The user's age.
|
|
38
|
+
*/
|
|
39
|
+
age?: number
|
|
40
|
+
/**
|
|
41
|
+
* The user's birthday in the format "YYYY-MM-DD", or "0000-MM-DD" to omit the year.
|
|
42
|
+
*/
|
|
43
|
+
birthday?: string
|
|
44
|
+
/**
|
|
45
|
+
* Coupon code that customer supplied when they signed up.
|
|
46
|
+
*/
|
|
47
|
+
coupon?: string
|
|
48
|
+
/**
|
|
49
|
+
* Friendbuy attribution ID that associates the customer who is signing up with the advocate who referred them.
|
|
50
|
+
*/
|
|
51
|
+
attributionId?: string
|
|
52
|
+
/**
|
|
53
|
+
* Friendbuy referral code that associates the customer who is signing up with the advocate who referred them.
|
|
54
|
+
*/
|
|
55
|
+
referralCode?: string
|
|
56
|
+
/**
|
|
57
|
+
* Custom attributes to send to Friendbuy. You should pass an object whose keys are the names of the custom attributes and whose values are strings. Non-string-valued attributes will be dropped.
|
|
58
|
+
*/
|
|
59
|
+
friendbuyAttributes?: {
|
|
60
|
+
[k: string]: unknown
|
|
61
|
+
}
|
|
62
|
+
}
|