Skip to content

Commit 7a565a9

Browse files
committed
feat: notify slack
1 parent 18c07b2 commit 7a565a9

File tree

3 files changed

+266
-105
lines changed

3 files changed

+266
-105
lines changed

src/common/apple/purchase.ts

Lines changed: 170 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ import {
1212
UserTransactionStatus,
1313
} from '../../entity/user/UserTransaction';
1414
import createOrGetConnection from '../../db';
15+
import { purchaseCores, UserTransactionError } from '../njord';
16+
import { TransferError } from '../../errors';
17+
import {
18+
concatTextToNewline,
19+
isProd,
20+
isTest,
21+
updateFlagsStatement,
22+
} from '../utils';
23+
import type { Block, KnownBlock } from '@slack/web-api';
24+
import { webhooks } from '../slack';
1525

1626
export const isCorePurchaseApple = ({
1727
transactionInfo,
@@ -25,6 +35,109 @@ export const isCorePurchaseApple = ({
2535
);
2636
};
2737

38+
export const notifyNewStoreKitPurchase = async ({
39+
data,
40+
transaction,
41+
user,
42+
currencyInUSD,
43+
}: {
44+
data: JWSTransactionDecodedPayload;
45+
transaction: UserTransaction;
46+
user: User;
47+
currencyInUSD: number;
48+
}) => {
49+
if (isTest) {
50+
return;
51+
}
52+
53+
const blocks: (KnownBlock | Block)[] = [
54+
{
55+
type: 'header',
56+
text: {
57+
type: 'plain_text',
58+
text: 'Cores purchased :cores: :apple-ico:',
59+
emoji: true,
60+
},
61+
},
62+
{
63+
type: 'section',
64+
fields: [
65+
{
66+
type: 'mrkdwn',
67+
text: concatTextToNewline('*Transaction ID:*', data.appTransactionId),
68+
},
69+
{
70+
type: 'mrkdwn',
71+
text: concatTextToNewline(
72+
'*App Account Token:*',
73+
data.appAccountToken,
74+
),
75+
},
76+
],
77+
},
78+
{
79+
type: 'section',
80+
fields: [
81+
{
82+
type: 'mrkdwn',
83+
text: concatTextToNewline('*Product:*', data.productId),
84+
},
85+
{
86+
type: 'mrkdwn',
87+
text: concatTextToNewline('*Cores:*', transaction.value.toString()),
88+
},
89+
{
90+
type: 'mrkdwn',
91+
text: concatTextToNewline(
92+
'*Purchased by:*',
93+
`<https://app.daily.dev/${user.id}|${user.id}>`,
94+
),
95+
},
96+
],
97+
},
98+
{
99+
type: 'section',
100+
fields: [
101+
{
102+
type: 'mrkdwn',
103+
text: concatTextToNewline(
104+
'*Cost:*',
105+
new Intl.NumberFormat('en-US', {
106+
style: 'currency',
107+
currency: 'USD',
108+
}).format(currencyInUSD || 0),
109+
),
110+
},
111+
{
112+
type: 'mrkdwn',
113+
text: concatTextToNewline('*Currency:*', 'USD'),
114+
},
115+
],
116+
},
117+
{
118+
type: 'section',
119+
fields: [
120+
{
121+
type: 'mrkdwn',
122+
text: concatTextToNewline(
123+
'*Cost (local):*',
124+
new Intl.NumberFormat('en-US', {
125+
style: 'currency',
126+
currency: data.currency,
127+
}).format((data.price || 0) / 1000),
128+
),
129+
},
130+
{
131+
type: 'mrkdwn',
132+
text: concatTextToNewline('*Currency (local):*', data.currency),
133+
},
134+
],
135+
},
136+
];
137+
138+
await webhooks.transactions.send({ blocks });
139+
};
140+
28141
export const handleCoresPurchase = async ({
29142
transactionInfo,
30143
user,
@@ -62,9 +175,62 @@ export const handleCoresPurchase = async ({
62175
},
63176
});
64177

65-
const userTransaction = await con
66-
.getRepository(UserTransaction)
67-
.save(payload);
178+
const transaction = await con.transaction(async (entityManager) => {
179+
const userTransaction = await entityManager
180+
.getRepository(UserTransaction)
181+
.save(payload);
182+
183+
// TODO feat/cores-iap enable for production https://dailydotdev.slack.com/archives/C07VA1FJTDK/p1745580651217029
184+
if (!isProd) {
185+
try {
186+
await purchaseCores({
187+
transaction: userTransaction,
188+
});
189+
} catch (error) {
190+
if (error instanceof TransferError) {
191+
const userTransactionError = new UserTransactionError({
192+
status: error.transfer.status,
193+
transaction: userTransaction,
194+
});
195+
196+
// update transaction status to error
197+
await entityManager.getRepository(UserTransaction).update(
198+
{
199+
id: userTransaction.id,
200+
},
201+
{
202+
status: error.transfer.status as number,
203+
flags: updateFlagsStatement<UserTransaction>({
204+
error: userTransactionError.message,
205+
}),
206+
},
207+
);
208+
209+
return entityManager.getRepository(UserTransaction).create({
210+
...userTransaction,
211+
status: error.transfer.status as number,
212+
flags: {
213+
...userTransaction.flags,
214+
error: userTransactionError.message,
215+
},
216+
});
217+
}
218+
219+
throw error;
220+
}
221+
}
222+
223+
return userTransaction;
224+
});
225+
226+
if (transaction.status === UserTransactionStatus.Success) {
227+
await notifyNewStoreKitPurchase({
228+
data: transactionInfo,
229+
transaction,
230+
user,
231+
currencyInUSD: transaction.valueIncFees,
232+
});
233+
}
68234

69-
return userTransaction;
235+
return transaction;
70236
};

src/common/apple/subscription.ts

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import {
2020
getAnalyticsEventFromAppleNotification,
2121
logAppleAnalyticsEvent,
2222
} from './utils';
23-
import { notifyNewStoreKitSubscription } from '../../routes/webhooks/apple';
2423
import { productIdToCycle } from './types';
24+
import { concatTextToNewline, isTest } from '../utils';
25+
import type { Block, KnownBlock } from '@slack/web-api';
26+
import { webhooks } from '../slack';
2527

2628
const getSubscriptionStatus = (
2729
notificationType: ResponseBodyV2DecodedPayload['notificationType'],
@@ -61,6 +63,99 @@ const getSubscriptionStatus = (
6163
}
6264
};
6365

66+
export const notifyNewStoreKitSubscription = async (
67+
data: JWSRenewalInfoDecodedPayload,
68+
user: User,
69+
currencyInUSD: number,
70+
) => {
71+
if (isTest) {
72+
return;
73+
}
74+
75+
const blocks: (KnownBlock | Block)[] = [
76+
{
77+
type: 'header',
78+
text: {
79+
type: 'plain_text',
80+
text: 'New Plus subscriber :moneybag: :apple-ico:',
81+
emoji: true,
82+
},
83+
},
84+
{
85+
type: 'section',
86+
fields: [
87+
{
88+
type: 'mrkdwn',
89+
text: concatTextToNewline('*Transaction ID:*', data.appTransactionId),
90+
},
91+
{
92+
type: 'mrkdwn',
93+
text: concatTextToNewline(
94+
'*App Account Token:*',
95+
data.appAccountToken,
96+
),
97+
},
98+
],
99+
},
100+
{
101+
type: 'section',
102+
fields: [
103+
{
104+
type: 'mrkdwn',
105+
text: concatTextToNewline('*Type:*', data.autoRenewProductId),
106+
},
107+
{
108+
type: 'mrkdwn',
109+
text: concatTextToNewline(
110+
'*Purchased by:*',
111+
`<https://app.daily.dev/${user.id}|${user.id}>`,
112+
),
113+
},
114+
],
115+
},
116+
{
117+
type: 'section',
118+
fields: [
119+
{
120+
type: 'mrkdwn',
121+
text: concatTextToNewline(
122+
'*Cost:*',
123+
new Intl.NumberFormat('en-US', {
124+
style: 'currency',
125+
currency: 'USD',
126+
}).format(currencyInUSD || 0),
127+
),
128+
},
129+
{
130+
type: 'mrkdwn',
131+
text: concatTextToNewline('*Currency:*', 'USD'),
132+
},
133+
],
134+
},
135+
{
136+
type: 'section',
137+
fields: [
138+
{
139+
type: 'mrkdwn',
140+
text: concatTextToNewline(
141+
'*Cost (local):*',
142+
new Intl.NumberFormat('en-US', {
143+
style: 'currency',
144+
currency: data.currency,
145+
}).format((data.renewalPrice || 0) / 1000),
146+
),
147+
},
148+
{
149+
type: 'mrkdwn',
150+
text: concatTextToNewline('*Currency (local):*', data.currency),
151+
},
152+
],
153+
},
154+
];
155+
156+
await webhooks.transactions.send({ blocks });
157+
};
158+
64159
const renewalInfoToSubscriptionFlags = (
65160
data: JWSRenewalInfoDecodedPayload,
66161
): UserSubscriptionFlags => {
@@ -147,8 +242,4 @@ export const handleAppleSubscription = async ({
147242
if (notification.notificationType === NotificationTypeV2.SUBSCRIBED) {
148243
await notifyNewStoreKitSubscription(renewalInfo, user, currencyInUSD);
149244
}
150-
151-
if (notification.notificationType === NotificationTypeV2.ONE_TIME_CHARGE) {
152-
// TODO feat/cores-iap handle one time charge
153-
}
154245
};

0 commit comments

Comments
 (0)