Skip to content

Commit 8699d21

Browse files

File tree

8 files changed

+104
-1
lines changed

8 files changed

+104
-1
lines changed

index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,9 +671,18 @@ export enum APIError {
671671
* An error that indicates the transaction identifier doesn’t represent a consumable in-app purchase.
672672
*
673673
* {@link https://developer.apple.com/documentation/appstoreserverapi/invalidtransactionnotconsumableerror InvalidTransactionNotConsumableError}
674+
*
675+
* @deprecated
674676
*/
675677
INVALID_TRANSACTION_NOT_CONSUMABLE = 4000043,
676678

679+
/**
680+
* An error that indicates the transaction identifier represents an unsupported in-app purchase type.
681+
*
682+
* {@link https://developer.apple.com/documentation/appstoreserverapi/invalidtransactiontypenotsupportederror InvalidTransactionTypeNotSupportedError}
683+
*/
684+
INVALID_TRANSACTION_TYPE_NOT_SUPPORTED = 4000047,
685+
677686
/**
678687
* An error that indicates the subscription doesn't qualify for a renewal-date extension due to its subscription state.
679688
*

models/ConsumptionRequest.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { LifetimeDollarsPurchased } from "./LifetimeDollarsPurchased"
77
import { LifetimeDollarsRefunded } from "./LifetimeDollarsRefunded"
88
import { Platform } from "./Platform"
99
import { PlayTime } from "./PlayTime"
10+
import { RefundPreference } from "./RefundPreference"
1011
import { UserStatus } from "./UserStatus"
1112

1213
/**
@@ -92,4 +93,11 @@ export interface ConsumptionRequest {
9293
* {@link https://developer.apple.com/documentation/appstoreserverapi/userstatus userStatus}
9394
**/
9495
userStatus?: UserStatus | number
96+
97+
/**
98+
* A value that indicates your preference, based on your operational logic, as to whether Apple should grant the refund.
99+
*
100+
* {@link https://developer.apple.com/documentation/appstoreserverapi/refundpreference refundPreference}
101+
**/
102+
refundPreference?: RefundPreference | number
95103
}

models/ConsumptionRequestReason.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) 2024 Apple Inc. Licensed under MIT License.
2+
3+
import { StringValidator } from "./Validator";
4+
5+
/**
6+
* The customer-provided reason for a refund request.
7+
*
8+
* {@link https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason consumptionRequestReason}
9+
*/
10+
export enum ConsumptionRequestReason {
11+
UNINTENDED_PURCHASE = "UNINTENDED_PURCHASE",
12+
FULFILLMENT_ISSUE = "FULFILLMENT_ISSUE",
13+
UNSATISFIED_WITH_PURCHASE = "UNSATISFIED_WITH_PURCHASE",
14+
LEGAL = "LEGAL",
15+
OTHER = "OTHER",
16+
}
17+
18+
export class ConsumptionRequestReasonValidator extends StringValidator {}

models/Data.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) 2023 Apple Inc. Licensed under MIT License.
22

