Skip to content

Commit 18c07b2

Browse files
committed
fix: tests
1 parent e9c0cc6 commit 18c07b2

File tree

5 files changed

+87
-46
lines changed

5 files changed

+87
-46
lines changed

__tests__/routes/webhooks/apple.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ describe('POST /webhooks/apple/notifications', () => {
106106
transactionId: '23456',
107107
originalTransactionId: '12345',
108108
webOrderLineItemId: '34343',
109-
bundleId: 'com.example',
109+
bundleId: 'dev.fylla',
110110
productId: 'annual',
111111
subscriptionGroupIdentifier: '55555',
112112
purchaseDate: 1698148900000,
@@ -228,6 +228,9 @@ describe('POST /webhooks/apple/notifications', () => {
228228
signedPayload: signedPayload({
229229
notificationType: NotificationTypeV2.SUBSCRIBED,
230230
data: {
231+
signedTransactionInfo: {
232+
productId: 'non-existing',
233+
},
231234
signedRenewalInfo: {
232235
autoRenewProductId: 'non-existing',
233236
},
@@ -244,6 +247,9 @@ describe('POST /webhooks/apple/notifications', () => {
244247
signedPayload: signedPayload({
245248
notificationType: NotificationTypeV2.SUBSCRIBED,
246249
data: {
250+
signedTransactionInfo: {
251+
appAccountToken: 'non-existing',
252+
},
247253
signedRenewalInfo: {
248254
appAccountToken: 'non-existing',
249255
},
@@ -370,6 +376,9 @@ describe('POST /webhooks/apple/notifications', () => {
370376
signedPayload: signedPayload({
371377
notificationType: NotificationTypeV2.SUBSCRIBED,
372378
data: {
379+
signedTransactionInfo: {
380+
appAccountToken: '4b1d83a3-163e-4434-a502-96fb2a516a51',
381+
},
373382
signedRenewalInfo: {
374383
appAccountToken: '4b1d83a3-163e-4434-a502-96fb2a516a51',
375384
},

src/common/apple/purchase.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,38 +14,38 @@ import {
1414
import createOrGetConnection from '../../db';
1515

1616
export const isCorePurchaseApple = ({
17-
decodedInfo,
17+
transactionInfo,
1818
}: {
19-
decodedInfo: JWSTransactionDecodedPayload;
19+
transactionInfo: JWSTransactionDecodedPayload;
2020
}) => {
2121
return (
22-
getAppleTransactionType({ decodedInfo }) ===
22+
getAppleTransactionType({ transactionInfo }) ===
2323
AppleTransactionType.Consumable &&
24-
!!decodedInfo.productId?.startsWith('cores_')
24+
!!transactionInfo.productId?.startsWith('cores_')
2525
);
2626
};
2727

2828
export const handleCoresPurchase = async ({
29-
decodedInfo,
29+
transactionInfo,
3030
user,
3131
}: {
32-
decodedInfo: JWSTransactionDecodedPayload;
32+
transactionInfo: JWSTransactionDecodedPayload;
3333
user: User;
3434
environment: Environment;
3535
notification: ResponseBodyV2DecodedPayload;
3636
}): Promise<UserTransaction> => {
37-
if (!decodedInfo.transactionId) {
38-
throw new Error('Missing transactionId in decodedInfo');
37+
if (!transactionInfo.transactionId) {
38+
throw new Error('Missing transactionId in transactionInfo');
3939
}
4040

41-
if (!decodedInfo.productId) {
42-
throw new Error('Missing productId in decodedInfo');
41+
if (!transactionInfo.productId) {
42+
throw new Error('Missing productId in transactionInfo');
4343
}
4444

4545
const con = await createOrGetConnection();
4646

4747
// TODO feat/cores-iap load from api metadata/new endpoint
48-
const coresValue = Number(decodedInfo.productId.match(/\d+/)?.[0]);
48+
const coresValue = Number(transactionInfo.productId.match(/\d+/)?.[0]);
4949

5050
const payload = con.getRepository(UserTransaction).create({
5151
processor: UserTransactionProcessor.AppleStoreKit,
@@ -58,7 +58,7 @@ export const handleCoresPurchase = async ({
5858
fee: 0, // no fee when buying cores
5959
request: {},
6060
flags: {
61-
providerId: decodedInfo.transactionId,
61+
providerId: transactionInfo.transactionId,
6262
},
6363
});
6464

src/common/apple/subscription.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Subtype,
44
type Environment,
55
type JWSRenewalInfoDecodedPayload,
6+
type JWSTransactionDecodedPayload,
67
type ResponseBodyV2DecodedPayload,
78
} from '@apple/app-store-server-library';
89
import { logger } from '../../logger';
@@ -84,12 +85,14 @@ const renewalInfoToSubscriptionFlags = (
8485
};
8586

8687
export const handleAppleSubscription = async ({
87-
decodedInfo,
88+
transactionInfo,
89+
renewalInfo,
8890
user,
8991
environment,
9092
notification,
9193
}: {
92-
decodedInfo: JWSRenewalInfoDecodedPayload;
94+
transactionInfo: JWSTransactionDecodedPayload;
95+
renewalInfo: JWSRenewalInfoDecodedPayload;
9396
user: User;
9497
environment: Environment;
9598
notification: ResponseBodyV2DecodedPayload;
@@ -113,7 +116,7 @@ export const handleAppleSubscription = async ({
113116
notification.subtype,
114117
);
115118

116-
const subscriptionFlags = renewalInfoToSubscriptionFlags(decodedInfo);
119+
const subscriptionFlags = renewalInfoToSubscriptionFlags(renewalInfo);
117120

118121
await updateStoreKitUserSubscription({
119122
userId: user.id,
@@ -122,8 +125,8 @@ export const handleAppleSubscription = async ({
122125
});
123126

124127
const currencyInUSD = await convertCurrencyToUSD(
125-
(decodedInfo.renewalPrice || 0) / 1000,
126-
decodedInfo.currency || 'USD',
128+
(renewalInfo.renewalPrice || 0) / 1000,
129+
renewalInfo.currency || 'USD',
127130
);
128131

129132
const eventName = getAnalyticsEventFromAppleNotification(
@@ -132,10 +135,20 @@ export const handleAppleSubscription = async ({
132135
);
133136

134137
if (eventName) {
135-
await logAppleAnalyticsEvent(decodedInfo, eventName, user, currencyInUSD);
138+
await logAppleAnalyticsEvent(
139+
transactionInfo,
140+
renewalInfo,
141+
eventName,
142+
user,
143+
currencyInUSD,
144+
);
136145
}
137146

138147
if (notification.notificationType === NotificationTypeV2.SUBSCRIBED) {
139-
await notifyNewStoreKitSubscription(decodedInfo, user, currencyInUSD);
148+
await notifyNewStoreKitSubscription(renewalInfo, user, currencyInUSD);
149+
}
150+
151+
if (notification.notificationType === NotificationTypeV2.ONE_TIME_CHARGE) {
152+
// TODO feat/cores-iap handle one time charge
140153
}
141154
};

src/common/apple/utils.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,21 @@ export const verifyAndDecodeAppleSignedData = async ({
3030
notification: ResponseBodyV2DecodedPayload;
3131
environment: Environment;
3232
verifier: SignedDataVerifier;
33-
}) => {
33+
}): Promise<{
34+
transactionInfo?: JWSTransactionDecodedPayload;
35+
renewalInfo?: JWSRenewalInfoDecodedPayload;
36+
}> => {
37+
let renewalInfo: JWSRenewalInfoDecodedPayload | undefined = undefined;
38+
let transactionInfo: JWSTransactionDecodedPayload | undefined = undefined;
39+
3440
if (!isNullOrUndefined(notification.data?.signedRenewalInfo)) {
35-
return await verifier.verifyAndDecodeTransaction(
41+
renewalInfo = await verifier.verifyAndDecodeRenewalInfo(
3642
notification.data.signedRenewalInfo,
3743
);
3844
}
3945

4046
if (!isNullOrUndefined(notification.data?.signedTransactionInfo)) {
41-
return await verifier.verifyAndDecodeTransaction(
47+
transactionInfo = await verifier.verifyAndDecodeTransaction(
4248
notification.data.signedTransactionInfo,
4349
);
4450
}
@@ -52,30 +58,36 @@ export const verifyAndDecodeAppleSignedData = async ({
5258
'Missing signed data in notification data',
5359
);
5460

55-
return null;
61+
return {
62+
transactionInfo,
63+
renewalInfo,
64+
};
5665
};
5766

5867
export const logAppleAnalyticsEvent = async (
59-
data: JWSRenewalInfoDecodedPayload,
68+
transactionInfo: JWSTransactionDecodedPayload,
69+
renewalInfo: JWSRenewalInfoDecodedPayload,
6070
eventName: AnalyticsEventName,
6171
user: User,
6272
currencyInUSD: number,
6373
) => {
64-
if (!data || isTest) {
74+
if (!transactionInfo || isTest) {
6575
return;
6676
}
6777

6878
const cycle =
69-
productIdToCycle[data?.autoRenewProductId as keyof typeof productIdToCycle];
70-
const cost = data?.renewalPrice;
79+
productIdToCycle[
80+
renewalInfo?.autoRenewProductId as keyof typeof productIdToCycle
81+
];
82+
const cost = renewalInfo?.renewalPrice;
7183

7284
const extra = {
7385
payment: SubscriptionProvider.AppleStoreKit,
7486
cycle,
7587
cost: currencyInUSD,
7688
currency: 'USD',
7789
localCost: cost ? cost / 1000 : undefined,
78-
localCurrency: data?.currency,
90+
localCurrency: transactionInfo?.currency,
7991
payout: {
8092
total: currencyInUSD * 100,
8193
grandTotal: currencyInUSD * 100,
@@ -86,8 +98,8 @@ export const logAppleAnalyticsEvent = async (
8698
await sendAnalyticsEvent([
8799
{
88100
event_name: eventName,
89-
event_timestamp: new Date(data?.signedDate || ''),
90-
event_id: data.appTransactionId,
101+
event_timestamp: new Date(transactionInfo?.signedDate || ''),
102+
event_id: transactionInfo.appTransactionId,
91103
app_platform: 'api',
92104
user_id: user.id,
93105
extra: JSON.stringify(extra),
@@ -130,11 +142,11 @@ export const loadAppleRootCAs = async (): Promise<Buffer[]> => {
130142
};
131143

132144
export const getAppleTransactionType = ({
133-
decodedInfo,
145+
transactionInfo,
134146
}: {
135-
decodedInfo: JWSTransactionDecodedPayload;
147+
transactionInfo: JWSTransactionDecodedPayload;
136148
}): AppleTransactionType | null => {
137-
switch (decodedInfo.type) {
149+
switch (transactionInfo.type) {
138150
case 'Auto-Renewable Subscription':
139151
return AppleTransactionType.AutoRenewableSubscription;
140152
case 'Non-Consumable':

src/routes/webhooks/apple.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,14 @@ const handleNotifcationRequest = async (
7272
return response.status(200).send({ received: true });
7373
}
7474

75-
const decodedInfo = await verifyAndDecodeAppleSignedData({
76-
notification,
77-
environment,
78-
verifier,
79-
});
75+
const { transactionInfo, renewalInfo } =
76+
await verifyAndDecodeAppleSignedData({
77+
notification,
78+
environment,
79+
verifier,
80+
});
8081

81-
if (!decodedInfo) {
82+
if (!transactionInfo) {
8283
return response.status(400).send({ error: 'Invalid Payload' });
8384
}
8485

@@ -87,7 +88,7 @@ const handleNotifcationRequest = async (
8788
select: ['id', 'subscriptionFlags'],
8889
where: {
8990
subscriptionFlags: JsonContains({
90-
appAccountToken: decodedInfo.appAccountToken,
91+
appAccountToken: transactionInfo.appAccountToken,
9192
}),
9293
},
9394
});
@@ -121,11 +122,11 @@ const handleNotifcationRequest = async (
121122
return response.status(403).send({ error: 'Invalid Payload' });
122123
}
123124

124-
switch (getAppleTransactionType({ decodedInfo })) {
125+
switch (getAppleTransactionType({ transactionInfo })) {
125126
case AppleTransactionType.Consumable:
126-
if (isCorePurchaseApple({ decodedInfo })) {
127+
if (isCorePurchaseApple({ transactionInfo })) {
127128
await handleCoresPurchase({
128-
decodedInfo,
129+
transactionInfo,
129130
user,
130131
environment,
131132
notification,
@@ -135,8 +136,13 @@ const handleNotifcationRequest = async (
135136
}
136137
break;
137138
case AppleTransactionType.AutoRenewableSubscription:
139+
if (!renewalInfo) {
140+
throw new Error('Missing renewal info for subscription');
141+
}
142+
138143
await handleAppleSubscription({
139-
decodedInfo,
144+
transactionInfo,
145+
renewalInfo,
140146
user,
141147
environment,
142148
notification,
@@ -148,7 +154,8 @@ const handleNotifcationRequest = async (
148154

149155
logger.info(
150156
{
151-
decodedInfo,
157+
transactionInfo,
158+
renewalInfo,
152159
user,
153160
environment,
154161
provider: SubscriptionProvider.AppleStoreKit,

0 commit comments

Comments
 (0)