3+
import { ConsumptionRequestReason, ConsumptionRequestReasonValidator } from "./ConsumptionRequestReason"
34
import { Environment, EnvironmentValidator } from "./Environment"
45
import { Status, StatusValidator } from "./Status"
56
import { Validator } from "./Validator"
@@ -59,12 +60,20 @@ export interface Data {
5960
* {@link https://developer.apple.com/documentation/appstoreservernotifications/status status}
6061
**/
6162
status?: Status | number
63+
64+
/**
65+
* The reason the customer requested the refund.
66+
*
67+
* {@link https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason consumptionRequestReason}
68+
**/
69+
consumptionRequestReason?: ConsumptionRequestReason | string
6270
}
6371

6472

6573
export class DataValidator implements Validator<Data> {
6674
static readonly environmentValidator = new EnvironmentValidator()
6775
static readonly statusValidator = new StatusValidator()
76+
static readonly consumptionRequestReasonValidator = new ConsumptionRequestReasonValidator()
6877
validate(obj: any): obj is Data {
6978
if ((typeof obj['environment'] !== 'undefined') && !(DataValidator.environmentValidator.validate(obj['environment']))) {
7079
return false
@@ -87,6 +96,9 @@ export class DataValidator implements Validator<Data> {
8796
if ((typeof obj['status'] !== 'undefined') && !(DataValidator.statusValidator.validate(obj['status']))) {
8897
return false
8998
}
99+
if ((typeof obj['consumptionRequestReason'] !== 'undefined') && !(DataValidator.consumptionRequestReasonValidator.validate(obj['consumptionRequestReason']))) {
100+
return false
101+
}
90102
return true
91103
}
92104
}

models/RefundPreference.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) 2024 Apple Inc. Licensed under MIT License.
2+
3+
/**
4+
* A value that indicates your preferred outcome for the refund request.
5+
*
6+
* {@link https://developer.apple.com/documentation/appstoreserverapi/refundpreference refundPreference}
7+
*/
8+
export enum RefundPreference {
9+
UNDECLARED = 0,
10+
PREFER_GRANT = 1,
11+
PREFER_DECLINE = 2,
12+
NO_PREFERENCE = 3,
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"notificationType": "CONSUMPTION_REQUEST",
3+
"notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20",
4+
"data": {
5+
"environment": "LocalTesting",
6+
"appAppleId": 41234,
7+
"bundleId": "com.example",
8+
"bundleVersion": "1.2.3",
9+
"signedTransactionInfo": "signed_transaction_info_value",
10+
"signedRenewalInfo": "signed_renewal_info_value",
11+
"status": 1,
12+
"consumptionRequestReason": "UNINTENDED_PURCHASE"
13+
},
14+
"version": "2.0",
15+
"signedDate": 1698148900000
16+
}

tests/unit-tests/api_client.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { Subtype } from "../../models/Subtype";
1515
import { UserStatus } from "../../models/UserStatus";
1616
import { readFile } from "../util"
1717
import { InAppOwnershipType } from "../../models/InAppOwnershipType";
18+
import { RefundPreference } from "../../models/RefundPreference";
1819
import { APIError, APIException, AppStoreServerAPIClient, ExtendReasonCode, ExtendRenewalDateRequest, MassExtendRenewalDateRequest, NotificationHistoryRequest, NotificationHistoryResponseItem, Order, OrderLookupStatus, ProductType, SendAttemptResult, TransactionHistoryRequest } from "../../index";
1920
import { Response } from "node-fetch";
2021

@@ -387,6 +388,7 @@ describe('The api client ', () => {
387388
expect(6).toBe(body.lifetimeDollarsRefunded)
388389
expect(7).toBe(body.lifetimeDollarsPurchased)
389390
expect(4).toBe(body.userStatus)
391+
expect(3).toBe(body.refundPreference)
390392
});
391393

392394
const consumptionRequest: ConsumptionRequest = {
@@ -400,7 +402,8 @@ describe('The api client ', () => {
400402
playTime: PlayTime.ONE_DAY_TO_FOUR_DAYS,
401403
lifetimeDollarsRefunded: LifetimeDollarsRefunded.ONE_THOUSAND_DOLLARS_TO_ONE_THOUSAND_NINE_HUNDRED_NINETY_NINE_DOLLARS_AND_NINETY_NINE_CENTS,
402404
lifetimeDollarsPurchased: LifetimeDollarsPurchased.TWO_THOUSAND_DOLLARS_OR_GREATER,
403-
userStatus: UserStatus.LIMITED_ACCESS
405+
userStatus: UserStatus.LIMITED_ACCESS,
406+
refundPreference: RefundPreference.NO_PREFERENCE
404407
}
405408

406409
client.sendConsumptionData("49571273", consumptionRequest);

tests/unit-tests/transaction_decoding.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { InAppOwnershipType } from "../../models/InAppOwnershipType";
1313
import { RevocationReason } from "../../models/RevocationReason";
1414
import { TransactionReason } from "../../models/TransactionReason";
1515
import { Type } from "../../models/Type";
16+
import { ConsumptionRequestReason } from "../../models/ConsumptionRequestReason";
1617

1718

1819
describe('Testing decoding of signed data', () => {
@@ -102,6 +103,29 @@ describe('Testing decoding of signed data', () => {
102103
expect("signed_transaction_info_value").toBe(notification.data!.signedTransactionInfo)
103104
expect("signed_renewal_info_value").toBe(notification.data!.signedRenewalInfo)
104105
expect(Status.ACTIVE).toBe(notification.data!.status)
106+
expect(notification.data!.consumptionRequestReason).toBeFalsy()
107+
})
108+
it('should decode a signed CONSUMPTION_REQUEST notification', async () => {
109+
const signedNotification = createSignedDataFromJson("tests/resources/models/signedNotification.json")
110+
111+
const notification = await getDefaultSignedPayloadVerifier().verifyAndDecodeNotification(signedNotification)
112+
113+
expect(NotificationTypeV2.CONSUMPTION_REQUEST).toBe(notification.notificationType)
114+
expect(notification.subtype).toBeFalsy()
115+
expect("002e14d5-51f5-4503-b5a8-c3a1af68eb20").toBe(notification.notificationUUID)
116+
expect("2.0").toBe(notification.version)
117+
expect(1698148900000).toBe(notification.signedDate)
118+
expect(notification.data).toBeTruthy()
119+
expect(notification.summary).toBeFalsy()
120+
expect(notification.externalPurchaseToken).toBeFalsy()
121+
expect(Environment.LOCAL_TESTING).toBe(notification.data!.environment)
122+
expect(41234).toBe(notification.data!.appAppleId)
123+
expect("com.example").toBe(notification.data!.bundleId)
124+
expect("1.2.3").toBe(notification.data!.bundleVersion)
125+
expect("signed_transaction_info_value").toBe(notification.data!.signedTransactionInfo)
126+
expect("signed_renewal_info_value").toBe(notification.data!.signedRenewalInfo)
127+
expect(Status.ACTIVE).toBe(notification.data!.status)
128+
expect(ConsumptionRequestReason.UNINTENDED_PURCHASE).toBe(notification.data!.consumptionRequestReason)
105129
})
106130
it('should decode a signed summary notification', async () => {
107131
const signedNotification = createSignedDataFromJson("tests/resources/models/signedSummaryNotification.json")

0 commit comments

Comments
 (0)