├── .github ├── dependabot.yml └── workflows │ ├── ci-prb.yml │ └── ci-release-docs.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── Package.swift ├── README.md ├── Sources └── AppStoreServerLibrary │ ├── AppStoreServerAPIClient.swift │ ├── ChainVerifier.swift │ ├── JWSSignatureCreator.swift │ ├── Models │ ├── AccountTenure.swift │ ├── AppStoreEnvironment.swift │ ├── AppTransaction.swift │ ├── AutoRenewStatus.swift │ ├── CheckTestNotificationResponse.swift │ ├── ConsumptionRequest.swift │ ├── ConsumptionRequestReason.swift │ ├── ConsumptionStatus.swift │ ├── DecodedSignedData.swift │ ├── DeliveryStatus.swift │ ├── ErrorPayload.swift │ ├── ExpirationIntent.swift │ ├── ExtendReasonCode.swift │ ├── ExtendRenewalDateRequest.swift │ ├── ExtendRenewalDateResponse.swift │ ├── ExternalPurchaseToken.swift │ ├── HistoryResponse.swift │ ├── InAppOwnershipType.swift │ ├── JWSRenewalInfoDecodedPayload.swift │ ├── JWSTransactionDecodedPayload.swift │ ├── LastTransactionsItem.swift │ ├── LifetimeDollarsPurchased.swift │ ├── LifetimeDollarsRefunded.swift │ ├── MassExtendRenewalDateRequest.swift │ ├── MassExtendRenewalDateResponse.swift │ ├── MassExtendRenewalDateStatusResponse.swift │ ├── NotificationData.swift │ ├── NotificationHistoryRequest.swift │ ├── NotificationHistoryResponse.swift │ ├── NotificationHistoryResponseItem.swift │ ├── NotificationTypeV2.swift │ ├── OfferDiscountType.swift │ ├── OfferType.swift │ ├── OrderLookupResponse.swift │ ├── OrderLookupStatus.swift │ ├── Platform.swift │ ├── PlayTime.swift │ ├── PriceIncreaseStatus.swift │ ├── ProductType.swift │ ├── PurchasePlatform.swift │ ├── RefundHistoryResponse.swift │ ├── RefundPreference.swift │ ├── ResponseBodyV2.swift │ ├── ResponseBodyV2DecodedPayload.swift │ ├── RevocationReason.swift │ ├── SendAttemptItem.swift │ ├── SendAttemptResult.swift │ ├── SendTestNotificationResponse.swift │ ├── Status.swift │ ├── StatusResponse.swift │ ├── SubscriptionGroupIdentifierItem.swift │ ├── Subtype.swift │ ├── Summary.swift │ ├── TransactionHistoryRequest.swift │ ├── TransactionInfoResponse.swift │ ├── TransactionReason.swift │ └── UserStatus.swift │ ├── PromotionalOfferSignatureCreator.swift │ ├── ReceiptUtility.swift │ ├── SignedDataVerifier.swift │ └── Utility.swift └── Tests └── AppStoreServerLibraryTests ├── AppStoreServerAPIClientTests.swift ├── JWSSignatureCreatorTests.swift ├── PromotionalOfferSignatureCreatorTests.swift ├── ReceiptUtilityTests.swift ├── SignedDataVerifierTests.swift ├── SignedModelTests.swift ├── TestingUtility.swift ├── XcodeSignedDataVerifierTests.swift └── resources ├── certs ├── testCA.der └── testSigningKey.p8 ├── mock_signed_data ├── legacyTransaction ├── missingX5CHeaderClaim ├── renewalInfo ├── testNotification ├── transactionInfo └── wrongBundleId ├── models ├── apiException.json ├── apiTooManyRequestsException.json ├── apiUnknownError.json ├── appTransaction.json ├── extendRenewalDateForAllActiveSubscribersResponse.json ├── extendSubscriptionRenewalDateResponse.json ├── getAllSubscriptionStatusesResponse.json ├── getNotificationHistoryResponse.json ├── getRefundHistoryResponse.json ├── getStatusOfSubscriptionRenewalDateExtensionsResponse.json ├── getTestNotificationStatusResponse.json ├── lookupOrderIdResponse.json ├── requestTestNotificationResponse.json ├── signedConsumptionRequestNotification.json ├── signedExternalPurchaseTokenNotification.json ├── signedExternalPurchaseTokenSandboxNotification.json ├── signedNotification.json ├── signedRenewalInfo.json ├── signedSummaryNotification.json ├── signedTransaction.json ├── transactionHistoryResponse.json ├── transactionHistoryResponseWithMalformedAppAppleId.json ├── transactionHistoryResponseWithMalformedEnvironment.json └── transactionInfoResponse.json └── xcode ├── xcode-app-receipt-empty ├── xcode-app-receipt-with-transaction ├── xcode-signed-app-transaction ├── xcode-signed-renewal-info └── xcode-signed-transaction /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | time: "02:00" 8 | -------------------------------------------------------------------------------- /.github/workflows/ci-prb.yml: -------------------------------------------------------------------------------- 1 | name: PR Builder 2 | on: 3 | pull_request: 4 | branches: [ main ] 5 | push: 6 | branches: [ main ] 7 | jobs: 8 | build: 9 | name: Swift ${{ matrix.swift }} ${{ matrix.os }} 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | swift: [ "6" ] 14 | os: [ ubuntu-latest ] 15 | steps: 16 | - name: Checkout Code 17 | uses: actions/checkout@v4 18 | - name: Set up Swift ${{ matrix.swift }} 19 | uses: swift-actions/setup-swift@v2 20 | with: 21 | swift-version: ${{ matrix.swift }} 22 | - name: Swift build 23 | run: swift build 24 | - name: Swift test 25 | run: swift test 26 | -------------------------------------------------------------------------------- /.github/workflows/ci-release-docs.yml: -------------------------------------------------------------------------------- 1 | name: Doc Builder 2 | on: 3 | release: 4 | types: [published] 5 | permissions: 6 | pages: write 7 | id-token: write 8 | jobs: 9 | build: 10 | name: Doc Builder 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Code 14 | uses: actions/checkout@v4 15 | - name: Set up Swift 6 16 | uses: swift-actions/setup-swift@v2 17 | with: 18 | swift-version: 6 19 | - name: Swift build 20 | run: swift build 21 | - name: Build docs 22 | run: swift package --allow-writing-to-directory docs generate-documentation --target AppStoreServerLibrary --product AppStoreServerLibrary --disable-indexing --hosting-base-path app-store-server-library-swift --output-path docs 23 | - name: Upload docs 24 | uses: actions/upload-pages-artifact@v3 25 | with: 26 | path: docs 27 | deploy: 28 | environment: 29 | name: github-pages 30 | url: ${{ steps.deployment.outputs.page_url }} 31 | needs: build 32 | runs-on: ubuntu-latest 33 | name: Deploy docs 34 | steps: 35 | - name: Deploy 36 | id: deployment 37 | uses: actions/deploy-pages@v4 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .swiftpm 2 | .DS_Store 3 | docs/ 4 | .build/ 5 | 6 | Packages/ 7 | Package.pins 8 | Package.resolved 9 | *.xcodeproj 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 3.0.0 4 | - Incorporate changes for App Store Server API v1.15 and App Store Server Notifications v2.15 [https://github.com/apple/app-store-server-library-swift/pull/82] 5 | - Add verified chain caching to improve performance [https://github.com/apple/app-store-server-library-swift/pull/80] 6 | - Rename Environment -> AppStoreEnvironment [https://github.com/apple/app-store-server-library-swift/pull/81] 7 | - This change is a breaking change 8 | - Rename Data -> NotificationData to deconflict with Foundation.Data [https://github.com/apple/app-store-server-library-swift/pull/79] 9 | - This change is a breaking change 10 | - Move to Swift and Swift Tools Version 6 [https://github.com/apple/app-store-server-library-swift/pull/78] 11 | - This change is a breaking change 12 | - Update to JWTKit5 [https://github.com/apple/app-store-server-library-swift/pull/68] from @dimitribouniol 13 | - Make data models conform to Sendable [https://github.com/apple/app-store-server-library-swift/pull/64] from @shimastripe 14 | 15 | ## Version 2.3.0 16 | - Incorporate changes for App Store Server API v1.13 and App Store Server Notifications v2.13 [https://github.com/apple/app-store-server-library-swift/pull/61] 17 | - Resolve issue where a Date passed as an input that contained a microsecond portiion would cause an API exception [https://github.com/apple/app-store-server-library-swift/pull/62] 18 | 19 | ## Version 2.2.0 20 | - Incorporate changes for App Store Server API v1.12 and App Store Server Notifications v2.12 [https://github.com/apple/app-store-server-library-swift/pull/57] 21 | - Improve README installation code [https://github.com/apple/app-store-server-library-swift/pull/56] from @philmodin 22 | - Handle null appAccountToken appropriately in PromotionalOfferSignatureCreator and update JSON parsing logic to follow standard patterns [https://github.com/apple/app-store-server-library-java/pull/100] 23 | 24 | ## Version 2.1.0 25 | - Incorporate changes for App Store Server API v1.11 and App Store Server Notifications v2.11 [https://github.com/apple/app-store-server-library-swift/pull/49] 26 | - Various documentation and quality of life improvements, including contributions from @Fidetro 27 | 28 | ## Version 2.0.0 29 | - Incorporate changes for App Store Server API v1.10.1 [https://github.com/apple/app-store-server-library-swift/pull/42] 30 | - This change is a breaking change, as the datatype of the price field has changed from Int32? to Int64? 31 | 32 | ## Version 1.1.0 33 | - Support App Store Server Notifications v2.10 [https://github.com/apple/app-store-server-library-swift/pull/37] 34 | - Require appAppleId in SignedDataVerifier for the Production environment from @shimastripe [https://github.com/apple/app-store-server-library-swift/pull/35] 35 | 36 | ## Version 1.0.2 37 | - Limit platforms to supported platforms [https://github.com/apple/app-store-server-library-swift/pull/29] 38 | 39 | ## Version 1.0.1 40 | - Add public constructors to all models [https://github.com/apple/app-store-server-library-swift/pull/26] 41 | 42 | ## Version 1.0.0 43 | - Add status field to the data model [https://github.com/apple/app-store-server-library-swift/pull/7] 44 | - Adding new error codes from App Store Server API v1.9 [https://github.com/apple/app-store-server-library-swift/pull/9] 45 | - Adding new fields from App Store Server API v1.10 [https://github.com/apple/app-store-server-library-swift/pull/12] 46 | - Migrate to AsyncHTTPClient [https://github.com/apple/app-store-server-library-swift/pull/15] 47 | - Add support for LocalTesting and Xcode environments [https://github.com/apple/app-store-server-library-swift/pull/19] 48 | - Allow reading unknown enum values [https://github.com/apple/app-store-server-library-swift/pull/20] 49 | - Add errorMessage to APIException [https://github.com/apple/app-store-server-library-swift/pull/21] 50 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [opensource-conduct@group.apple.com](mailto:opensource-conduct@group.apple.com). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing! 4 | 5 | ## Reporting Bugs 6 | 7 | Please report bugs by creating [Github issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues). 8 | To help the community understand the bug and get it fixed faster, please provide the following information when creating a new issue: 9 | - A clear and descriptive title 10 | - The exact steps to reproduce the bug 11 | - The observed behavior and expected behavior 12 | 13 | If possible, also include payloads, commands, screenshots, etc to help the community identify the problem. Do not include any personal or sensitive data. 14 | 15 | ## Suggesting Improvements 16 | 17 | You can suggest improvements also by creating Github issues. 18 | When creating a new suggestion, please provide the following information: 19 | - A clear and descriptive title 20 | - A description of the proposed improvement in as many details as possible 21 | - Explain why the improvement is important 22 | 23 | ## Documentation Contribution 24 | 25 | Documentation contribution will make it easier for the community to work on the project. 26 | You may add README/diagrams to the components, or improve the existing docs. For major doc changes, we encourage you to create issues before contributing. Let us know what you are planning to change before the contribution. 27 | 28 | ## Code Contribution 29 | 30 | For minor changes (like small bug fixes or typo correction), feel free to open up a PR directly. 31 | For new features or major changes, we encourage you to create a Github issue first, and get agreement before starting on the implementation. This is to save you time in case there's duplicate effort or unforeseen risk. 32 | 33 | ## Project Licensing 34 | 35 | All contributions (including Pull Requests) to this project are provided under the terms of the project’s [LICENSE](LICENSE.txt) -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2023 Apple Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | Acknowledgements 3 | Portions of this App Store Server Library software may utilize the following copyrighted 4 | material, the use of which is hereby acknowledged. 5 | 6 | _____________________ 7 | 8 | Qutheory, LLC ( jwt-kit ) 9 | MIT License 10 | Copyright © 2020 Qutheory, LLC 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | _____________________ 16 | 17 | Apple Inc. and the SwiftCrypto project authors ( SwiftCrypto ) 18 | Copyright © 2021-2023 Apple Inc. and the SwiftCrypto project authors 19 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 20 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 21 | 22 | _____________________ 23 | 24 | Apple Inc. and the Swift project authors ( swift-docc-plugin ) 25 | Copyright © 2022 Apple Inc. and the Swift project authors 26 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 27 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 28 | Runtime Library Exception to the Apache 2.0 License: ## 29 | As an exception, if you use this Software to compile your source code and portions of this Software are embedded into the binary product as a result, you may redistribute such product without providing attribution as would otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. 30 | 31 | _____________________ 32 | 33 | Airside Mobile Inc. ( JOSESwift ) 34 | Copyright © 2020 Airside Mobile Inc. 35 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 36 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 37 | 38 | _____________________ 39 | 40 | Apple Inc. and the SwiftCertificates project authors ( swift-certificates ) 41 | Copyright © 2023 Apple Inc. and the SwiftCertificates project authors 42 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 43 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 44 | 45 | _____________________ 46 | 47 | Apple Inc. and the SwiftASN1 project authors ( swift-asn1 ) 48 | Copyright © 2023 Apple Inc. and the SwiftASN1 project authors 49 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 50 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 51 | 52 | _____________________ 53 | 54 | AsyncHttpClient Project ( async-http-client ) 55 | Copyright (c) 2014-2023 AsyncHttpClient Project. All rights reserved. 56 | 57 | Licensed under the Apache License, Version 2.0 (the "License"); 58 | you may not use this file except in compliance with the License. 59 | You may obtain a copy of the License at 60 | 61 | http://www.apache.org/licenses/LICENSE-2.0 62 | 63 | Unless required by applicable law or agreed to in writing, software 64 | distributed under the License is distributed on an "AS IS" BASIS, 65 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 66 | See the License for the specific language governing permissions and 67 | limitations under the License. 68 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.0 2 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 3 | 4 | import PackageDescription 5 | 6 | 7 | let package = Package( 8 | name: "AppStoreServerLibrary", 9 | platforms: [ 10 | .macOS(.v13), // And other server environments 11 | ], 12 | products: [ 13 | .library( 14 | name: "AppStoreServerLibrary", 15 | targets: ["AppStoreServerLibrary"]), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/apple/swift-certificates.git", from: "1.0.0"), 19 | .package(url: "https://github.com/apple/swift-asn1.git", from: "1.1.0"), 20 | .package(url: "https://github.com/apple/swift-crypto.git", "1.0.0" ..< "4.0.0"), 21 | .package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0"), 22 | .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), 23 | .package(url: "https://github.com/apple/swift-nio", from: "2.0.0"), 24 | .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"), 25 | ], 26 | targets: [ 27 | .target( 28 | name: "AppStoreServerLibrary", 29 | dependencies: [ 30 | .product(name: "X509", package: "swift-certificates"), 31 | .product(name: "Crypto", package: "swift-crypto"), 32 | .product(name: "SwiftASN1", package: "swift-asn1"), 33 | .product(name: "JWTKit", package: "jwt-kit"), 34 | .product(name: "AsyncHTTPClient", package: "async-http-client"), 35 | .product(name: "NIOFoundationCompat", package: "swift-nio"), 36 | .product(name: "NIOHTTP1", package: "swift-nio"), 37 | ]), 38 | .testTarget( 39 | name: "AppStoreServerLibraryTests", 40 | dependencies: ["AppStoreServerLibrary"], 41 | resources: [.copy("resources")] 42 | ), 43 | ] 44 | ) 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apple App Store Server Swift Library 2 | The Swift server library for the [App Store Server API](https://developer.apple.com/documentation/appstoreserverapi) and [App Store Server Notifications](https://developer.apple.com/documentation/appstoreservernotifications). Also available in [Java](https://github.com/apple/app-store-server-library-java), [Python](https://github.com/apple/app-store-server-library-python), and [Node.js](https://github.com/apple/app-store-server-library-node). 3 | 4 | ## Table of Contents 5 | 1. [Installation](#installation) 6 | 2. [Documentation](#documentation) 7 | 3. [Usage](#usage) 8 | 4. [Support](#support) 9 | 10 | ## Installation 11 | 12 | ### Requirements 13 | 14 | - Swift 6 15 | 16 | ### Swift Package Manager 17 | Add the following dependency 18 | ```swift 19 | .package(url: "https://github.com/apple/app-store-server-library-swift.git", .upToNextMinor(from: "3.0.0")), 20 | ``` 21 | 22 | ## Documentation 23 | 24 | [Documentation](https://apple.github.io/app-store-server-library-swift/documentation/appstoreserverlibrary/) 25 | 26 | [WWDC Video](https://developer.apple.com/videos/play/wwdc2023/10143/) 27 | 28 | ### Obtaining an In-App Purchase key from App Store Connect 29 | 30 | To use the App Store Server API or create promotional offer signatures, a signing key downloaded from App Store Connect is required. To obtain this key, you must have the Admin role. Go to Users and Access > Integrations > In-App Purchase. Here you can create and manage keys, as well as find your issuer ID. When using a key, you'll need the key ID and issuer ID as well. 31 | 32 | ### Obtaining Apple Root Certificates 33 | 34 | Download and store the root certificates found in the Apple Root Certificates section of the [Apple PKI](https://www.apple.com/certificateauthority/) site. Provide these certificates as an array to a SignedDataVerifier to allow verifying the signed data comes from Apple. 35 | 36 | ## Usage 37 | 38 | ### API Usage 39 | 40 | ```swift 41 | import AppStoreServerLibrary 42 | 43 | let issuerId = "99b16628-15e4-4668-972b-eeff55eeff55" 44 | let keyId = "ABCDEFGHIJ" 45 | let bundleId = "com.example" 46 | let encodedKey = try! String(contentsOfFile: "/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8") 47 | let environment = AppStoreEnvironment.sandbox 48 | 49 | // try! used for example purposes only 50 | let client = try! AppStoreServerAPIClient(signingKey: encodedKey, keyId: keyId, issuerId: issuerId, bundleId: bundleId, environment: environment) 51 | 52 | let response = await client.requestTestNotification() 53 | switch response { 54 | case .success(let response): 55 | print(response.testNotificationToken) 56 | case .failure(let errorCode, let rawApiError, let apiError, let errorMessage, let causedBy): 57 | print(errorCode) 58 | print(rawApiError) 59 | print(apiError) 60 | print(errorMessage) 61 | print(causedBy) 62 | } 63 | ``` 64 | 65 | ### Verification Usage 66 | 67 | ```swift 68 | import AppStoreServerLibrary 69 | 70 | let bundleId = "com.example" 71 | let appleRootCAs = loadRootCAs() // Specific implementation may vary 72 | let appAppleId: Int64? = nil // appAppleId must be provided for the Production environment 73 | let enableOnlineChecks = true 74 | let environment = AppStoreEnvironment.sandbox 75 | 76 | // try! used for example purposes only 77 | let verifier = try! SignedDataVerifier(rootCertificates: appleRootCAs, bundleId: bundleId, appAppleId: appAppleId, environment: environment, enableOnlineChecks: enableOnlineChecks) 78 | 79 | let notificationPayload = "ey..." 80 | let notificationResult = await verifier.verifyAndDecodeNotification(signedPayload: notificationPayload) 81 | switch notificationResult { 82 | case .valid(let decodedNotificaiton): 83 | ... 84 | case .invalid(let error): 85 | ... 86 | } 87 | ``` 88 | 89 | ### Receipt Usage 90 | 91 | ```swift 92 | import AppStoreServerLibrary 93 | 94 | let issuerId = "99b16628-15e4-4668-972b-eeff55eeff55" 95 | let keyId = "ABCDEFGHIJ" 96 | let bundleId = "com.example" 97 | let encodedKey = try! String(contentsOfFile: "/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8") 98 | let environment = AppStoreEnvironment.sandbox 99 | 100 | // try! used for example purposes only 101 | let client = try! AppStoreServerAPIClient(signingKey: encodedKey, keyId: keyId, issuerId: issuerId, bundleId: bundleId, environment: environment) 102 | 103 | let appReceipt = "MI..." 104 | let transactionIdOptional = ReceiptUtility.extractTransactionId(appReceipt: appReceipt) 105 | if let transactionId = transactionIdOptional { 106 | var transactionHistoryRequest = TransactionHistoryRequest() 107 | transactionHistoryRequest.sort = TransactionHistoryRequest.Order.ascending 108 | transactionHistoryRequest.revoked = false 109 | transactionHistoryRequest.productTypes = [TransactionHistoryRequest.ProductType.autoRenewable] 110 | 111 | var response: HistoryResponse? 112 | var transactions: [String] = [] 113 | repeat { 114 | let revisionToken = response?.revision 115 | let apiResponse = await client.getTransactionHistory(transactionId: transactionId, revision: revisionToken, transactionHistoryRequest: transactionHistoryRequest, version: .v2) 116 | switch apiResponse { 117 | case .success(let successfulResponse): 118 | response = successfulResponse 119 | case .failure: 120 | // Handle Failure 121 | throw 122 | } 123 | if let signedTransactions = response?.signedTransactions { 124 | transactions.append(contentsOf: signedTransactions) 125 | } 126 | } while (response?.hasMore ?? false) 127 | print(transactions) 128 | } 129 | ``` 130 | 131 | ### Promotional Offer Signature Creation 132 | 133 | ```swift 134 | import AppStoreServerLibrary 135 | 136 | let keyId = "ABCDEFGHIJ" 137 | let bundleId = "com.example" 138 | let encodedKey = try! String(contentsOfFile: "/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8") 139 | 140 | let productId = "" 141 | let subscriptionOfferId = "" 142 | let appAccountToken = "" 143 | 144 | // try! used for example purposes only 145 | let signatureCreator = try! PromotionalOfferSignatureCreator(privateKey: encodedKey, keyId: keyId, bundleId: bundleId) 146 | 147 | let nonce = UUID() 148 | let timestamp = Int64(Date().timeIntervalSince1970) * 1000 149 | let signature = signatureCreator.createSignature(productIdentifier: productIdentifier, subscriptionOfferID: subscriptionOfferID, appAccountToken: appAccountToken, nonce: nonce, timestamp: timestamp) 150 | print(signature) 151 | ``` 152 | 153 | ## Support 154 | 155 | Only the latest major version of the library will receive updates, including security updates. Therefore, it is recommended to update to new major versions. 156 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/ChainVerifier.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | import X509 5 | import SwiftASN1 6 | import JWTKit 7 | import Crypto 8 | import AsyncHTTPClient 9 | import NIOFoundationCompat 10 | 11 | class ChainVerifier { 12 | 13 | private static let EXPECTED_CHAIN_LENGTH = 3 14 | private static let EXPECTED_JWT_SEGMENTS = 3 15 | private static let EXPECTED_ALGORITHM = "ES256" 16 | 17 | private static let MAXIMUM_CACHE_SIZE = 32 // There are unlikely to be more than a couple keys at once 18 | private static let CACHE_TIME_LIMIT: Int64 = 15 * 60 // 15 minutes in seconds 19 | 20 | private let store: CertificateStore 21 | private let requester: Requester 22 | private var verifiedPublicKeyCache: [CacheKey: CacheValue] 23 | 24 | init(rootCertificates: [Data]) throws { 25 | let parsedCertificates = try rootCertificates.map { try Certificate(derEncoded: [UInt8]($0)) } 26 | self.store = CertificateStore(parsedCertificates) 27 | self.requester = Requester() 28 | self.verifiedPublicKeyCache = [:] 29 | } 30 | 31 | func verify(signedData: String, type: T.Type, onlineVerification: Bool, environment: AppStoreEnvironment) async -> VerificationResult where T: Decodable { 32 | let header: JWTHeader; 33 | let decodedBody: T; 34 | do { 35 | let bodySegments = signedData.components(separatedBy: ".") 36 | if (bodySegments.count != ChainVerifier.EXPECTED_JWT_SEGMENTS) { 37 | return VerificationResult.invalid(VerificationError.INVALID_JWT_FORMAT) 38 | } 39 | let jsonDecoder = getJsonDecoder() 40 | guard let headerData = Data(base64Encoded: base64URLToBase64(bodySegments[0])), let bodyData = Data(base64Encoded: base64URLToBase64(bodySegments[1])) else { 41 | return VerificationResult.invalid(VerificationError.INVALID_JWT_FORMAT) 42 | } 43 | header = try jsonDecoder.decode(JWTHeader.self, from: headerData) 44 | decodedBody = try jsonDecoder.decode(type, from: bodyData) 45 | } catch { 46 | return VerificationResult.invalid(VerificationError.INVALID_JWT_FORMAT) 47 | } 48 | 49 | if (environment == AppStoreEnvironment.xcode || environment == AppStoreEnvironment.localTesting) { 50 | // Data is not signed by the App Store, and verification should be skipped 51 | // The environment MUST be checked in the public method calling this 52 | return VerificationResult.valid(decodedBody) 53 | } 54 | 55 | guard let x5c_header = header.x5c else { 56 | return VerificationResult.invalid(VerificationError.INVALID_JWT_FORMAT) 57 | } 58 | if ChainVerifier.EXPECTED_ALGORITHM != header.alg || x5c_header.count != ChainVerifier.EXPECTED_CHAIN_LENGTH { 59 | return VerificationResult.invalid(VerificationError.INVALID_JWT_FORMAT) 60 | } 61 | 62 | 63 | guard let leaf_der_enocded = Data(base64Encoded: x5c_header[0]), 64 | let intermeidate_der_encoded = Data(base64Encoded: x5c_header[1]) else { 65 | return VerificationResult.invalid(VerificationError.INVALID_CERTIFICATE) 66 | } 67 | do { 68 | let leafCertificate = try Certificate(derEncoded: Array(leaf_der_enocded)) 69 | let intermediateCertificate = try Certificate(derEncoded: Array(intermeidate_der_encoded)) 70 | let validationTime = !onlineVerification && decodedBody.signedDate != nil ? decodedBody.signedDate! : getDate() 71 | 72 | let verificationResult = await verifyChain(leaf: leafCertificate, intermediate: intermediateCertificate, online: onlineVerification, validationTime: validationTime) 73 | switch verificationResult { 74 | case .validCertificate(let chain): 75 | let leafCertificate = chain.first! 76 | guard let publicKey = P256.Signing.PublicKey(leafCertificate.publicKey) else { 77 | return VerificationResult.invalid(VerificationError.VERIFICATION_FAILURE) 78 | } 79 | // Verify using Vapor 80 | let keys = JWTKeyCollection() 81 | await keys.add(ecdsa: try ECDSA.PublicKey(backing: publicKey)) 82 | let verifiedBody: VerificationResult = try await VerificationResult.valid(keys.verify(signedData)) 83 | switch verifiedBody { 84 | case .invalid(_): 85 | return VerificationResult.invalid(VerificationError.VERIFICATION_FAILURE) 86 | case .valid(_): break 87 | } 88 | return VerificationResult.valid(decodedBody) 89 | case .couldNotValidate(_): 90 | return VerificationResult.invalid(VerificationError.VERIFICATION_FAILURE) 91 | } 92 | } catch { 93 | return VerificationResult.invalid(VerificationError.INVALID_JWT_FORMAT) 94 | } 95 | } 96 | 97 | func verifyChain(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date) async -> X509.VerificationResult { 98 | if online { 99 | if let cachedResult = verifiedPublicKeyCache[CacheKey(leaf: leaf, intermediate: intermediate)] { 100 | if cachedResult.expirationTime > getDate() { 101 | return cachedResult.publicKey 102 | } 103 | } 104 | } 105 | let verificationResult = await verifyChainWithoutCaching(leaf: leaf, intermediate: intermediate, online: online, validationTime: validationTime) 106 | 107 | if online { 108 | if case let .validCertificate(verifiedChain) = verificationResult { 109 | verifiedPublicKeyCache[CacheKey(leaf: leaf, intermediate: intermediate)] = CacheValue(expirationTime: getDate().addingTimeInterval(TimeInterval(integerLiteral: ChainVerifier.CACHE_TIME_LIMIT)), publicKey: verificationResult) 110 | if verifiedPublicKeyCache.count > ChainVerifier.MAXIMUM_CACHE_SIZE { 111 | for kv in verifiedPublicKeyCache { 112 | if kv.value.expirationTime < getDate() { 113 | verifiedPublicKeyCache.removeValue(forKey: kv.key) 114 | } 115 | } 116 | } 117 | } 118 | } 119 | 120 | return verificationResult 121 | } 122 | 123 | func verifyChainWithoutCaching(leaf: Certificate, intermediate: Certificate, online: Bool, validationTime: Date) async -> X509.VerificationResult { 124 | var verifier = Verifier(rootCertificates: self.store) { 125 | RFC5280Policy(validationTime: validationTime) 126 | AppStoreOIDPolicy() 127 | if online { 128 | OCSPVerifierPolicy(failureMode: .hard, requester: requester, validationTime: getDate()) 129 | } 130 | } 131 | let intermediateStore = CertificateStore([intermediate]) 132 | return await verifier.validate(leafCertificate: leaf, intermediates: intermediateStore) 133 | } 134 | 135 | func getDate() -> Date { 136 | return Date() 137 | } 138 | } 139 | 140 | struct CacheKey: Hashable { 141 | let leaf: Certificate 142 | let intermediate: Certificate 143 | } 144 | 145 | struct CacheValue { 146 | let expirationTime: Date 147 | let publicKey: X509.VerificationResult 148 | } 149 | 150 | struct VaporBody : JWTPayload { 151 | func verify(using algorithm: some JWTAlgorithm) async throws { 152 | // No-op 153 | } 154 | } 155 | 156 | struct JWTHeader: Decodable, Encodable { 157 | public var alg: String? 158 | public var x5c: [String]? 159 | } 160 | 161 | final class AppStoreOIDPolicy: VerifierPolicy { 162 | 163 | private static let NUMBER_OF_CERTS = 3 164 | private static let WWDR_INTERMEDIATE_OID: ASN1ObjectIdentifier = [1, 2, 840, 113635, 100, 6, 2, 1] 165 | private static let RECEIPT_SIGNER_OID: ASN1ObjectIdentifier = [1, 2, 840, 113635, 100, 6, 11, 1] 166 | 167 | init() { 168 | verifyingCriticalExtensions = [] 169 | } 170 | var verifyingCriticalExtensions: [SwiftASN1.ASN1ObjectIdentifier] 171 | 172 | func chainMeetsPolicyRequirements(chain: X509.UnverifiedCertificateChain) async -> X509.PolicyEvaluationResult { 173 | if (chain.count != AppStoreOIDPolicy.NUMBER_OF_CERTS) { 174 | return X509.PolicyEvaluationResult.failsToMeetPolicy(reason: "Chain has unexpected length") 175 | } 176 | let intermediateCertificate = chain[1] 177 | let leafCertificate = chain[0] 178 | if (!intermediateCertificate.extensions.contains(where: { ext in 179 | ext.oid == AppStoreOIDPolicy.WWDR_INTERMEDIATE_OID 180 | })) { 181 | return X509.PolicyEvaluationResult.failsToMeetPolicy(reason: "Intermediate certificate does not contain WWDR OID") 182 | } 183 | if (!leafCertificate.extensions.contains(where: { ext in 184 | ext.oid == AppStoreOIDPolicy.RECEIPT_SIGNER_OID 185 | })) { 186 | return X509.PolicyEvaluationResult.failsToMeetPolicy(reason: "Leaf certificate does not contain Receipt Signing OID") 187 | } 188 | return X509.PolicyEvaluationResult.meetsPolicy 189 | } 190 | } 191 | 192 | final class Requester: OCSPRequester { 193 | 194 | private let client: HTTPClient 195 | 196 | init() { 197 | self.client = .init() 198 | } 199 | 200 | func query(request: [UInt8], uri: String) async -> X509.OCSPRequesterQueryResult { 201 | do { 202 | var urlRequest = HTTPClientRequest(url: uri) 203 | urlRequest.method = .POST 204 | urlRequest.headers.add(name: "Content-Type", value: "application/ocsp-request") 205 | urlRequest.body = .bytes(request) 206 | let response = try await client.execute(urlRequest, timeout: .seconds(30)) 207 | var body = try await response.body.collect(upTo: 1024 * 1024) 208 | guard let data = body.readData(length: body.readableBytes) else { 209 | throw OCSPFetchError() 210 | } 211 | return .response([UInt8](data)) 212 | } catch { 213 | return .terminalError(error) 214 | } 215 | } 216 | 217 | deinit { 218 | try? self.client.syncShutdown() 219 | } 220 | 221 | private struct OCSPFetchError: Error {} 222 | } 223 | 224 | public enum VerificationResult { 225 | case valid(T) 226 | case invalid(VerificationError) 227 | } 228 | 229 | public enum VerificationError: Hashable, Sendable { 230 | case INVALID_JWT_FORMAT 231 | case INVALID_CERTIFICATE 232 | case VERIFICATION_FAILURE 233 | case INVALID_APP_IDENTIFIER 234 | case INVALID_ENVIRONMENT 235 | } 236 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/JWSSignatureCreator.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | import JWTKit 5 | import Crypto 6 | 7 | fileprivate protocol BasePayload: Codable { 8 | var nonce: String { get } 9 | var iss: IssuerClaim { get } 10 | var bid: String { get } 11 | var aud: AudienceClaim { get } 12 | var iat: IssuedAtClaim { get } 13 | } 14 | 15 | fileprivate class BasePayloadObject: BasePayload { 16 | let nonce: String 17 | let iss: IssuerClaim 18 | let bid: String 19 | let aud: AudienceClaim 20 | let iat: IssuedAtClaim 21 | init(nonce: String, iss: IssuerClaim, bid: String, aud: AudienceClaim, iat: IssuedAtClaim) { 22 | self.nonce = nonce 23 | self.iss = iss 24 | self.bid = bid 25 | self.aud = aud 26 | self.iat = iat 27 | } 28 | } 29 | 30 | fileprivate final class PromotionalOfferV2Payload: BasePayload, JWTPayload { 31 | 32 | let nonce: String 33 | let iss: IssuerClaim 34 | let bid: String 35 | let aud: AudienceClaim 36 | let iat: IssuedAtClaim 37 | let productId: String 38 | let offerIdentifier: String 39 | let transactionId: String? 40 | 41 | init(basePayload: BasePayload, productId: String, offerIdentifier: String, transactionId: String? = nil) { 42 | self.productId = productId 43 | self.offerIdentifier = offerIdentifier 44 | self.transactionId = transactionId 45 | self.nonce = basePayload.nonce 46 | self.iss = basePayload.iss 47 | self.bid = basePayload.bid 48 | self.aud = basePayload.aud 49 | self.iat = basePayload.iat 50 | } 51 | 52 | required init(from decoder: any Decoder) throws { 53 | fatalError("Do not attempt to decode a JWS locally") 54 | } 55 | 56 | func verify(using algorithm: some JWTKit.JWTAlgorithm) async throws { 57 | fatalError("Do not attempt to locally verify a JWS") 58 | } 59 | } 60 | 61 | fileprivate final class IntroductoryOfferEligibilityPayload: BasePayload, JWTPayload { 62 | let nonce: String 63 | let iss: IssuerClaim 64 | let bid: String 65 | let aud: AudienceClaim 66 | let iat: IssuedAtClaim 67 | let productId: String 68 | let allowIntroductoryOffer: Bool 69 | let transactionId: String 70 | 71 | init(basePayload: BasePayload, productId: String, allowIntroductoryOffer: Bool, transactionId: String) { 72 | self.productId = productId 73 | self.allowIntroductoryOffer = allowIntroductoryOffer 74 | self.transactionId = transactionId 75 | self.nonce = basePayload.nonce 76 | self.iss = basePayload.iss 77 | self.bid = basePayload.bid 78 | self.aud = basePayload.aud 79 | self.iat = basePayload.iat 80 | } 81 | 82 | required init(from decoder: any Decoder) throws { 83 | fatalError("Do not attempt to decode a JWS locally") 84 | } 85 | 86 | func verify(using algorithm: some JWTKit.JWTAlgorithm) async throws { 87 | fatalError("Do not attempt to locally verify a JWS") 88 | } 89 | } 90 | 91 | fileprivate final class AdvancedCommerceInAppPayload: BasePayload, JWTPayload { 92 | let nonce: String 93 | let iss: IssuerClaim 94 | let bid: String 95 | let aud: AudienceClaim 96 | let iat: IssuedAtClaim 97 | let request: String 98 | 99 | init(basePayload: BasePayload, request: String) { 100 | self.request = request 101 | self.nonce = basePayload.nonce 102 | self.iss = basePayload.iss 103 | self.bid = basePayload.bid 104 | self.aud = basePayload.aud 105 | self.iat = basePayload.iat 106 | } 107 | 108 | required init(from decoder: any Decoder) throws { 109 | fatalError("Do not attempt to decode a JWS locally") 110 | } 111 | 112 | func verify(using algorithm: some JWTKit.JWTAlgorithm) async throws { 113 | fatalError("Do not attempt to locally verify a JWS") 114 | } 115 | } 116 | 117 | public class JWSSignatureCreator { 118 | 119 | private let audience: String 120 | private let signingKey: P256.Signing.PrivateKey 121 | private let keyId: String 122 | private let issuerId: String 123 | private let bundleId: String 124 | 125 | init(audience: String, signingKey: String, keyId: String, issuerId: String, bundleId: String) throws { 126 | self.audience = audience 127 | self.signingKey = try P256.Signing.PrivateKey(pemRepresentation: signingKey) 128 | self.keyId = keyId 129 | self.issuerId = issuerId 130 | self.bundleId = bundleId 131 | } 132 | 133 | fileprivate func getBasePayload() -> BasePayload { 134 | return BasePayloadObject( 135 | nonce: UUID().uuidString, 136 | iss: .init(value: self.issuerId), 137 | bid: self.bundleId, 138 | aud: .init(value: self.audience), 139 | iat: .init(value: Date()) 140 | ) 141 | } 142 | 143 | fileprivate func createSignature(payload: JWTPayload) async throws -> String { 144 | let keys = JWTKeyCollection() 145 | try await keys.add(ecdsa: ECDSA.PrivateKey(backing: signingKey)) 146 | return try await keys.sign(payload, header: ["typ": "JWT", "kid": .string(self.keyId)]) 147 | } 148 | } 149 | 150 | public class PromotionalOfferV2SignatureCreator: JWSSignatureCreator { 151 | ///Create a PromotionalOfferV2SignatureCreator 152 | /// 153 | ///- Parameter signingKey: Your private key downloaded from App Store Connect 154 | ///- Parameter issuerId: Your issuer ID from the Keys page in App Store Connect 155 | ///- Parameter bundleId: Your app’s bundle ID 156 | ///- Parameter environment: The environment to target 157 | public init(signingKey: String, keyId: String, issuerId: String, bundleId: String) throws { 158 | try super.init(audience: "promotional-offer", signingKey: signingKey, keyId: keyId, issuerId: issuerId, bundleId: bundleId) 159 | } 160 | 161 | ///Create a promotional offer V2 signature. 162 | /// 163 | ///- Parameter productId: The unique identifier of the product 164 | ///- Parameter offerIdentifier: The promotional offer identifier that you set up in App Store Connect 165 | ///- Parameter transactionId: The unique identifier of any transaction that belongs to the customer. You can use the customer's appTransactionId, even for customers who haven't made any In-App Purchases in your app. This field is optional, but recommended. 166 | ///- Returns: The signed JWS. 167 | ///[Generating JWS to sign App Store requests](https://developer.apple.com/documentation/storekit/generating-jws-to-sign-app-store-requests) 168 | public func createSignature(productId: String, offerIdentifier: String, transactionId: String? = nil) async throws -> String { 169 | let baseClaims = super.getBasePayload() 170 | let claims = PromotionalOfferV2Payload(basePayload: baseClaims, productId: productId, offerIdentifier: offerIdentifier, transactionId: transactionId) 171 | return try await super.createSignature(payload: claims) 172 | } 173 | } 174 | 175 | public class IntroductoryOfferEligibilitySignatureCreator: JWSSignatureCreator { 176 | ///Create a IntroductoryOfferEligibilitySignatureCreator 177 | /// 178 | ///- Parameter signingKey: Your private key downloaded from App Store Connect 179 | ///- Parameter issuerId: Your issuer ID from the Keys page in App Store Connect 180 | ///- Parameter bundleId: Your app’s bundle ID 181 | ///- Parameter environment: The environment to target 182 | public init(signingKey: String, keyId: String, issuerId: String, bundleId: String) throws { 183 | try super.init(audience: "introductory-offer-eligibility", signingKey: signingKey, keyId: keyId, issuerId: issuerId, bundleId: bundleId) 184 | } 185 | 186 | ///Create an introductory offer eligibility signature. 187 | /// 188 | ///- Parameter productId: The unique identifier of the product 189 | ///- Parameter allowIntroductoryOffer: A boolean value that determines whether the customer is eligible for an introductory offer 190 | ///- Parameter transactionId: The unique identifier of any transaction that belongs to the customer. You can use the customer's appTransactionId, even for customers who haven't made any In-App Purchases in your app. 191 | ///- Returns: The signed JWS. 192 | ///[Generating JWS to sign App Store requests](https://developer.apple.com/documentation/storekit/generating-jws-to-sign-app-store-requests) 193 | public func createSignature(productId: String, allowIntroductoryOffer: Bool, transactionId: String) async throws -> String { 194 | let baseClaims = super.getBasePayload() 195 | let claims = IntroductoryOfferEligibilityPayload(basePayload: baseClaims, productId: productId, allowIntroductoryOffer: allowIntroductoryOffer, transactionId: transactionId) 196 | return try await super.createSignature(payload: claims) 197 | } 198 | } 199 | 200 | public protocol AdvancedCommerceInAppRequest: Encodable { 201 | 202 | } 203 | 204 | public class AdvancedCommerceInAppSignatureCreator: JWSSignatureCreator { 205 | ///Create a AdvancedCommerceInAppSignatureCreator 206 | /// 207 | ///- Parameter signingKey: Your private key downloaded from App Store Connect 208 | ///- Parameter issuerId: Your issuer ID from the Keys page in App Store Connect 209 | ///- Parameter bundleId: Your app’s bundle ID 210 | ///- Parameter environment: The environment to target 211 | public init(signingKey: String, keyId: String, issuerId: String, bundleId: String) throws { 212 | try super.init(audience: "advanced-commerce-api", signingKey: signingKey, keyId: keyId, issuerId: issuerId, bundleId: bundleId) 213 | } 214 | 215 | ///Create an Advanced Commerce in-app signed request. 216 | /// 217 | ///- Parameter advancedCommerceInAppRequest: The request to be signed. 218 | ///- Returns: The signed JWS. 219 | ///[Generating JWS to sign App Store requests](https://developer.apple.com/documentation/storekit/generating-jws-to-sign-app-store-requests) 220 | public func createSignature(advancedCommerceInAppRequest: AdvancedCommerceInAppRequest) async throws -> String { 221 | let jsonEncoder = getJsonEncoder() 222 | let body = try jsonEncoder.encode(advancedCommerceInAppRequest) 223 | 224 | let base64EncodedBody = body.base64EncodedString() 225 | let baseClaims = super.getBasePayload() 226 | let claims = AdvancedCommerceInAppPayload(basePayload: baseClaims, request: base64EncodedBody) 227 | return try await super.createSignature(payload: claims) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/AccountTenure.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The age of the customer’s account. 4 | /// 5 | ///[accountTenure](https://developer.apple.com/documentation/appstoreserverapi/accounttenure) 6 | public enum AccountTenure: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case zeroToThreeDays = 1 9 | case threeDaysToTenDays = 2 10 | case tenDaysToThirtyDays = 3 11 | case thirtyDaysToNinetyDays = 4 12 | case ninetyDaysToOneHundredEightyDays = 5 13 | case oneHundredEightyDaysToThreeHundredSixtyFiveDays = 6 14 | case greaterThanThreeHundredSixtyFiveDays = 7 15 | } 16 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/AppStoreEnvironment.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The server environment, either sandbox or production. 4 | /// 5 | ///[environment](https://developer.apple.com/documentation/appstoreserverapi/environment) 6 | public enum AppStoreEnvironment: String, Decodable, Encodable, Hashable, Sendable { 7 | case sandbox = "Sandbox" 8 | case production = "Production" 9 | case xcode = "Xcode" 10 | case localTesting = "LocalTesting" // Used for unit testing 11 | } 12 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/AppTransaction.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | 5 | 6 | ///Information that represents the customer’s purchase of the app, cryptographically signed by the App Store. 7 | /// 8 | ///[AppTransaction](https://developer.apple.com/documentation/storekit/apptransaction) 9 | public struct AppTransaction: DecodedSignedData, Decodable, Encodable, Hashable, Sendable { 10 | 11 | public init(receiptType: AppStoreEnvironment? = nil, appAppleId: Int64? = nil, bundleId: String? = nil, applicationVersion: String? = nil, versionExternalIdentifier: Int64? = nil, receiptCreationDate: Date? = nil, originalPurchaseDate: Date? = nil, originalApplicationVersion: String? = nil, deviceVerification: String? = nil, deviceVerificationNonce: UUID? = nil, preorderDate: Date? = nil, appTransactionId: String? = nil, originalPlatform: PurchasePlatform? = nil) { 12 | self.receiptType = receiptType 13 | self.appAppleId = appAppleId 14 | self.bundleId = bundleId 15 | self.applicationVersion = applicationVersion 16 | self.versionExternalIdentifier = versionExternalIdentifier 17 | self.receiptCreationDate = receiptCreationDate 18 | self.originalPurchaseDate = originalPurchaseDate 19 | self.originalApplicationVersion = originalApplicationVersion 20 | self.deviceVerification = deviceVerification 21 | self.deviceVerificationNonce = deviceVerificationNonce 22 | self.preorderDate = preorderDate 23 | self.appTransactionId = appTransactionId 24 | self.originalPlatform = originalPlatform 25 | } 26 | 27 | public init(rawReceiptType: String? = nil, appAppleId: Int64? = nil, bundleId: String? = nil, applicationVersion: String? = nil, versionExternalIdentifier: Int64? = nil, receiptCreationDate: Date? = nil, originalPurchaseDate: Date? = nil, originalApplicationVersion: String? = nil, deviceVerification: String? = nil, deviceVerificationNonce: UUID? = nil, preorderDate: Date? = nil, appTransactionId: String? = nil, rawOriginalPlatform: String? = nil) { 28 | self.rawReceiptType = rawReceiptType 29 | self.appAppleId = appAppleId 30 | self.bundleId = bundleId 31 | self.applicationVersion = applicationVersion 32 | self.versionExternalIdentifier = versionExternalIdentifier 33 | self.receiptCreationDate = receiptCreationDate 34 | self.originalPurchaseDate = originalPurchaseDate 35 | self.originalApplicationVersion = originalApplicationVersion 36 | self.deviceVerification = deviceVerification 37 | self.deviceVerificationNonce = deviceVerificationNonce 38 | self.preorderDate = preorderDate 39 | self.appTransactionId = appTransactionId 40 | self.rawOriginalPlatform = rawOriginalPlatform 41 | } 42 | 43 | ///The server environment that signs the app transaction. 44 | /// 45 | ///[environment](https://developer.apple.com/documentation/storekit/apptransaction/3963901-environment) 46 | public var receiptType: AppStoreEnvironment? { 47 | get { 48 | return rawReceiptType.flatMap { AppStoreEnvironment(rawValue: $0) } 49 | } 50 | set { 51 | self.rawReceiptType = newValue.map { $0.rawValue } 52 | } 53 | } 54 | 55 | ///See ``receiptType`` 56 | public var rawReceiptType: String? 57 | 58 | ///The unique identifier the App Store uses to identify the app. 59 | /// 60 | ///[appId](https://developer.apple.com/documentation/storekit/apptransaction/3954436-appid) 61 | public var appAppleId: Int64? 62 | 63 | ///The bundle identifier that the app transaction applies to. 64 | /// 65 | ///[bundleId](https://developer.apple.com/documentation/storekit/apptransaction/3954439-bundleid) 66 | public var bundleId: String? 67 | 68 | ///The app version that the app transaction applies to. 69 | /// 70 | ///[appVersion](https://developer.apple.com/documentation/storekit/apptransaction/3954437-appversion) 71 | public var applicationVersion: String? 72 | 73 | ///The version external identifier of the app 74 | /// 75 | ///[appVersionID](https://developer.apple.com/documentation/storekit/apptransaction/3954438-appversionid) 76 | public var versionExternalIdentifier: Int64? 77 | 78 | ///The date that the App Store signed the JWS app transaction. 79 | /// 80 | ///[signedDate](https://developer.apple.com/documentation/storekit/apptransaction/3954449-signeddate) 81 | public var receiptCreationDate: Date? 82 | 83 | ///The date the user originally purchased the app from the App Store. 84 | /// 85 | ///[originalPurchaseDate](https://developer.apple.com/documentation/storekit/apptransaction/3954448-originalpurchasedate) 86 | public var originalPurchaseDate: Date? 87 | 88 | ///The app version that the user originally purchased from the App Store. 89 | /// 90 | ///[originalAppVersion](https://developer.apple.com/documentation/storekit/apptransaction/3954447-originalappversion) 91 | public var originalApplicationVersion: String? 92 | 93 | ///The Base64 device verification value to use to verify whether the app transaction belongs to the device. 94 | /// 95 | ///[deviceVerification](https://developer.apple.com/documentation/storekit/apptransaction/3954441-deviceverification) 96 | public var deviceVerification: String? 97 | 98 | ///The UUID used to compute the device verification value. 99 | /// 100 | ///[deviceVerificationNonce](https://developer.apple.com/documentation/storekit/apptransaction/3954442-deviceverificationnonce) 101 | public var deviceVerificationNonce: UUID? 102 | 103 | ///The date the customer placed an order for the app before it’s available in the App Store. 104 | /// 105 | ///[preorderDate](https://developer.apple.com/documentation/storekit/apptransaction/4013175-preorderdate) 106 | public var preorderDate: Date? 107 | 108 | 109 | ///The date that the App Store signed the JWS app transaction. 110 | /// 111 | ///[signedDate](https://developer.apple.com/documentation/storekit/apptransaction/3954449-signeddate) 112 | public var signedDate: Date? { 113 | receiptCreationDate 114 | } 115 | 116 | ///The unique identifier of the app download transaction. 117 | /// 118 | ///[appTransactionId](https://developer.apple.com/documentation/storekit/apptransaction/apptransactionid) 119 | public var appTransactionId: String? 120 | 121 | ///The platform on which the customer originally purchased the app. 122 | /// 123 | ///[originalPlatform](https://developer.apple.com/documentation/storekit/apptransaction/originalplatform-4mogz) 124 | public var originalPlatform: PurchasePlatform? { 125 | get { 126 | return rawOriginalPlatform.flatMap { PurchasePlatform(rawValue: $0) } 127 | } 128 | set { 129 | self.rawOriginalPlatform = newValue.map { $0.rawValue } 130 | } 131 | } 132 | 133 | ///See ``originalPlatform`` 134 | public var rawOriginalPlatform: String? 135 | 136 | 137 | public enum CodingKeys: CodingKey { 138 | case receiptType 139 | case appAppleId 140 | case bundleId 141 | case applicationVersion 142 | case versionExternalIdentifier 143 | case receiptCreationDate 144 | case originalPurchaseDate 145 | case originalApplicationVersion 146 | case deviceVerification 147 | case deviceVerificationNonce 148 | case preorderDate 149 | case appTransactionId 150 | case originalPlatform 151 | } 152 | 153 | public init(from decoder: any Decoder) throws { 154 | let container = try decoder.container(keyedBy: CodingKeys.self) 155 | self.rawReceiptType = try container.decodeIfPresent(String.self, forKey: .receiptType) 156 | self.appAppleId = try container.decodeIfPresent(Int64.self, forKey: .appAppleId) 157 | self.bundleId = try container.decodeIfPresent(String.self, forKey: .bundleId) 158 | self.applicationVersion = try container.decodeIfPresent(String.self, forKey: .applicationVersion) 159 | self.versionExternalIdentifier = try container.decodeIfPresent(Int64.self, forKey: .versionExternalIdentifier) 160 | self.receiptCreationDate = try container.decodeIfPresent(Date.self, forKey: .receiptCreationDate) 161 | self.originalPurchaseDate = try container.decodeIfPresent(Date.self, forKey: .originalPurchaseDate) 162 | self.originalApplicationVersion = try container.decodeIfPresent(String.self, forKey: .originalApplicationVersion) 163 | self.deviceVerification = try container.decodeIfPresent(String.self, forKey: .deviceVerification) 164 | self.deviceVerificationNonce = try container.decodeIfPresent(UUID.self, forKey: .deviceVerificationNonce) 165 | self.preorderDate = try container.decodeIfPresent(Date.self, forKey: .preorderDate) 166 | self.appTransactionId = try container.decodeIfPresent(String.self, forKey: .appTransactionId) 167 | self.rawOriginalPlatform = try container.decodeIfPresent(String.self, forKey: .originalPlatform) 168 | } 169 | 170 | public func encode(to encoder: any Encoder) throws { 171 | var container = encoder.container(keyedBy: CodingKeys.self) 172 | try container.encodeIfPresent(self.rawReceiptType, forKey: .receiptType) 173 | try container.encodeIfPresent(self.appAppleId, forKey: .appAppleId) 174 | try container.encodeIfPresent(self.bundleId, forKey: .bundleId) 175 | try container.encodeIfPresent(self.applicationVersion, forKey: .applicationVersion) 176 | try container.encodeIfPresent(self.versionExternalIdentifier, forKey: .versionExternalIdentifier) 177 | try container.encodeIfPresent(self.receiptCreationDate, forKey: .receiptCreationDate) 178 | try container.encodeIfPresent(self.originalPurchaseDate, forKey: .originalPurchaseDate) 179 | try container.encodeIfPresent(self.originalApplicationVersion, forKey: .originalApplicationVersion) 180 | try container.encodeIfPresent(self.deviceVerification, forKey: .deviceVerification) 181 | try container.encodeIfPresent(self.deviceVerificationNonce, forKey: .deviceVerificationNonce) 182 | try container.encodeIfPresent(self.preorderDate, forKey: .preorderDate) 183 | try container.encodeIfPresent(self.appTransactionId, forKey: .appTransactionId) 184 | try container.encodeIfPresent(self.rawOriginalPlatform, forKey: .originalPlatform) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/AutoRenewStatus.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The renewal status for an auto-renewable subscription. 4 | /// 5 | ///[autoRenewStatus](https://developer.apple.com/documentation/appstoreserverapi/autorenewstatus) 6 | public enum AutoRenewStatus: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case off = 0 8 | case on = 1 9 | } 10 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/CheckTestNotificationResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that contains the contents of the test notification sent by the App Store server and the result from your server. 4 | /// 5 | ///[CheckTestNotificationResponse](https://developer.apple.com/documentation/appstoreserverapi/checktestnotificationresponse) 6 | public struct CheckTestNotificationResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(signedPayload: String? = nil, sendAttempts: [SendAttemptItem]? = nil) { 9 | self.signedPayload = signedPayload 10 | self.sendAttempts = sendAttempts 11 | } 12 | 13 | ///A cryptographically signed payload, in JSON Web Signature (JWS) format, containing the response body for a version 2 notification. 14 | /// 15 | ///[signedPayload](https://developer.apple.com/documentation/appstoreservernotifications/signedpayload) 16 | public var signedPayload: String? 17 | 18 | ///An array of information the App Store server records for its attempts to send the TEST notification to your server. The array may contain a maximum of six sendAttemptItem objects. 19 | /// 20 | ///[sendAttemptItem](https://developer.apple.com/documentation/appstoreserverapi/sendattemptitem) 21 | public var sendAttempts: [SendAttemptItem]? 22 | } 23 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ConsumptionRequestReason.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Apple Inc. Licensed under MIT License. 2 | 3 | ///The customer-provided reason for a refund request. 4 | /// 5 | ///[consumptionRequestReason](https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason) 6 | public enum ConsumptionRequestReason: String, Decodable, Encodable, Hashable, Sendable { 7 | case unintendedPurchase = "UNINTENDED_PURCHASE" 8 | case fulfillmentIssue = "FULFILLMENT_ISSUE" 9 | case unsatisfiedWithPurchase = "UNSATISFIED_WITH_PURCHASE" 10 | case legal = "LEGAL" 11 | case other = "OTHER" 12 | } 13 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ConsumptionStatus.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A value that indicates the extent to which the customer consumed the in-app purchase. 4 | /// 5 | ///[consumptionStatus](https://developer.apple.com/documentation/appstoreserverapi/consumptionstatus) 6 | public enum ConsumptionStatus: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case notConsumed = 1 9 | case partiallyConsumed = 2 10 | case fullyConsumed = 3 11 | } 12 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/DecodedSignedData.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | 5 | protocol DecodedSignedData { 6 | var signedDate: Date? { get } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/DeliveryStatus.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A value that indicates whether the app successfully delivered an in-app purchase that works properly. 4 | /// 5 | ///[deliveryStatus](https://developer.apple.com/documentation/appstoreserverapi/deliverystatus) 6 | public enum DeliveryStatus: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case deliveredAndWorkingProperly = 0 8 | case didNotDeliverDueToQualityIssue = 1 9 | case deliveredWrongItem = 2 10 | case didNotDeliverDueToServerOutage = 3 11 | case didNotDeliverDueToIngameCurrencyChange = 4 12 | case didNotDeliverForOtherReason = 5 13 | } 14 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ErrorPayload.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | 4 | public struct ErrorPayload: Decodable, Encodable, Hashable, Sendable { 5 | public var errorCode: Int64? 6 | public var errorMessage: String? 7 | } 8 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ExpirationIntent.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The reason an auto-renewable subscription expired. 4 | /// 5 | ///[expirationIntent](https://developer.apple.com/documentation/appstoreserverapi/expirationintent) 6 | public enum ExpirationIntent: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case customerCancelled = 1 8 | case billingError = 2 9 | case customerDidNotConsentToPriceIncrease = 3 10 | case productNotAvailable = 4 11 | case other = 5 12 | } 13 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ExtendReasonCode.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The code that represents the reason for the subscription-renewal-date extension. 4 | /// 5 | ///[extendReasonCode](https://developer.apple.com/documentation/appstoreserverapi/extendreasoncode) 6 | public enum ExtendReasonCode: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case customerSatisfaction = 1 9 | case other = 2 10 | case serviceIssueOrOutage = 3 11 | } 12 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ExtendRenewalDateRequest.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The request body that contains subscription-renewal-extension data for an individual subscription. 4 | /// 5 | ///[ExtendRenewalDateRequest](https://developer.apple.com/documentation/appstoreserverapi/extendrenewaldaterequest) 6 | public struct ExtendRenewalDateRequest: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(extendByDays: Int32? = nil, extendReasonCode: ExtendReasonCode? = nil, requestIdentifier: String? = nil) { 9 | self.extendByDays = extendByDays 10 | self.extendReasonCode = extendReasonCode 11 | self.requestIdentifier = requestIdentifier 12 | } 13 | 14 | ///The number of days to extend the subscription renewal date. 15 | /// 16 | ///[extendByDays](https://developer.apple.com/documentation/appstoreserverapi/extendbydays) 17 | /// 18 | ///maximum: 90 19 | public var extendByDays: Int32? 20 | 21 | ///The reason code for the subscription date extension 22 | /// 23 | ///[extendReasonCode](https://developer.apple.com/documentation/appstoreserverapi/extendreasoncode) 24 | public var extendReasonCode: ExtendReasonCode? 25 | 26 | ///A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 27 | /// 28 | ///[requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 29 | public var requestIdentifier: String? 30 | } 31 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ExtendRenewalDateResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | ///A response that indicates whether an individual renewal-date extension succeeded, and related details. 5 | /// 6 | ///[ExtendRenewalDateResponse](https://developer.apple.com/documentation/appstoreserverapi/extendrenewaldateresponse) 7 | public struct ExtendRenewalDateResponse: Decodable, Encodable, Hashable, Sendable { 8 | 9 | public init(originalTransactionId: String? = nil, webOrderLineItemId: String? = nil, success: Bool? = nil, effectiveDate: Date? = nil) { 10 | self.originalTransactionId = originalTransactionId 11 | self.webOrderLineItemId = webOrderLineItemId 12 | self.success = success 13 | self.effectiveDate = effectiveDate 14 | } 15 | 16 | ///The original transaction identifier of a purchase. 17 | /// 18 | ///[originalTransactionId](https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid) 19 | public var originalTransactionId: String? 20 | 21 | ///The unique identifier of subscription-purchase events across devices, including renewals. 22 | /// 23 | ///[webOrderLineItemId](https://developer.apple.com/documentation/appstoreserverapi/weborderlineitemid) 24 | public var webOrderLineItemId: String? 25 | 26 | ///A Boolean value that indicates whether the subscription-renewal-date extension succeeded. 27 | /// 28 | ///[success](https://developer.apple.com/documentation/appstoreserverapi/success) 29 | public var success: Bool? 30 | 31 | ///The new subscription expiration date for a subscription-renewal extension. 32 | /// 33 | ///[effectiveDate](https://developer.apple.com/documentation/appstoreserverapi/effectivedate) 34 | public var effectiveDate: Date? 35 | } 36 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ExternalPurchaseToken.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Apple Inc. Licensed under MIT License. 2 | 3 | ///The payload data that contains an external purchase token. 4 | /// 5 | ///[externalPurchaseToken](https://developer.apple.com/documentation/appstoreservernotifications/externalpurchasetoken) 6 | public struct ExternalPurchaseToken: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(externalPurchaseId: String? = nil, tokenCreationDate: Int64? = nil, appAppleId: Int64? = nil, bundleId: String? = nil) { 9 | self.externalPurchaseId = externalPurchaseId 10 | self.tokenCreationDate = tokenCreationDate 11 | self.appAppleId = appAppleId 12 | self.bundleId = bundleId 13 | } 14 | 15 | ///The field of an external purchase token that uniquely identifies the token. 16 | /// 17 | ///[externalPurchaseId](https://developer.apple.com/documentation/appstoreservernotifications/externalpurchaseid) 18 | public var externalPurchaseId: String? 19 | 20 | ///The field of an external purchase token that contains the UNIX date, in milliseconds, when the system created the token. 21 | /// 22 | ///[tokenCreationDate](https://developer.apple.com/documentation/appstoreservernotifications/tokencreationdate) 23 | public var tokenCreationDate: Int64? 24 | 25 | ///The unique identifier of an app in the App Store. 26 | /// 27 | ///[appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 28 | public var appAppleId: Int64? 29 | 30 | ///The bundle identifier of an app. 31 | /// 32 | ///[bundleId](https://developer.apple.com/documentation/appstoreservernotifications/bundleid) 33 | public var bundleId: String? 34 | } 35 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/HistoryResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that contains the customer’s transaction history for an app. 4 | /// 5 | ///[HistoryResponse](https://developer.apple.com/documentation/appstoreserverapi/historyresponse) 6 | public struct HistoryResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(revision: String? = nil, hasMore: Bool? = nil, bundleId: String? = nil, appAppleId: Int64? = nil, environment: AppStoreEnvironment? = nil, signedTransactions: [String]? = nil) { 9 | self.revision = revision 10 | self.hasMore = hasMore 11 | self.bundleId = bundleId 12 | self.appAppleId = appAppleId 13 | self.environment = environment 14 | self.signedTransactions = signedTransactions 15 | } 16 | 17 | public init(revision: String? = nil, hasMore: Bool? = nil, bundleId: String? = nil, appAppleId: Int64? = nil, rawEnvironment: String? = nil, signedTransactions: [String]? = nil) { 18 | self.revision = revision 19 | self.hasMore = hasMore 20 | self.bundleId = bundleId 21 | self.appAppleId = appAppleId 22 | self.rawEnvironment = rawEnvironment 23 | self.signedTransactions = signedTransactions 24 | } 25 | 26 | ///A token you use in a query to request the next set of transactions for the customer. 27 | /// 28 | ///[revision](https://developer.apple.com/documentation/appstoreserverapi/revision) 29 | public var revision: String? 30 | 31 | ///A Boolean value indicating whether the App Store has more transaction data. 32 | /// 33 | ///[hasMore](https://developer.apple.com/documentation/appstoreserverapi/hasmore) 34 | public var hasMore: Bool? 35 | 36 | ///The bundle identifier of an app. 37 | /// 38 | ///[bundleId](https://developer.apple.com/documentation/appstoreserverapi/bundleid) 39 | public var bundleId: String? 40 | 41 | ///The unique identifier of an app in the App Store. 42 | /// 43 | ///[appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 44 | public var appAppleId: Int64? 45 | 46 | ///The server environment in which you’re making the request, whether sandbox or production. 47 | /// 48 | ///[environment](https://developer.apple.com/documentation/appstoreserverapi/environment) 49 | public var environment: AppStoreEnvironment? { 50 | get { 51 | return rawEnvironment.flatMap { AppStoreEnvironment(rawValue: $0) } 52 | } 53 | set { 54 | self.rawEnvironment = newValue.map { $0.rawValue } 55 | } 56 | } 57 | 58 | ///See ``environment`` 59 | public var rawEnvironment: String? 60 | 61 | ///An array of in-app purchase transactions for the customer, signed by Apple, in JSON Web Signature format. 62 | /// 63 | ///[JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 64 | public var signedTransactions: [String]? 65 | 66 | public enum CodingKeys: CodingKey { 67 | case revision 68 | case hasMore 69 | case bundleId 70 | case appAppleId 71 | case environment 72 | case signedTransactions 73 | } 74 | 75 | public init(from decoder: any Decoder) throws { 76 | let container = try decoder.container(keyedBy: CodingKeys.self) 77 | self.revision = try container.decodeIfPresent(String.self, forKey: .revision) 78 | self.hasMore = try container.decodeIfPresent(Bool.self, forKey: .hasMore) 79 | self.bundleId = try container.decodeIfPresent(String.self, forKey: .bundleId) 80 | self.appAppleId = try container.decodeIfPresent(Int64.self, forKey: .appAppleId) 81 | self.rawEnvironment = try container.decodeIfPresent(String.self, forKey: .environment) 82 | self.signedTransactions = try container.decodeIfPresent([String].self, forKey: .signedTransactions) 83 | } 84 | 85 | public func encode(to encoder: any Encoder) throws { 86 | var container = encoder.container(keyedBy: CodingKeys.self) 87 | try container.encodeIfPresent(self.revision, forKey: .revision) 88 | try container.encodeIfPresent(self.hasMore, forKey: .hasMore) 89 | try container.encodeIfPresent(self.bundleId, forKey: .bundleId) 90 | try container.encodeIfPresent(self.appAppleId, forKey: .appAppleId) 91 | try container.encodeIfPresent(self.rawEnvironment, forKey: .environment) 92 | try container.encodeIfPresent(self.signedTransactions, forKey: .signedTransactions) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/InAppOwnershipType.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The relationship of the user with the family-shared purchase to which they have access. 4 | /// 5 | ///[inAppOwnershipType](https://developer.apple.com/documentation/appstoreserverapi/inappownershiptype) 6 | public enum InAppOwnershipType: String, Decodable, Encodable, Hashable, Sendable { 7 | case familyShared = "FAMILY_SHARED" 8 | case purchased = "PURCHASED" 9 | } 10 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/LastTransactionsItem.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The most recent App Store-signed transaction information and App Store-signed renewal information for an auto-renewable subscription. 4 | /// 5 | ///[lastTransactionsItem](https://developer.apple.com/documentation/appstoreserverapi/lasttransactionsitem) 6 | public struct LastTransactionsItem: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(status: Status? = nil, originalTransactionId: String? = nil, signedTransactionInfo: String? = nil, signedRenewalInfo: String? = nil) { 9 | self.status = status 10 | self.originalTransactionId = originalTransactionId 11 | self.signedTransactionInfo = signedTransactionInfo 12 | self.signedRenewalInfo = signedRenewalInfo 13 | } 14 | 15 | public init(rawStatus: Int32? = nil, originalTransactionId: String? = nil, signedTransactionInfo: String? = nil, signedRenewalInfo: String? = nil) { 16 | self.rawStatus = rawStatus 17 | self.originalTransactionId = originalTransactionId 18 | self.signedTransactionInfo = signedTransactionInfo 19 | self.signedRenewalInfo = signedRenewalInfo 20 | } 21 | 22 | ///The status of the auto-renewable subscription. 23 | /// 24 | ///[status](https://developer.apple.com/documentation/appstoreserverapi/status) 25 | public var status: Status? { 26 | get { 27 | return rawStatus.flatMap { Status(rawValue: $0) } 28 | } 29 | set { 30 | self.rawStatus = newValue.map { $0.rawValue } 31 | } 32 | } 33 | 34 | ///See ``status`` 35 | public var rawStatus: Int32? 36 | 37 | ///The original transaction identifier of a purchase. 38 | /// 39 | ///[originalTransactionId](https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid) 40 | public var originalTransactionId: String? 41 | 42 | ///Transaction information signed by the App Store, in JSON Web Signature (JWS) format. 43 | /// 44 | ///[JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 45 | public var signedTransactionInfo: String? 46 | 47 | ///Subscription renewal information, signed by the App Store, in JSON Web Signature (JWS) format. 48 | /// 49 | ///[JWSRenewalInfo](https://developer.apple.com/documentation/appstoreserverapi/jwsrenewalinfo) 50 | public var signedRenewalInfo: String? 51 | 52 | public enum CodingKeys: CodingKey { 53 | case status 54 | case originalTransactionId 55 | case signedTransactionInfo 56 | case signedRenewalInfo 57 | } 58 | 59 | public init(from decoder: any Decoder) throws { 60 | let container = try decoder.container(keyedBy: CodingKeys.self) 61 | self.rawStatus = try container.decodeIfPresent(Int32.self, forKey: .status) 62 | self.originalTransactionId = try container.decodeIfPresent(String.self, forKey: .originalTransactionId) 63 | self.signedTransactionInfo = try container.decodeIfPresent(String.self, forKey: .signedTransactionInfo) 64 | self.signedRenewalInfo = try container.decodeIfPresent(String.self, forKey: .signedRenewalInfo) 65 | } 66 | 67 | public func encode(to encoder: any Encoder) throws { 68 | var container = encoder.container(keyedBy: CodingKeys.self) 69 | try container.encodeIfPresent(self.rawStatus, forKey: .status) 70 | try container.encodeIfPresent(self.originalTransactionId, forKey: .originalTransactionId) 71 | try container.encodeIfPresent(self.signedTransactionInfo, forKey: .signedTransactionInfo) 72 | try container.encodeIfPresent(self.signedRenewalInfo, forKey: .signedRenewalInfo) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/LifetimeDollarsPurchased.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A value that indicates the total amount, in USD, of in-app purchases the customer has made in your app, across all platforms. 4 | /// 5 | ///[lifetimeDollarsPurchased](https://developer.apple.com/documentation/appstoreserverapi/lifetimedollarspurchased) 6 | public enum LifetimeDollarsPurchased: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case zeroDollars = 1 9 | case oneCentToFortyNineDollarsAndNinetyNineCents = 2 10 | case fiftyDollarsToNinetyNineDollarsAndNinetyNineCents = 3 11 | case oneHundredDollarsToFourHundredNinetyNineDollarsAndNinetyNineCents = 4 12 | case fiveHundredDollarsToNineHundredNinetyNineDollarsAndNinetyNineCents = 5 13 | case oneThousandDollarsToOneThousandNineHundredNinetyNineDollarsAndNinetyNineCents = 6 14 | case twoThousandDollarsOrGreater = 7 15 | } 16 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/LifetimeDollarsRefunded.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A value that indicates the dollar amount of refunds the customer has received in your app, since purchasing the app, across all platforms. 4 | /// 5 | ///[lifetimeDollarsRefunded](https://developer.apple.com/documentation/appstoreserverapi/lifetimedollarsrefunded) 6 | public enum LifetimeDollarsRefunded: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case zeroDollars = 1 9 | case oneCentToFortyNineDollarsAndNinetyNineCents = 2 10 | case fiftyDollarsToNinetyNineDollarsAndNinetyNineCents = 3 11 | case oneHundredDollarsToFourHundredNinetyNineDollarsAndNinetyNineCents = 4 12 | case fiveHundredDollarsToNineHundredNinetyNineDollarsAndNinetyNineCents = 5 13 | case oneThousandDollarsToOneThousandNineHundredNinetyNineDollarsAndNinetyNineCents = 6 14 | case twoThousandDollarsOrGreater = 7 15 | } 16 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/MassExtendRenewalDateRequest.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The request body that contains subscription-renewal-extension data to apply for all eligible active subscribers. 4 | /// 5 | ///[MassExtendRenewalDateRequest](https://developer.apple.com/documentation/appstoreserverapi/massextendrenewaldaterequest) 6 | public struct MassExtendRenewalDateRequest: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(extendByDays: Int32? = nil, extendReasonCode: ExtendReasonCode? = nil, requestIdentifier: String? = nil, storefrontCountryCodes: [String]? = nil, productId: String? = nil) { 9 | self.extendByDays = extendByDays 10 | self.extendReasonCode = extendReasonCode 11 | self.requestIdentifier = requestIdentifier 12 | self.storefrontCountryCodes = storefrontCountryCodes 13 | self.productId = productId 14 | } 15 | 16 | ///The number of days to extend the subscription renewal date. 17 | /// 18 | ///[extendByDays](https://developer.apple.com/documentation/appstoreserverapi/extendbydays) 19 | ///maximum: 90 20 | public var extendByDays: Int32? 21 | 22 | ///The reason code for the subscription-renewal-date extension. 23 | /// 24 | ///[extendReasonCode](https://developer.apple.com/documentation/appstoreserverapi/extendreasoncode) 25 | public var extendReasonCode: ExtendReasonCode? 26 | 27 | ///A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 28 | /// 29 | ///[requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 30 | public var requestIdentifier: String? 31 | 32 | ///A list of storefront country codes you provide to limit the storefronts for a subscription-renewal-date extension. 33 | /// 34 | ///[storefrontCountryCodes](https://developer.apple.com/documentation/appstoreserverapi/storefrontcountrycodes) 35 | public var storefrontCountryCodes: [String]? 36 | 37 | ///The unique identifier for the product, that you create in App Store Connect. 38 | /// 39 | ///[productId](https://developer.apple.com/documentation/appstoreserverapi/productid) 40 | public var productId: String? 41 | } 42 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/MassExtendRenewalDateResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that indicates the server successfully received the subscription-renewal-date extension request. 4 | /// 5 | ///[MassExtendRenewalDateResponse](https://developer.apple.com/documentation/appstoreserverapi/massextendrenewaldateresponse) 6 | public struct MassExtendRenewalDateResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(requestIdentifier: String? = nil) { 9 | self.requestIdentifier = requestIdentifier 10 | } 11 | 12 | ///A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 13 | /// 14 | ///[requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 15 | public var requestIdentifier: String? 16 | } 17 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/MassExtendRenewalDateStatusResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | ///A response that indicates the current status of a request to extend the subscription renewal date to all eligible subscribers. 5 | /// 6 | ///[MassExtendRenewalDateStatusResponse](https://developer.apple.com/documentation/appstoreserverapi/massextendrenewaldatestatusresponse) 7 | public struct MassExtendRenewalDateStatusResponse: Decodable, Encodable, Hashable, Sendable { 8 | 9 | public init(requestIdentifier: String? = nil, complete: Bool? = nil, completeDate: Date? = nil, succeededCount: Int64? = nil, failedCount: Int64? = nil) { 10 | self.requestIdentifier = requestIdentifier 11 | self.complete = complete 12 | self.completeDate = completeDate 13 | self.succeededCount = succeededCount 14 | self.failedCount = failedCount 15 | } 16 | 17 | ///A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 18 | /// 19 | ///[requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 20 | public var requestIdentifier: String? 21 | 22 | ///A Boolean value that indicates whether the App Store completed the request to extend a subscription renewal date to active subscribers. 23 | /// 24 | ///[complete](https://developer.apple.com/documentation/appstoreserverapi/complete) 25 | public var complete: Bool? 26 | 27 | ///The UNIX time, in milliseconds, that the App Store completes a request to extend a subscription renewal date for eligible subscribers. 28 | /// 29 | ///[completeDate](https://developer.apple.com/documentation/appstoreserverapi/completedate) 30 | public var completeDate: Date? 31 | 32 | ///The count of subscriptions that successfully receive a subscription-renewal-date extension. 33 | /// 34 | ///[succeededCount](https://developer.apple.com/documentation/appstoreserverapi/succeededcount) 35 | public var succeededCount: Int64? 36 | 37 | ///The count of subscriptions that fail to receive a subscription-renewal-date extension. 38 | /// 39 | ///[failedCount](https://developer.apple.com/documentation/appstoreserverapi/failedcount) 40 | public var failedCount: Int64? 41 | } 42 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/NotificationData.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The app metadata and the signed renewal and transaction information. 4 | /// 5 | ///[data](https://developer.apple.com/documentation/appstoreservernotifications/data) 6 | public struct NotificationData: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(environment: AppStoreEnvironment? = nil, appAppleId: Int64? = nil, bundleId: String? = nil, bundleVersion: String? = nil, signedTransactionInfo: String? = nil, signedRenewalInfo: String? = nil, status: Status? = nil, consumptionRequestReason: ConsumptionRequestReason? = nil) { 9 | self.environment = environment 10 | self.appAppleId = appAppleId 11 | self.bundleId = bundleId 12 | self.bundleVersion = bundleVersion 13 | self.signedTransactionInfo = signedTransactionInfo 14 | self.signedRenewalInfo = signedRenewalInfo 15 | self.status = status 16 | self.consumptionRequestReason = consumptionRequestReason 17 | } 18 | 19 | public init(rawEnvironment: String? = nil, appAppleId: Int64? = nil, bundleId: String? = nil, bundleVersion: String? = nil, signedTransactionInfo: String? = nil, signedRenewalInfo: String? = nil, rawStatus: Int32? = nil, rawConsumptionRequestReason: String? = nil) { 20 | self.rawEnvironment = rawEnvironment 21 | self.appAppleId = appAppleId 22 | self.bundleId = bundleId 23 | self.bundleVersion = bundleVersion 24 | self.signedTransactionInfo = signedTransactionInfo 25 | self.signedRenewalInfo = signedRenewalInfo 26 | self.rawStatus = rawStatus 27 | self.rawConsumptionRequestReason = rawConsumptionRequestReason 28 | } 29 | 30 | ///The server environment that the notification applies to, either sandbox or production. 31 | /// 32 | ///[environment](https://developer.apple.com/documentation/appstoreservernotifications/environment) 33 | public var environment: AppStoreEnvironment? { 34 | get { 35 | return rawEnvironment.flatMap { AppStoreEnvironment(rawValue: $0) } 36 | } 37 | set { 38 | self.rawEnvironment = newValue.map { $0.rawValue } 39 | } 40 | } 41 | 42 | ///See ``environment`` 43 | public var rawEnvironment: String? 44 | 45 | ///The unique identifier of an app in the App Store. 46 | /// 47 | ///[appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 48 | public var appAppleId: Int64? 49 | 50 | ///The bundle identifier of an app. 51 | /// 52 | ///[bundleId](https://developer.apple.com/documentation/appstoreserverapi/bundleid) 53 | public var bundleId: String? 54 | 55 | ///The version of the build that identifies an iteration of the bundle. 56 | /// 57 | ///[bundleVersion](https://developer.apple.com/documentation/appstoreservernotifications/bundleversion) 58 | public var bundleVersion: String? 59 | 60 | ///Transaction information signed by the App Store, in JSON Web Signature (JWS) format. 61 | /// 62 | ///[JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 63 | public var signedTransactionInfo: String? 64 | 65 | ///Subscription renewal information, signed by the App Store, in JSON Web Signature (JWS) format. 66 | /// 67 | ///[JWSRenewalInfo](https://developer.apple.com/documentation/appstoreserverapi/jwsrenewalinfo) 68 | public var signedRenewalInfo: String? 69 | 70 | ///The status of an auto-renewable subscription as of the signedDate in the responseBodyV2DecodedPayload. 71 | /// 72 | ///[status](https://developer.apple.com/documentation/appstoreservernotifications/status) 73 | public var status: Status? { 74 | get { 75 | return rawStatus.flatMap { Status(rawValue: $0) } 76 | } 77 | set { 78 | self.rawStatus = newValue.map { $0.rawValue } 79 | } 80 | } 81 | 82 | ///See ``status`` 83 | public var rawStatus: Int32? 84 | 85 | ///The reason the customer requested the refund. 86 | /// 87 | ///[consumptionRequestReason](https://developer.apple.com/documentation/appstoreservernotifications/consumptionrequestreason) 88 | public var consumptionRequestReason: ConsumptionRequestReason? { 89 | get { 90 | return rawConsumptionRequestReason.flatMap { ConsumptionRequestReason(rawValue: $0) } 91 | } 92 | set { 93 | self.rawConsumptionRequestReason = newValue.map { $0.rawValue } 94 | } 95 | } 96 | 97 | ///See ``consumptionRequestReason`` 98 | public var rawConsumptionRequestReason: String? 99 | 100 | public enum CodingKeys: CodingKey { 101 | case environment 102 | case appAppleId 103 | case bundleId 104 | case bundleVersion 105 | case signedTransactionInfo 106 | case signedRenewalInfo 107 | case status 108 | case consumptionRequestReason 109 | } 110 | 111 | public init(from decoder: any Decoder) throws { 112 | let container = try decoder.container(keyedBy: CodingKeys.self) 113 | self.rawEnvironment = try container.decodeIfPresent(String.self, forKey: .environment) 114 | self.appAppleId = try container.decodeIfPresent(Int64.self, forKey: .appAppleId) 115 | self.bundleId = try container.decodeIfPresent(String.self, forKey: .bundleId) 116 | self.bundleVersion = try container.decodeIfPresent(String.self, forKey: .bundleVersion) 117 | self.signedTransactionInfo = try container.decodeIfPresent(String.self, forKey: .signedTransactionInfo) 118 | self.signedRenewalInfo = try container.decodeIfPresent(String.self, forKey: .signedRenewalInfo) 119 | self.rawStatus = try container.decodeIfPresent(Int32.self, forKey: .status) 120 | self.rawConsumptionRequestReason = try container.decodeIfPresent(String.self, forKey: .consumptionRequestReason) 121 | } 122 | 123 | public func encode(to encoder: any Encoder) throws { 124 | var container = encoder.container(keyedBy: CodingKeys.self) 125 | try container.encodeIfPresent(self.rawEnvironment, forKey: .environment) 126 | try container.encodeIfPresent(self.appAppleId, forKey: .appAppleId) 127 | try container.encodeIfPresent(self.bundleId, forKey: .bundleId) 128 | try container.encodeIfPresent(self.bundleVersion, forKey: .bundleVersion) 129 | try container.encodeIfPresent(self.signedTransactionInfo, forKey: .signedTransactionInfo) 130 | try container.encodeIfPresent(self.signedRenewalInfo, forKey: .signedRenewalInfo) 131 | try container.encodeIfPresent(self.rawStatus, forKey: .status) 132 | try container.encodeIfPresent(self.rawConsumptionRequestReason, forKey: .consumptionRequestReason) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/NotificationHistoryRequest.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | ///The request body for notification history. 5 | /// 6 | ///[NotificationHistoryRequest](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryrequest) 7 | public struct NotificationHistoryRequest: Decodable, Encodable, Hashable, Sendable { 8 | 9 | public init(startDate: Date? = nil, endDate: Date? = nil, notificationType: NotificationTypeV2? = nil, notificationSubtype: Subtype? = nil, transactionId: String? = nil, onlyFailures: Bool? = nil) { 10 | self.startDate = startDate 11 | self.endDate = endDate 12 | self.notificationType = notificationType 13 | self.notificationSubtype = notificationSubtype 14 | self.transactionId = transactionId 15 | self.onlyFailures = onlyFailures 16 | } 17 | 18 | ///The start date of the timespan for the requested App Store Server Notification history records. The startDate needs to precede the endDate. Choose a startDate that’s within the past 180 days from the current date. 19 | /// 20 | ///[startDate](https://developer.apple.com/documentation/appstoreserverapi/startdate) 21 | public var startDate: Date? 22 | 23 | ///The end date of the timespan for the requested App Store Server Notification history records. Choose an endDate that’s later than the startDate. If you choose an endDate in the future, the endpoint automatically uses the current date as the endDate. 24 | /// 25 | ///[endDate](https://developer.apple.com/documentation/appstoreserverapi/enddate) 26 | public var endDate: Date? 27 | 28 | ///A notification type. Provide this field to limit the notification history records to those with this one notification type. For a list of notifications types, see notificationType. 29 | ///Include either the transactionId or the notificationType in your query, but not both. 30 | /// 31 | ///[notificationType](https://developer.apple.com/documentation/appstoreserverapi/notificationtype) 32 | public var notificationType: NotificationTypeV2? 33 | 34 | ///A notification subtype. Provide this field to limit the notification history records to those with this one notification subtype. For a list of subtypes, see subtype. If you specify a notificationSubtype, you need to also specify its related notificationType. 35 | /// 36 | ///[notificationSubtype](https://developer.apple.com/documentation/appstoreserverapi/notificationsubtype) 37 | public var notificationSubtype: Subtype? 38 | 39 | ///The transaction identifier, which may be an original transaction identifier, of any transaction belonging to the customer. Provide this field to limit the notification history request to this one customer. 40 | ///Include either the transactionId or the notificationType in your query, but not both. 41 | /// 42 | ///[transactionId](https://developer.apple.com/documentation/appstoreserverapi/transactionid) 43 | public var transactionId: String? 44 | 45 | ///A Boolean value you set to true to request only the notifications that haven’t reached your server successfully. The response also includes notifications that the App Store server is currently retrying to send to your server. 46 | /// 47 | ///[onlyFailures](https://developer.apple.com/documentation/appstoreserverapi/onlyfailures) 48 | public var onlyFailures: Bool? 49 | } 50 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/NotificationHistoryResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that contains the App Store Server Notifications history for your app. 4 | /// 5 | ///[NotificationHistoryResponse](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponse) 6 | public struct NotificationHistoryResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(paginationToken: String? = nil, hasMore: Bool? = nil, notificationHistory: [NotificationHistoryResponseItem]? = nil) { 9 | self.paginationToken = paginationToken 10 | self.hasMore = hasMore 11 | self.notificationHistory = notificationHistory 12 | } 13 | 14 | ///A pagination token that you return to the endpoint on a subsequent call to receive the next set of results. 15 | /// 16 | ///[paginationToken](https://developer.apple.com/documentation/appstoreserverapi/paginationtoken) 17 | public var paginationToken: String? 18 | 19 | ///A Boolean value indicating whether the App Store has more transaction data. 20 | /// 21 | ///[hasMore](https://developer.apple.com/documentation/appstoreserverapi/hasmore) 22 | public var hasMore: Bool? 23 | 24 | ///An array of App Store server notification history records. 25 | /// 26 | ///[notificationHistoryResponseItem](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponseitem) 27 | public var notificationHistory: [NotificationHistoryResponseItem]? 28 | } 29 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/NotificationHistoryResponseItem.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The App Store server notification history record, including the signed notification payload and the result of the server’s first send attempt. 4 | /// 5 | ///[notificationHistoryResponseItem](https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponseitem) 6 | public struct NotificationHistoryResponseItem: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(signedPayload: String? = nil, sendAttempts: [SendAttemptItem]? = nil) { 9 | self.signedPayload = signedPayload 10 | self.sendAttempts = sendAttempts 11 | } 12 | 13 | ///A cryptographically signed payload, in JSON Web Signature (JWS) format, containing the response body for a version 2 notification. 14 | /// 15 | ///[signedPayload](https://developer.apple.com/documentation/appstoreservernotifications/signedpayload) 16 | public var signedPayload: String? 17 | 18 | ///An array of information the App Store server records for its attempts to send a notification to your server. The maximum number of entries in the array is six. 19 | /// 20 | ///[sendAttemptItem](https://developer.apple.com/documentation/appstoreserverapi/sendattemptitem) 21 | public var sendAttempts: [SendAttemptItem]? 22 | } 23 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/NotificationTypeV2.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The type that describes the in-app purchase or external purchase event for which the App Store sends the version 2 notification. 4 | /// 5 | ///[notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype) 6 | public enum NotificationTypeV2: String, Decodable, Encodable, Hashable, Sendable { 7 | case subscribed = "SUBSCRIBED" 8 | case didChangeRenewalPref = "DID_CHANGE_RENEWAL_PREF" 9 | case didChangeRenewalStatus = "DID_CHANGE_RENEWAL_STATUS" 10 | case offerRedeemed = "OFFER_REDEEMED" 11 | case didRenew = "DID_RENEW" 12 | case expired = "EXPIRED" 13 | case didFailToRenew = "DID_FAIL_TO_RENEW" 14 | case gracePeriodExpired = "GRACE_PERIOD_EXPIRED" 15 | case priceIncrease = "PRICE_INCREASE" 16 | case refund = "REFUND" 17 | case refundDeclined = "REFUND_DECLINED" 18 | case consumptionRequest = "CONSUMPTION_REQUEST" 19 | case renewalExtended = "RENEWAL_EXTENDED" 20 | case revoke = "REVOKE" 21 | case test = "TEST" 22 | case renewalExtension = "RENEWAL_EXTENSION" 23 | case refundReversed = "REFUND_REVERSED" 24 | case externalPurchaseToken = "EXTERNAL_PURCHASE_TOKEN" 25 | case oneTimeCharge = "ONE_TIME_CHARGE" 26 | } 27 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/OfferDiscountType.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The payment mode you configure for an introductory offer, promotional offer, or offer code on an auto-renewable subscription. 4 | /// 5 | ///[offerDiscountType](https://developer.apple.com/documentation/appstoreserverapi/offerdiscounttype) 6 | public enum OfferDiscountType: String, Decodable, Encodable, Hashable, Sendable { 7 | case freeTrial = "FREE_TRIAL" 8 | case payAsYouGo = "PAY_AS_YOU_GO" 9 | case payUpFront = "PAY_UP_FRONT" 10 | } 11 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/OfferType.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The type of subscription offer. 4 | /// 5 | ///[offerType](https://developer.apple.com/documentation/appstoreserverapi/offertype) 6 | public enum OfferType: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case introductoryOffer = 1 8 | case promotionalOffer = 2 9 | case subscriptionOfferCode = 3 10 | case winBackOffer = 4 11 | } 12 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/OrderLookupResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that includes the order lookup status and an array of signed transactions for the in-app purchases in the order. 4 | /// 5 | ///[OrderLookupResponse](https://developer.apple.com/documentation/appstoreserverapi/orderlookupresponse) 6 | public struct OrderLookupResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(status: OrderLookupStatus? = nil, signedTransactions: [String]? = nil) { 9 | self.status = status 10 | self.signedTransactions = signedTransactions 11 | } 12 | 13 | public init(rawStatus: Int32? = nil, signedTransactions: [String]? = nil) { 14 | self.rawStatus = rawStatus 15 | self.signedTransactions = signedTransactions 16 | } 17 | 18 | ///The status that indicates whether the order ID is valid. 19 | /// 20 | ///[OrderLookupStatus](https://developer.apple.com/documentation/appstoreserverapi/orderlookupstatus) 21 | public var status: OrderLookupStatus? { 22 | get { 23 | return rawStatus.flatMap { OrderLookupStatus(rawValue: $0) } 24 | } 25 | set { 26 | self.rawStatus = newValue.map { $0.rawValue } 27 | } 28 | } 29 | 30 | ///See ``status`` 31 | public var rawStatus: Int32? 32 | 33 | ///An array of in-app purchase transactions that are part of order, signed by Apple, in JSON Web Signature format. 34 | /// 35 | ///[JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 36 | public var signedTransactions: [String]? 37 | 38 | public enum CodingKeys: CodingKey { 39 | case status 40 | case signedTransactions 41 | } 42 | 43 | public init(from decoder: any Decoder) throws { 44 | let container = try decoder.container(keyedBy: CodingKeys.self) 45 | self.rawStatus = try container.decodeIfPresent(Int32.self, forKey: .status) 46 | self.signedTransactions = try container.decodeIfPresent([String].self, forKey: .signedTransactions) 47 | } 48 | 49 | public func encode(to encoder: any Encoder) throws { 50 | var container = encoder.container(keyedBy: CodingKeys.self) 51 | try container.encodeIfPresent(self.rawStatus, forKey: .status) 52 | try container.encodeIfPresent(self.signedTransactions, forKey: .signedTransactions) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/OrderLookupStatus.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A value that indicates whether the order ID in the request is valid for your app. 4 | /// 5 | ///[OrderLookupStatus](https://developer.apple.com/documentation/appstoreserverapi/orderlookupstatus) 6 | public enum OrderLookupStatus: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case valid = 0 8 | case invalid = 1 9 | } 10 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/Platform.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The platform on which the customer consumed the in-app purchase. 4 | /// 5 | ///[platform](https://developer.apple.com/documentation/appstoreserverapi/platform) 6 | public enum Platform: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case apple = 1 9 | case nonApple = 2 10 | } 11 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/PlayTime.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A value that indicates the amount of time that the customer used the app. 4 | /// 5 | ///[playTime](https://developer.apple.com/documentation/appstoreserverapi/playtime) 6 | public enum PlayTime: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case zeroToFiveMinutes = 1 9 | case fiveToSixtyMinutes = 2 10 | case oneToSixHours = 3 11 | case sixHoursToTwentyFourHours = 4 12 | case oneDayToFourDays = 5 13 | case fourDaysToSixteenDays = 6 14 | case overSixteenDays = 7 15 | } 16 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/PriceIncreaseStatus.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The status that indicates whether an auto-renewable subscription is subject to a price increase. 4 | /// 5 | ///[priceIncreaseStatus](https://developer.apple.com/documentation/appstoreserverapi/priceincreasestatus) 6 | public enum PriceIncreaseStatus: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case customerHasNotResponded = 0 8 | case customerConsentedOrWasNotifiedWithoutNeedingConsent = 1 9 | } 10 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ProductType.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The type of in-app purchase products you can offer in your app. 4 | /// 5 | ///[type](https://developer.apple.com/documentation/appstoreserverapi/type) 6 | public enum ProductType: String, Decodable, Encodable, Hashable, Sendable { 7 | case autoRenewableSubscription = "Auto-Renewable Subscription" 8 | case nonConsumable = "Non-Consumable" 9 | case consumable = "Consumable" 10 | case nonRenewingSubscription = "Non-Renewing Subscription" 11 | } 12 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/PurchasePlatform.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Apple Inc. Licensed under MIT License. 2 | 3 | ///Values that represent Apple platforms. 4 | /// 5 | ///[AppStore.Platform](https://developer.apple.com/documentation/storekit/appstore/platform) 6 | public enum PurchasePlatform: String, Decodable, Encodable, Hashable, Sendable { 7 | case iOS = "iOS" 8 | case macOS = "macOS" 9 | case tvOS = "tvOS" 10 | case visionOS = "visionOS" 11 | } 12 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/RefundHistoryResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that contains an array of signed JSON Web Signature (JWS) refunded transactions, and paging information. 4 | /// 5 | ///[RefundHistoryResponse](https://developer.apple.com/documentation/appstoreserverapi/refundhistoryresponse) 6 | public struct RefundHistoryResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(signedTransactions: [String]? = nil, revision: String? = nil, hasMore: Bool? = nil) { 9 | self.signedTransactions = signedTransactions 10 | self.revision = revision 11 | self.hasMore = hasMore 12 | } 13 | 14 | ///A list of up to 20 JWS transactions, or an empty array if the customer hasn't received any refunds in your app. The transactions are sorted in ascending order by revocationDate. 15 | /// 16 | ///[JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 17 | public var signedTransactions: [String]? 18 | 19 | ///A token you use in a query to request the next set of transactions for the customer. 20 | /// 21 | ///[revision](https://developer.apple.com/documentation/appstoreserverapi/revision) 22 | public var revision: String? 23 | 24 | ///A Boolean value indicating whether the App Store has more transaction data. 25 | /// 26 | ///[hasMore](https://developer.apple.com/documentation/appstoreserverapi/hasmore) 27 | public var hasMore: Bool? 28 | } 29 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/RefundPreference.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Apple Inc. Licensed under MIT License. 2 | 3 | ///A value that indicates your preferred outcome for the refund request. 4 | /// 5 | ///[refundPreference](https://developer.apple.com/documentation/appstoreserverapi/refundpreference) 6 | public enum RefundPreference: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case preferGrant = 1 9 | case preferDecline = 2 10 | case noPreference = 3 11 | } 12 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ResponseBodyV2.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The response body the App Store sends in a version 2 server notification. 4 | /// 5 | ///[responseBodyV2](https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2) 6 | public struct ResponseBodyV2: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(signedPayload: String? = nil) { 9 | self.signedPayload = signedPayload 10 | } 11 | 12 | ///A cryptographically signed payload, in JSON Web Signature (JWS) format, containing the response body for a version 2 notification. 13 | /// 14 | ///[signedPayload](https://developer.apple.com/documentation/appstoreservernotifications/signedpayload) 15 | public var signedPayload: String? 16 | } 17 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/ResponseBodyV2DecodedPayload.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | ///A decoded payload containing the version 2 notification data. 5 | /// 6 | ///[responseBodyV2DecodedPayload](https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2decodedpayload) 7 | public struct ResponseBodyV2DecodedPayload: DecodedSignedData, Decodable, Encodable, Hashable, Sendable { 8 | 9 | public init(notificationType: NotificationTypeV2? = nil, subtype: Subtype? = nil, notificationUUID: String? = nil, data: NotificationData? = nil, version: String? = nil, signedDate: Date? = nil, summary: Summary? = nil, externalPurchaseToken: ExternalPurchaseToken? = nil) { 10 | self.notificationType = notificationType 11 | self.subtype = subtype 12 | self.notificationUUID = notificationUUID 13 | self.data = data 14 | self.version = version 15 | self.signedDate = signedDate 16 | self.summary = summary 17 | self.externalPurchaseToken = externalPurchaseToken 18 | } 19 | 20 | public init(rawNotificationType: String? = nil, rawSubtype: String? = nil, notificationUUID: String? = nil, data: NotificationData? = nil, version: String? = nil, signedDate: Date? = nil, summary: Summary? = nil, externalPurchaseToken: ExternalPurchaseToken? = nil) { 21 | self.rawNotificationType = rawNotificationType 22 | self.rawSubtype = rawSubtype 23 | self.notificationUUID = notificationUUID 24 | self.data = data 25 | self.version = version 26 | self.signedDate = signedDate 27 | self.summary = summary 28 | self.externalPurchaseToken = externalPurchaseToken 29 | } 30 | 31 | ///The in-app purchase event for which the App Store sends this version 2 notification. 32 | /// 33 | ///[notificationType](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype) 34 | public var notificationType: NotificationTypeV2? { 35 | get { 36 | return rawNotificationType.flatMap { NotificationTypeV2(rawValue: $0) } 37 | } 38 | set { 39 | self.rawNotificationType = newValue.map { $0.rawValue } 40 | } 41 | } 42 | 43 | ///See ``notificationType`` 44 | public var rawNotificationType: String? 45 | 46 | ///Additional information that identifies the notification event. The subtype field is present only for specific version 2 notifications. 47 | /// 48 | ///[subtype](https://developer.apple.com/documentation/appstoreservernotifications/subtype) 49 | public var subtype: Subtype? { 50 | get { 51 | return rawSubtype.flatMap { Subtype(rawValue: $0) } 52 | } 53 | set { 54 | self.rawSubtype = newValue.map { $0.rawValue } 55 | } 56 | } 57 | 58 | ///See ``subtype`` 59 | public var rawSubtype: String? 60 | 61 | ///A unique identifier for the notification. 62 | /// 63 | ///[notificationUUID](https://developer.apple.com/documentation/appstoreservernotifications/notificationuuid) 64 | public var notificationUUID: String? 65 | 66 | ///The object that contains the app metadata and signed renewal and transaction information. 67 | ///The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields. 68 | /// 69 | ///[data](https://developer.apple.com/documentation/appstoreservernotifications/data) 70 | public var data: NotificationData? 71 | 72 | ///A string that indicates the notification’s App Store Server Notifications version number. 73 | /// 74 | ///[version](https://developer.apple.com/documentation/appstoreservernotifications/version) 75 | public var version: String? 76 | 77 | ///The UNIX time, in milliseconds, that the App Store signed the JSON Web Signature data. 78 | /// 79 | ///[signedDate](https://developer.apple.com/documentation/appstoreserverapi/signeddate) 80 | public var signedDate: Date? 81 | 82 | ///The summary data that appears when the App Store server completes your request to extend a subscription renewal date for eligible subscribers. 83 | ///The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields. 84 | /// 85 | ///[summary](https://developer.apple.com/documentation/appstoreservernotifications/summary) 86 | public var summary: Summary? 87 | 88 | ///This field appears when the notificationType is EXTERNAL_PURCHASE_TOKEN. 89 | ///The data, summary, and externalPurchaseToken fields are mutually exclusive. The payload contains only one of these fields. 90 | /// 91 | ///[externalPurchaseToken](https://developer.apple.com/documentation/appstoreservernotifications/externalpurchasetoken) 92 | public var externalPurchaseToken: ExternalPurchaseToken? 93 | 94 | enum CodingKeys: CodingKey { 95 | case notificationType 96 | case subtype 97 | case notificationUUID 98 | case data 99 | case version 100 | case signedDate 101 | case summary 102 | case externalPurchaseToken 103 | } 104 | 105 | public init(from decoder: any Decoder) throws { 106 | let container = try decoder.container(keyedBy: CodingKeys.self) 107 | self.rawNotificationType = try container.decodeIfPresent(String.self, forKey: .notificationType) 108 | self.rawSubtype = try container.decodeIfPresent(String.self, forKey: .subtype) 109 | self.notificationUUID = try container.decodeIfPresent(String.self, forKey: .notificationUUID) 110 | self.data = try container.decodeIfPresent(NotificationData.self, forKey: .data) 111 | self.version = try container.decodeIfPresent(String.self, forKey: .version) 112 | self.signedDate = try container.decodeIfPresent(Date.self, forKey: .signedDate) 113 | self.summary = try container.decodeIfPresent(Summary.self, forKey: .summary) 114 | self.externalPurchaseToken = try container.decodeIfPresent(ExternalPurchaseToken.self, forKey: .externalPurchaseToken) 115 | } 116 | 117 | public func encode(to encoder: any Encoder) throws { 118 | var container = encoder.container(keyedBy: CodingKeys.self) 119 | try container.encodeIfPresent(self.rawNotificationType, forKey: .notificationType) 120 | try container.encodeIfPresent(self.rawSubtype, forKey: .subtype) 121 | try container.encodeIfPresent(self.notificationUUID, forKey: .notificationUUID) 122 | try container.encodeIfPresent(self.data, forKey: .data) 123 | try container.encodeIfPresent(self.version, forKey: .version) 124 | try container.encodeIfPresent(self.signedDate, forKey: .signedDate) 125 | try container.encodeIfPresent(self.summary, forKey: .summary) 126 | try container.encodeIfPresent(self.externalPurchaseToken, forKey: .externalPurchaseToken) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/RevocationReason.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The reason for a refunded transaction. 4 | /// 5 | ///[revocationReason](https://developer.apple.com/documentation/appstoreserverapi/revocationreason) 6 | public enum RevocationReason: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case refundedDueToIssue = 1 8 | case refundedForOtherReason = 0 9 | } 10 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/SendAttemptItem.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | ///The success or error information and the date the App Store server records when it attempts to send a server notification to your server. 5 | /// 6 | ///[sendAttemptItem](https://developer.apple.com/documentation/appstoreserverapi/sendattemptitem) 7 | public struct SendAttemptItem: Decodable, Encodable, Hashable, Sendable { 8 | 9 | public init(attemptDate: Date? = nil, sendAttemptResult: SendAttemptResult? = nil) { 10 | self.attemptDate = attemptDate 11 | self.sendAttemptResult = sendAttemptResult 12 | } 13 | 14 | public init(attemptDate: Date? = nil, rawSendAttemptResult: String? = nil) { 15 | self.attemptDate = attemptDate 16 | self.rawSendAttemptResult = rawSendAttemptResult 17 | } 18 | 19 | ///The date the App Store server attempts to send a notification. 20 | /// 21 | ///[attemptDate](https://developer.apple.com/documentation/appstoreservernotifications/attemptdate) 22 | public var attemptDate: Date? 23 | 24 | ///The success or error information the App Store server records when it attempts to send an App Store server notification to your server. 25 | /// 26 | ///[sendAttemptResult](https://developer.apple.com/documentation/appstoreserverapi/sendattemptresult) 27 | public var sendAttemptResult: SendAttemptResult? { 28 | get { 29 | return rawSendAttemptResult.flatMap { SendAttemptResult(rawValue: $0) } 30 | } 31 | set { 32 | self.rawSendAttemptResult = newValue.map { $0.rawValue } 33 | } 34 | } 35 | 36 | ///See ``sendAttemptResult`` 37 | public var rawSendAttemptResult: String? 38 | 39 | public enum CodingKeys: CodingKey { 40 | case attemptDate 41 | case sendAttemptResult 42 | } 43 | 44 | public init(from decoder: any Decoder) throws { 45 | let container = try decoder.container(keyedBy: CodingKeys.self) 46 | self.attemptDate = try container.decodeIfPresent(Date.self, forKey: .attemptDate) 47 | self.rawSendAttemptResult = try container.decodeIfPresent(String.self, forKey: .sendAttemptResult) 48 | } 49 | 50 | public func encode(to encoder: any Encoder) throws { 51 | var container = encoder.container(keyedBy: CodingKeys.self) 52 | try container.encodeIfPresent(self.attemptDate, forKey: .attemptDate) 53 | try container.encodeIfPresent(self.rawSendAttemptResult, forKey: .sendAttemptResult) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/SendAttemptResult.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The success or error information the App Store server records when it attempts to send an App Store server notification to your server. 4 | /// 5 | ///[sendAttemptResult](https://developer.apple.com/documentation/appstoreserverapi/sendattemptresult) 6 | public enum SendAttemptResult: String, Decodable, Encodable, Hashable, Sendable { 7 | case success = "SUCCESS" 8 | case timedOut = "TIMED_OUT" 9 | case tlsIssue = "TLS_ISSUE" 10 | case circularRedirect = "CIRCULAR_REDIRECT" 11 | case noResponse = "NO_RESPONSE" 12 | case socketIssue = "SOCKET_ISSUE" 13 | case unsuportedCharset = "UNSUPPORTED_CHARSET" 14 | case invalidResponse = "INVALID_RESPONSE" 15 | case prematureClose = "PREMATURE_CLOSE" 16 | case unsuccessfulHttpResponseCode = "UNSUCCESSFUL_HTTP_RESPONSE_CODE" 17 | case other = "OTHER" 18 | } 19 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/SendTestNotificationResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that contains the test notification token. 4 | /// 5 | ///[SendTestNotificationResponse](https://developer.apple.com/documentation/appstoreserverapi/sendtestnotificationresponse) 6 | public struct SendTestNotificationResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(testNotificationToken: String? = nil) { 9 | self.testNotificationToken = testNotificationToken 10 | } 11 | 12 | ///A unique identifier for a notification test that the App Store server sends to your server. 13 | /// 14 | ///[testNotificationToken](https://developer.apple.com/documentation/appstoreserverapi/testnotificationtoken) 15 | public var testNotificationToken: String? 16 | } 17 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/Status.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The status of an auto-renewable subscription. 4 | /// 5 | ///[status](https://developer.apple.com/documentation/appstoreserverapi/status) 6 | public enum Status: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case active = 1 8 | case expired = 2 9 | case billingRetry = 3 10 | case billingGracePeriod = 4 11 | case revoked = 5 12 | } 13 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/StatusResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that contains status information for all of a customer’s auto-renewable subscriptions in your app. 4 | /// 5 | ///[StatusResponse](https://developer.apple.com/documentation/appstoreserverapi/statusresponse) 6 | public struct StatusResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(environment: AppStoreEnvironment? = nil, bundleId: String? = nil, appAppleId: Int64? = nil, data: [SubscriptionGroupIdentifierItem]? = nil) { 9 | self.environment = environment 10 | self.bundleId = bundleId 11 | self.appAppleId = appAppleId 12 | self.data = data 13 | } 14 | 15 | public init(rawEnvironment: String? = nil, bundleId: String? = nil, appAppleId: Int64? = nil, data: [SubscriptionGroupIdentifierItem]? = nil) { 16 | self.rawEnvironment = rawEnvironment 17 | self.bundleId = bundleId 18 | self.appAppleId = appAppleId 19 | self.data = data 20 | } 21 | 22 | ///The server environment, sandbox or production, in which the App Store generated the response. 23 | /// 24 | ///[environment](https://developer.apple.com/documentation/appstoreserverapi/environment) 25 | public var environment: AppStoreEnvironment? { 26 | get { 27 | return rawEnvironment.flatMap { AppStoreEnvironment(rawValue: $0) } 28 | } 29 | set { 30 | self.rawEnvironment = newValue.map { $0.rawValue } 31 | } 32 | } 33 | 34 | ///See ``environment`` 35 | public var rawEnvironment: String? 36 | 37 | ///The bundle identifier of an app. 38 | /// 39 | ///[bundleId](https://developer.apple.com/documentation/appstoreserverapi/bundleid) 40 | public var bundleId: String? 41 | 42 | ///The unique identifier of an app in the App Store. 43 | /// 44 | ///[appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 45 | public var appAppleId: Int64? 46 | 47 | ///An array of information for auto-renewable subscriptions, including App Store-signed transaction information and App Store-signed renewal information. 48 | public var data: [SubscriptionGroupIdentifierItem]? 49 | 50 | public enum CodingKeys: CodingKey { 51 | case environment 52 | case bundleId 53 | case appAppleId 54 | case data 55 | } 56 | 57 | public init(from decoder: any Decoder) throws { 58 | let container = try decoder.container(keyedBy: CodingKeys.self) 59 | self.rawEnvironment = try container.decodeIfPresent(String.self, forKey: .environment) 60 | self.bundleId = try container.decodeIfPresent(String.self, forKey: .bundleId) 61 | self.appAppleId = try container.decodeIfPresent(Int64.self, forKey: .appAppleId) 62 | self.data = try container.decodeIfPresent([SubscriptionGroupIdentifierItem].self, forKey: .data) 63 | } 64 | 65 | public func encode(to encoder: any Encoder) throws { 66 | var container = encoder.container(keyedBy: CodingKeys.self) 67 | try container.encodeIfPresent(self.rawEnvironment, forKey: .environment) 68 | try container.encodeIfPresent(self.bundleId, forKey: .bundleId) 69 | try container.encodeIfPresent(self.appAppleId, forKey: .appAppleId) 70 | try container.encodeIfPresent(self.data, forKey: .data) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/SubscriptionGroupIdentifierItem.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///Information for auto-renewable subscriptions, including signed transaction information and signed renewal information, for one subscription group. 4 | /// 5 | ///[SubscriptionGroupIdentifierItem](https://developer.apple.com/documentation/appstoreserverapi/subscriptiongroupidentifieritem) 6 | public struct SubscriptionGroupIdentifierItem: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(subscriptionGroupIdentifier: String? = nil, lastTransactions: [LastTransactionsItem]? = nil) { 9 | self.subscriptionGroupIdentifier = subscriptionGroupIdentifier 10 | self.lastTransactions = lastTransactions 11 | } 12 | 13 | ///The identifier of the subscription group that the subscription belongs to. 14 | /// 15 | ///[subscriptionGroupIdentifier](https://developer.apple.com/documentation/appstoreserverapi/subscriptiongroupidentifier) 16 | public var subscriptionGroupIdentifier: String? 17 | 18 | ///An array of the most recent App Store-signed transaction information and App Store-signed renewal information for all auto-renewable subscriptions in the subscription group. 19 | /// 20 | ///[lastTransactionsItem](https://developer.apple.com/documentation/appstoreserverapi/lasttransactionsitem) 21 | public var lastTransactions: [LastTransactionsItem]? 22 | } 23 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/Subtype.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A string that provides details about select notification types in version 2. 4 | /// 5 | ///[subtype](https://developer.apple.com/documentation/appstoreservernotifications/subtype) 6 | public enum Subtype: String, Decodable, Encodable, Hashable, Sendable { 7 | case initialBuy = "INITIAL_BUY" 8 | case resubscribe = "RESUBSCRIBE" 9 | case downgrade = "DOWNGRADE" 10 | case upgrade = "UPGRADE" 11 | case autoRenewEnabled = "AUTO_RENEW_ENABLED" 12 | case autoRenewDisabled = "AUTO_RENEW_DISABLED" 13 | case voluntary = "VOLUNTARY" 14 | case billingRetry = "BILLING_RETRY" 15 | case priceIncrease = "PRICE_INCREASE" 16 | case gracePeriod = "GRACE_PERIOD" 17 | case pending = "PENDING" 18 | case accepted = "ACCEPTED" 19 | case billingRecovery = "BILLING_RECOVERY" 20 | case productNotForSale = "PRODUCT_NOT_FOR_SALE" 21 | case summary = "SUMMARY" 22 | case failure = "FAILURE" 23 | case unreported = "UNREPORTED" 24 | } 25 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/Summary.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The payload data for a subscription-renewal-date extension notification. 4 | /// 5 | ///[summary](https://developer.apple.com/documentation/appstoreservernotifications/summary) 6 | public struct Summary: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(environment: AppStoreEnvironment? = nil, appAppleId: Int64? = nil, bundleId: String? = nil, productId: String? = nil, requestIdentifier: String? = nil, storefrontCountryCodes: [String]? = nil, succeededCount: Int64? = nil, failedCount: Int64? = nil) { 9 | self.environment = environment 10 | self.appAppleId = appAppleId 11 | self.bundleId = bundleId 12 | self.productId = productId 13 | self.requestIdentifier = requestIdentifier 14 | self.storefrontCountryCodes = storefrontCountryCodes 15 | self.succeededCount = succeededCount 16 | self.failedCount = failedCount 17 | } 18 | 19 | public init(rawEnvironment: String? = nil, appAppleId: Int64? = nil, bundleId: String? = nil, productId: String? = nil, requestIdentifier: String? = nil, storefrontCountryCodes: [String]? = nil, succeededCount: Int64? = nil, failedCount: Int64? = nil) { 20 | self.rawEnvironment = rawEnvironment 21 | self.appAppleId = appAppleId 22 | self.bundleId = bundleId 23 | self.productId = productId 24 | self.requestIdentifier = requestIdentifier 25 | self.storefrontCountryCodes = storefrontCountryCodes 26 | self.succeededCount = succeededCount 27 | self.failedCount = failedCount 28 | } 29 | 30 | ///The server environment that the notification applies to, either sandbox or production. 31 | /// 32 | ///[environment](https://developer.apple.com/documentation/appstoreservernotifications/environment) 33 | public var environment: AppStoreEnvironment? { 34 | get { 35 | return rawEnvironment.flatMap { AppStoreEnvironment(rawValue: $0) } 36 | } 37 | set { 38 | self.rawEnvironment = newValue.map { $0.rawValue } 39 | } 40 | } 41 | 42 | ///See ``environment`` 43 | public var rawEnvironment: String? 44 | 45 | ///The unique identifier of an app in the App Store. 46 | /// 47 | ///[appAppleId](https://developer.apple.com/documentation/appstoreservernotifications/appappleid) 48 | public var appAppleId: Int64? 49 | 50 | ///The bundle identifier of an app. 51 | /// 52 | ///[bundleId](https://developer.apple.com/documentation/appstoreserverapi/bundleid) 53 | public var bundleId: String? 54 | 55 | ///The unique identifier for the product, that you create in App Store Connect. 56 | /// 57 | ///[productId](https://developer.apple.com/documentation/appstoreserverapi/productid) 58 | public var productId: String? 59 | 60 | ///A string that contains a unique identifier you provide to track each subscription-renewal-date extension request. 61 | /// 62 | ///[requestIdentifier](https://developer.apple.com/documentation/appstoreserverapi/requestidentifier) 63 | public var requestIdentifier: String? 64 | 65 | ///A list of storefront country codes you provide to limit the storefronts for a subscription-renewal-date extension. 66 | /// 67 | ///[storefrontCountryCodes](https://developer.apple.com/documentation/appstoreserverapi/storefrontcountrycodes) 68 | public var storefrontCountryCodes: [String]? 69 | 70 | ///The count of subscriptions that successfully receive a subscription-renewal-date extension. 71 | /// 72 | ///[succeededCount](https://developer.apple.com/documentation/appstoreserverapi/succeededcount) 73 | public var succeededCount: Int64? 74 | 75 | ///The count of subscriptions that fail to receive a subscription-renewal-date extension. 76 | /// 77 | ///[failedCount](https://developer.apple.com/documentation/appstoreserverapi/failedcount) 78 | public var failedCount: Int64? 79 | 80 | public enum CodingKeys: CodingKey { 81 | case environment 82 | case appAppleId 83 | case bundleId 84 | case productId 85 | case requestIdentifier 86 | case storefrontCountryCodes 87 | case succeededCount 88 | case failedCount 89 | } 90 | 91 | public init(from decoder: any Decoder) throws { 92 | let container = try decoder.container(keyedBy: CodingKeys.self) 93 | self.rawEnvironment = try container.decodeIfPresent(String.self, forKey: .environment) 94 | self.appAppleId = try container.decodeIfPresent(Int64.self, forKey: .appAppleId) 95 | self.bundleId = try container.decodeIfPresent(String.self, forKey: .bundleId) 96 | self.productId = try container.decodeIfPresent(String.self, forKey: .productId) 97 | self.requestIdentifier = try container.decodeIfPresent(String.self, forKey: .requestIdentifier) 98 | self.storefrontCountryCodes = try container.decodeIfPresent([String].self, forKey: .storefrontCountryCodes) 99 | self.succeededCount = try container.decodeIfPresent(Int64.self, forKey: .succeededCount) 100 | self.failedCount = try container.decodeIfPresent(Int64.self, forKey: .failedCount) 101 | } 102 | 103 | public func encode(to encoder: any Encoder) throws { 104 | var container = encoder.container(keyedBy: CodingKeys.self) 105 | try container.encodeIfPresent(self.rawEnvironment, forKey: .environment) 106 | try container.encodeIfPresent(self.appAppleId, forKey: .appAppleId) 107 | try container.encodeIfPresent(self.bundleId, forKey: .bundleId) 108 | try container.encodeIfPresent(self.productId, forKey: .productId) 109 | try container.encodeIfPresent(self.requestIdentifier, forKey: .requestIdentifier) 110 | try container.encodeIfPresent(self.storefrontCountryCodes, forKey: .storefrontCountryCodes) 111 | try container.encodeIfPresent(self.succeededCount, forKey: .succeededCount) 112 | try container.encodeIfPresent(self.failedCount, forKey: .failedCount) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/TransactionHistoryRequest.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | 5 | public struct TransactionHistoryRequest: Hashable, Sendable { 6 | 7 | public init(startDate: Date? = nil, endDate: Date? = nil, productIds: [String]? = nil, productTypes: [ProductType]? = nil, sort: Order? = nil, subscriptionGroupIdentifiers: [String]? = nil, inAppOwnershipType: InAppOwnershipType? = nil, revoked: Bool? = nil) { 8 | self.startDate = startDate 9 | self.endDate = endDate 10 | self.productIds = productIds 11 | self.productTypes = productTypes 12 | self.sort = sort 13 | self.subscriptionGroupIdentifiers = subscriptionGroupIdentifiers 14 | self.inAppOwnershipType = inAppOwnershipType 15 | self.revoked = revoked 16 | } 17 | 18 | /// An optional start date of the timespan for the transaction history records you’re requesting. The startDate must precede the endDate if you specify both dates. To be included in results, the transaction’s purchaseDate must be equal to or greater than the startDate. 19 | /// 20 | ///[startDate](https://developer.apple.com/documentation/appstoreserverapi/startdate) 21 | public var startDate: Date? 22 | 23 | ///An optional end date of the timespan for the transaction history records you’re requesting. Choose an endDate that’s later than the startDate if you specify both dates. Using an endDate in the future is valid. To be included in results, the transaction’s purchaseDate must be less than the endDate. 24 | /// 25 | ///[endDate](https://developer.apple.com/documentation/appstoreserverapi/enddate) 26 | public var endDate: Date? 27 | 28 | ///An optional filter that indicates the product identifier to include in the transaction history. Your query may specify more than one productID. 29 | /// 30 | ///[productId](https://developer.apple.com/documentation/appstoreserverapi/productid) 31 | public var productIds: [String]? 32 | 33 | ///An optional filter that indicates the product type to include in the transaction history. Your query may specify more than one productType. 34 | public var productTypes: [ProductType]? 35 | 36 | ///An optional sort order for the transaction history records. The response sorts the transaction records by their recently modified date. The default value is ASCENDING, so you receive the oldest records first. 37 | public var sort: Order? 38 | 39 | ///An optional filter that indicates the subscription group identifier to include in the transaction history. Your query may specify more than one subscriptionGroupIdentifier. 40 | /// 41 | ///[subscriptionGroupIdentifier](https://developer.apple.com/documentation/appstoreserverapi/subscriptiongroupidentifier) 42 | public var subscriptionGroupIdentifiers: [String]? 43 | 44 | ///An optional filter that limits the transaction history by the in-app ownership type. 45 | /// 46 | ///[inAppOwnershipType](https://developer.apple.com/documentation/appstoreserverapi/inappownershiptype) 47 | public var inAppOwnershipType: InAppOwnershipType? 48 | 49 | ///An optional Boolean value that indicates whether the response includes only revoked transactions when the value is true, or contains only nonrevoked transactions when the value is false. By default, the request doesn't include this parameter. 50 | public var revoked: Bool? 51 | 52 | public enum ProductType: String, Decodable, Encodable, Sendable { 53 | case autoRenewable = "AUTO_RENEWABLE" 54 | case nonRenewable = "NON_RENEWABLE" 55 | case consumable = "CONSUMABLE" 56 | case nonConsumable = "NON_CONSUMABLE" 57 | } 58 | 59 | public enum Order: String, Decodable, Encodable, Sendable { 60 | case ascending = "ASCENDING" 61 | case descending = "DESCENDING" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/TransactionInfoResponse.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///A response that contains signed transaction information for a single transaction. 4 | /// 5 | ///[TransactionInfoResponse](https://developer.apple.com/documentation/appstoreserverapi/transactioninforesponse) 6 | public struct TransactionInfoResponse: Decodable, Encodable, Hashable, Sendable { 7 | 8 | public init(signedTransactionInfo: String? = nil) { 9 | self.signedTransactionInfo = signedTransactionInfo 10 | } 11 | 12 | ///A customer’s in-app purchase transaction, signed by Apple, in JSON Web Signature (JWS) format. 13 | /// 14 | ///[JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 15 | public var signedTransactionInfo: String? 16 | } 17 | 18 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/TransactionReason.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The cause of a purchase transaction, which indicates whether it’s a customer’s purchase or a renewal for an auto-renewable subscription that the system initiates. 4 | /// 5 | ///[transactionReason](https://developer.apple.com/documentation/appstoreserverapi/transactionreason) 6 | public enum TransactionReason: String, Decodable, Encodable, Hashable, Sendable { 7 | case purchase = "PURCHASE" 8 | case renewal = "RENEWAL" 9 | } 10 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Models/UserStatus.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | ///The status of a customer’s account within your app. 4 | /// 5 | ///[userStatus](https://developer.apple.com/documentation/appstoreserverapi/userstatus) 6 | public enum UserStatus: Int32, Decodable, Encodable, Hashable, Sendable { 7 | case undeclared = 0 8 | case active = 1 9 | case suspended = 2 10 | case terminated = 3 11 | case limitedAccess = 4 12 | } 13 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/PromotionalOfferSignatureCreator.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | import Crypto 5 | 6 | public struct PromotionalOfferSignatureCreator { 7 | private let ecPrivateKey: P256.Signing.PrivateKey 8 | private let keyId: String 9 | private let bundleId: String 10 | 11 | public init(privateKey: String, keyId: String, bundleId: String) throws { 12 | self.ecPrivateKey = try P256.Signing.PrivateKey(pemRepresentation: privateKey) 13 | self.keyId = keyId 14 | self.bundleId = bundleId 15 | } 16 | ///Create a promotional offer signature 17 | /// 18 | ///[Generating a signature for promotional offers](https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/subscriptions_and_offers/generating_a_signature_for_promotional_offers) 19 | /// 20 | /// - Parameter productIdentifier: The subscription product identifier 21 | /// - Parameter subscriptionOfferID: The subscription discount identifier 22 | /// - Parameter appAccountToken: An optional string value that you define; may be an empty string 23 | /// - Parameter nonce: A one-time UUID value that your server generates. Generate a new nonce for every signature. 24 | /// - Parameter timestamp: A timestamp your server generates in UNIX time format, in milliseconds. The timestamp keeps the offer active for 24 hours. 25 | /// - Returns: The Base64 encoded signature 26 | /// - Throws: If there was an error creating the signature 27 | public func createSignature(productIdentifier: String, subscriptionOfferID: String, appAccountToken: String, nonce: UUID, timestamp: Int64) throws -> String { 28 | let payload = "\(self.bundleId)\u{2063}\(self.keyId)\u{2063}\(productIdentifier)\u{2063}\(subscriptionOfferID)\u{2063}\(appAccountToken.lowercased())\u{2063}\(nonce.uuidString.lowercased())\u{2063}\(timestamp)" 29 | let dataBytes = Data(payload.utf8) 30 | let signature = try ecPrivateKey.signature(for: SHA256.hash(data: dataBytes)) 31 | return signature.derRepresentation.base64EncodedString() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/ReceiptUtility.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | import SwiftASN1 5 | 6 | public class ReceiptUtility { 7 | 8 | private static let IN_APP_TYPE_ID = Int64(17) 9 | private static let TRANSACTION_IDENTIFIER_TYPE_ID = Int64(1703) 10 | private static let ORIGINAL_TRANSACTION_IDENTIFIER_TYPE_ID = Int64(1705) 11 | 12 | ///Extracts a transaction id from an encoded App Receipt. Throws if the receipt does not match the expected format. 13 | ///*NO validation* is performed on the receipt, and any data returned should only be used to call the App Store Server API. 14 | ///- Parameter appReceipt The unmodified app receipt 15 | ///- Returns A transaction id from the array of in-app purchases, null if the receipt contains no in-app purchases 16 | public static func extractTransactionId(appReceipt: String) -> String? { 17 | var result: String? = nil 18 | if let parsedData = Data(base64Encoded: appReceipt), let parsedContainer = try? BER.parse([UInt8](parsedData)) { 19 | try? BER.sequence(parsedContainer, identifier: ASN1Identifier.sequence) { nodes in 20 | let _ = try ASN1ObjectIdentifier(berEncoded: &nodes) 21 | try? BER.optionalExplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { arrayNode in 22 | try? BER.sequence(arrayNode, identifier: ASN1Identifier.sequence) { nodes in 23 | var _ = nodes.next() 24 | _ = nodes.next() 25 | if let contentInfo = nodes.next() { 26 | try? BER.sequence(contentInfo, identifier: ASN1Identifier.sequence) { nodes in 27 | _ = nodes.next() 28 | try? BER.optionalExplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { arrayNode in 29 | let content = try ASN1OctetString(berEncoded: arrayNode) 30 | result = extractTransactionIdFromAppReceiptInner(appReceiptContent: content) 31 | } 32 | } 33 | } 34 | var _ = nodes.next() 35 | _ = nodes.next() 36 | } 37 | } 38 | } 39 | } 40 | return result 41 | } 42 | 43 | private static func extractTransactionIdFromAppReceiptInner(appReceiptContent: ASN1OctetString) -> String? { 44 | var result: String? = nil 45 | if let parsedAppReceipt = try? BER.parse([UInt8](appReceiptContent.bytes)) { 46 | try? BER.sequence(parsedAppReceipt, identifier: ASN1Identifier.set) { nodes in 47 | while let node = nodes.next() { 48 | try? BER.sequence(node, identifier: ASN1Identifier.sequence) { sequenceNodes in 49 | if let typeEncoded = sequenceNodes.next(), sequenceNodes.next() != nil, let valueEncoded = sequenceNodes.next() { 50 | let type = try? Int64(berEncoded: typeEncoded, withIdentifier: ASN1Identifier.integer) 51 | let value = try? ASN1OctetString(berEncoded: valueEncoded) 52 | if type == IN_APP_TYPE_ID, let unwrappedValue = value { 53 | result = extractTransactionIdFromInAppReceipt(inAppReceiptContent: unwrappedValue) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | return result 61 | } 62 | 63 | private static func extractTransactionIdFromInAppReceipt(inAppReceiptContent: ASN1OctetString) -> String? { 64 | var result: String? = nil 65 | if let parsedInAppReceipt = try? BER.parse([UInt8](inAppReceiptContent.bytes)) { 66 | try? BER.sequence(parsedInAppReceipt, identifier: ASN1Identifier.set) { nodes in 67 | while let node = nodes.next() { 68 | try? BER.sequence(node, identifier: ASN1Identifier.sequence) { sequenceNodes in 69 | if let typeEncoded = sequenceNodes.next(), sequenceNodes.next() != nil, let valueEncoded = sequenceNodes.next() { 70 | let type = try? Int64(berEncoded: typeEncoded, withIdentifier: ASN1Identifier.integer) 71 | let value = try? ASN1OctetString(berEncoded: valueEncoded) 72 | if type == TRANSACTION_IDENTIFIER_TYPE_ID || type == ORIGINAL_TRANSACTION_IDENTIFIER_TYPE_ID, let unwrappedValue = value { 73 | if let parseResult = try? BER.parse(unwrappedValue.bytes) { 74 | if let utf8String = try? ASN1UTF8String(berEncoded: parseResult, withIdentifier: .utf8String) { 75 | result = String(utf8String) 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | return result 85 | } 86 | 87 | ///Extracts a transaction id from an encoded transactional receipt. Throws if the receipt does not match the expected format. 88 | ///*NO validation* is performed on the receipt, and any data returned should only be used to call the App Store Server API. 89 | /// - Parameter transactionReceipt The unmodified transactionReceipt 90 | /// - Returns A transaction id, or null if no transactionId is found in the receipt 91 | public static func extractTransactionId(transactionReceipt: String) -> String? { 92 | if let d = Data(base64Encoded: transactionReceipt), let decodedReceipt = String(bytes: d, encoding: .utf8) { 93 | let purchaseInfoRange = NSRange(decodedReceipt.startIndex.. VerificationResult { 42 | let renewalInfoResult = await decodeSignedData(signedData: signedRenewalInfo, type: JWSRenewalInfoDecodedPayload.self) 43 | switch renewalInfoResult { 44 | case .valid(let renewalInfo): 45 | if self.environment != renewalInfo.environment { 46 | return VerificationResult.invalid(VerificationError.INVALID_ENVIRONMENT) 47 | } 48 | case .invalid(_): 49 | break 50 | } 51 | return renewalInfoResult 52 | } 53 | /// Verifies and decodes a signedTransaction obtained from the App Store Server API, an App Store Server Notification, or from a device 54 | /// See [JWSTransaction](https://developer.apple.com/documentation/appstoreserverapi/jwstransaction) 55 | /// 56 | /// - Parameter signedTransaction The signedTransaction field 57 | /// - Returns: If success, the decoded transaction info after verification, else the reason for verification failure 58 | public func verifyAndDecodeTransaction(signedTransaction: String) async -> VerificationResult { 59 | let transactionResult = await decodeSignedData(signedData: signedTransaction, type: JWSTransactionDecodedPayload.self) 60 | switch transactionResult { 61 | case .valid(let transaction): 62 | if self.bundleId != transaction.bundleId { 63 | return VerificationResult.invalid(VerificationError.INVALID_APP_IDENTIFIER) 64 | } 65 | if self.environment != transaction.environment { 66 | return VerificationResult.invalid(VerificationError.INVALID_ENVIRONMENT) 67 | } 68 | case .invalid(_): 69 | break 70 | } 71 | return transactionResult 72 | } 73 | /// Verifies and decodes an App Store Server Notification signedPayload 74 | /// See [signedPayload](https://developer.apple.com/documentation/appstoreservernotifications/signedpayload) 75 | /// 76 | /// - Parameter signedPayload The payload received by your server 77 | /// - Returns: If success, the decoded payload after verification, else the reason for verification failure 78 | public func verifyAndDecodeNotification(signedPayload: String) async -> VerificationResult { 79 | return await verifyAndDecodeNotification(signedPayload: signedPayload, validateNotification: self.verifyNotificationAppIdentifierAndEnvironment) 80 | } 81 | 82 | internal func verifyAndDecodeNotification(signedPayload: String, validateNotification: (_ appBundleID: String?, _ appAppleID: Int64?, _ environment: AppStoreEnvironment?) -> VerificationError?) async -> VerificationResult { 83 | let notificationResult = await decodeSignedData(signedData: signedPayload, type: ResponseBodyV2DecodedPayload.self) 84 | switch notificationResult { 85 | case .valid(let notification): 86 | let appAppleId: Int64? 87 | let bundleId : String? 88 | let environment: AppStoreEnvironment? 89 | if let data = notification.data { 90 | appAppleId = data.appAppleId 91 | bundleId = data.bundleId 92 | environment = data.environment 93 | } else if let summary = notification.summary { 94 | appAppleId = summary.appAppleId 95 | bundleId = summary.bundleId 96 | environment = summary.environment 97 | } else if let externalPurchaseToken = notification.externalPurchaseToken { 98 | appAppleId = externalPurchaseToken.appAppleId 99 | bundleId = externalPurchaseToken.bundleId 100 | if externalPurchaseToken.externalPurchaseId?.starts(with: "SANDBOX") == true { 101 | environment = .sandbox 102 | } else { 103 | environment = .production 104 | } 105 | } else { 106 | appAppleId = nil 107 | bundleId = nil 108 | environment = nil 109 | } 110 | if let result = validateNotification(bundleId, appAppleId, environment) { 111 | return .invalid(result) 112 | } 113 | case .invalid(_): 114 | break 115 | } 116 | return notificationResult 117 | } 118 | 119 | internal func verifyNotificationAppIdentifierAndEnvironment(bundleId: String?, appAppleId: Int64?, environment: AppStoreEnvironment?) -> VerificationError? { 120 | if self.bundleId != bundleId || (self.environment == .production && self.appAppleId != appAppleId) { 121 | return .INVALID_APP_IDENTIFIER 122 | } 123 | if self.environment != environment { 124 | return .INVALID_ENVIRONMENT 125 | } 126 | return nil 127 | } 128 | 129 | ///Verifies and decodes a signed AppTransaction 130 | ///See [AppTransaction](https://developer.apple.com/documentation/storekit/apptransaction) 131 | /// 132 | ///- Parameter signedAppTransaction The signed AppTransaction 133 | ///- Returns: If success, the decoded AppTransaction after validation, else the reason for verification failure 134 | public func verifyAndDecodeAppTransaction(signedAppTransaction: String) async -> VerificationResult { 135 | let appTransactionResult = await decodeSignedData(signedData: signedAppTransaction, type: AppTransaction.self) 136 | switch appTransactionResult { 137 | case .valid(let appTransaction): 138 | let environment = appTransaction.receiptType 139 | if self.bundleId != appTransaction.bundleId || (self.environment == .production && self.appAppleId != appTransaction.appAppleId) { 140 | return VerificationResult.invalid(VerificationError.INVALID_APP_IDENTIFIER) 141 | } 142 | if self.environment != environment { 143 | return VerificationResult.invalid(VerificationError.INVALID_ENVIRONMENT) 144 | } 145 | case .invalid(_): 146 | break 147 | } 148 | return appTransactionResult 149 | } 150 | 151 | private func decodeSignedData(signedData: String, type: T.Type) async -> VerificationResult where T : Decodable { 152 | return await chainVerifier.verify(signedData: signedData, type: type, onlineVerification: self.enableOnlineChecks, environment: self.environment) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Sources/AppStoreServerLibrary/Utility.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | 5 | internal func base64URLToBase64(_ encodedString: String) -> String { 6 | let replacedString = encodedString 7 | .replacingOccurrences(of: "/", with: "+") 8 | .replacingOccurrences(of: "_", with: "-") 9 | if (replacedString.count % 4 != 0) { 10 | return replacedString + String(repeating: "=", count: 4 - replacedString.count % 4) 11 | } 12 | return replacedString 13 | } 14 | 15 | internal func getJsonDecoder() -> JSONDecoder { 16 | let decoder = JSONDecoder() 17 | decoder.dateDecodingStrategy = .millisecondsSince1970 18 | return decoder 19 | } 20 | 21 | internal func getJsonEncoder() -> JSONEncoder { 22 | let encoder = JSONEncoder() 23 | encoder.dateEncodingStrategy = .custom({ date, e in 24 | // To encode the same as millisecondsSince1970, however truncating the decimal part 25 | var container = e.singleValueContainer() 26 | try container.encode((date.timeIntervalSince1970 * 1000.0).rounded(.towardZero)) 27 | }) 28 | return encoder 29 | } 30 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/JWSSignatureCreatorTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Apple Inc. Licensed under MIT License. 2 | 3 | import XCTest 4 | @testable import AppStoreServerLibrary 5 | 6 | import X509 7 | 8 | final class JWSSignatureCreatorTests: XCTestCase { 9 | 10 | public func testPromotionalOfferV2SignatureCreator() async throws { 11 | let key = TestingUtility.readFile("resources/certs/testSigningKey.p8") 12 | 13 | let signatureCreator = try PromotionalOfferV2SignatureCreator(signingKey: key, keyId: "keyId", issuerId: "issuerId", bundleId: "bundleId") 14 | let signature = try await signatureCreator.createSignature(productId: "productId", offerIdentifier: "offerIdentifier", transactionId: "transactionId") 15 | let tokenComponents = signature.components(separatedBy: ".") 16 | guard let headerData = Foundation.Data(base64Encoded: base64URLToBase64(tokenComponents[0])), 17 | let payloadData = Foundation.Data(base64Encoded: base64URLToBase64(tokenComponents[1])) else { 18 | XCTAssertTrue(false) 19 | return 20 | } 21 | let header = try JSONSerialization.jsonObject(with: headerData) as! [String: Any] 22 | let payload = try JSONSerialization.jsonObject(with: payloadData) as! [String: Any] 23 | 24 | // Header 25 | XCTAssertEqual("JWT", header["typ"] as! String) 26 | XCTAssertEqual("ES256", header["alg"] as! String) 27 | XCTAssertEqual("keyId", header["kid"] as! String) 28 | // Payload 29 | XCTAssertEqual("issuerId", payload["iss"] as! String) 30 | XCTAssertNotNil(payload["iat"]) 31 | XCTAssertNil(payload["exp"]) 32 | XCTAssertEqual("promotional-offer", payload["aud"] as! String) 33 | XCTAssertEqual("bundleId", payload["bid"] as! String) 34 | XCTAssertNotNil(payload["nonce"]) 35 | XCTAssertEqual("productId", payload["productId"] as! String) 36 | XCTAssertEqual("offerIdentifier", payload["offerIdentifier"] as! String) 37 | XCTAssertEqual("transactionId", payload["transactionId"] as! String) 38 | } 39 | 40 | public func testPromotionalOfferV2SignatureCreatorWithoutTransactionId() async throws { 41 | let key = TestingUtility.readFile("resources/certs/testSigningKey.p8") 42 | 43 | let signatureCreator = try PromotionalOfferV2SignatureCreator(signingKey: key, keyId: "keyId", issuerId: "issuerId", bundleId: "bundleId") 44 | let signature = try await signatureCreator.createSignature(productId: "productId", offerIdentifier: "offerIdentifier", transactionId: nil) 45 | let tokenComponents = signature.components(separatedBy: ".") 46 | guard let payloadData = Foundation.Data(base64Encoded: base64URLToBase64(tokenComponents[1])) else { 47 | XCTAssertTrue(false) 48 | return 49 | } 50 | let payload = try JSONSerialization.jsonObject(with: payloadData) as! [String: Any] 51 | XCTAssertNil(payload["transactionId"]) 52 | } 53 | 54 | public func testIntroductoryOfferEligbilitySignatureCreator() async throws { 55 | let key = TestingUtility.readFile("resources/certs/testSigningKey.p8") 56 | 57 | let signatureCreator = try IntroductoryOfferEligibilitySignatureCreator(signingKey: key, keyId: "keyId", issuerId: "issuerId", bundleId: "bundleId") 58 | let signature = try await signatureCreator.createSignature(productId: "productId", allowIntroductoryOffer: true, transactionId: "transactionId") 59 | let tokenComponents = signature.components(separatedBy: ".") 60 | guard let headerData = Foundation.Data(base64Encoded: base64URLToBase64(tokenComponents[0])), 61 | let payloadData = Foundation.Data(base64Encoded: base64URLToBase64(tokenComponents[1])) else { 62 | XCTAssertTrue(false) 63 | return 64 | } 65 | let header = try JSONSerialization.jsonObject(with: headerData) as! [String: Any] 66 | let payload = try JSONSerialization.jsonObject(with: payloadData) as! [String: Any] 67 | 68 | // Header 69 | XCTAssertEqual("JWT", header["typ"] as! String) 70 | XCTAssertEqual("ES256", header["alg"] as! String) 71 | XCTAssertEqual("keyId", header["kid"] as! String) 72 | // Payload 73 | XCTAssertEqual("issuerId", payload["iss"] as! String) 74 | XCTAssertNotNil(payload["iat"]) 75 | XCTAssertNil(payload["exp"]) 76 | XCTAssertEqual("introductory-offer-eligibility", payload["aud"] as! String) 77 | XCTAssertEqual("bundleId", payload["bid"] as! String) 78 | XCTAssertNotNil(payload["nonce"]) 79 | XCTAssertEqual("productId", payload["productId"] as! String) 80 | XCTAssertEqual(true, payload["allowIntroductoryOffer"] as! Bool) 81 | XCTAssertEqual("transactionId", payload["transactionId"] as! String) 82 | } 83 | 84 | public func testAdvancedCommerceInAppSignatureCreator() async throws { 85 | let key = TestingUtility.readFile("resources/certs/testSigningKey.p8") 86 | 87 | let signatureCreator = try AdvancedCommerceInAppSignatureCreator(signingKey: key, keyId: "keyId", issuerId: "issuerId", bundleId: "bundleId") 88 | let inAppRequest = TestInAppRequest(testData: "testData") 89 | let signature = try await signatureCreator.createSignature(advancedCommerceInAppRequest: inAppRequest) 90 | let tokenComponents = signature.components(separatedBy: ".") 91 | guard let headerData = Foundation.Data(base64Encoded: base64URLToBase64(tokenComponents[0])), 92 | let payloadData = Foundation.Data(base64Encoded: base64URLToBase64(tokenComponents[1])) else { 93 | XCTAssertTrue(false) 94 | return 95 | } 96 | let header = try JSONSerialization.jsonObject(with: headerData) as! [String: Any] 97 | let payload = try JSONSerialization.jsonObject(with: payloadData) as! [String: Any] 98 | 99 | // Header 100 | XCTAssertEqual("JWT", header["typ"] as! String) 101 | XCTAssertEqual("ES256", header["alg"] as! String) 102 | XCTAssertEqual("keyId", header["kid"] as! String) 103 | // Payload 104 | XCTAssertEqual("issuerId", payload["iss"] as! String) 105 | XCTAssertNotNil(payload["iat"]) 106 | XCTAssertNil(payload["exp"]) 107 | XCTAssertEqual("advanced-commerce-api", payload["aud"] as! String) 108 | XCTAssertEqual("bundleId", payload["bid"] as! String) 109 | XCTAssertNotNil(payload["nonce"]) 110 | let base64EncodedRequest = payload["request"] as! String 111 | guard let requestData = Foundation.Data(base64Encoded: base64EncodedRequest) else { 112 | XCTAssertTrue(false) 113 | return 114 | } 115 | let decodedRequest = try JSONSerialization.jsonObject(with: requestData) as! [String: Any] 116 | XCTAssertEqual("testData", decodedRequest["testData"] as! String) 117 | } 118 | 119 | struct TestInAppRequest: AdvancedCommerceInAppRequest { 120 | var testData: String 121 | 122 | init(testData: String) { 123 | self.testData = testData 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/PromotionalOfferSignatureCreatorTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import XCTest 4 | @testable import AppStoreServerLibrary 5 | 6 | import X509 7 | 8 | final class PromotionalOfferSignatureCreatorTests: XCTestCase { 9 | 10 | public func testSignatureCreator() throws { 11 | let key = TestingUtility.readFile("resources/certs/testSigningKey.p8") 12 | 13 | let signatureCreator = try PromotionalOfferSignatureCreator(privateKey: key, keyId: "keyId", bundleId: "bundleId") 14 | let signature = try signatureCreator.createSignature(productIdentifier: "productId", subscriptionOfferID: "offerId", appAccountToken: "appAccountToken", nonce: UUID(uuidString: "20fba8a0-2b80-4a7d-a17f-85c1854727f8")!, timestamp: 1698148900000) 15 | XCTAssertNotNil(signature) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/ReceiptUtilityTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import XCTest 4 | @testable import AppStoreServerLibrary 5 | 6 | import X509 7 | 8 | final class ReceiptUtilityTests: XCTestCase { 9 | 10 | private let APP_RECEIPT_EXPECTED_TRANSACTION_ID = "0" 11 | private let TRANSACTION_RECEIPT_EXPECTED_TRANSACTION_ID = "33993399" 12 | 13 | public func testXcodeAppReceiptExtractionWithNoTransactions() throws { 14 | let receipt = TestingUtility.readFile("resources/xcode/xcode-app-receipt-empty") 15 | 16 | let extractedTransactionId = ReceiptUtility.extractTransactionId(appReceipt: receipt) 17 | 18 | XCTAssertNil(extractedTransactionId) 19 | } 20 | 21 | public func testXcodeAppReceiptExtractionWithTransactions() throws { 22 | let receipt = TestingUtility.readFile("resources/xcode/xcode-app-receipt-with-transaction") 23 | 24 | let extractedTransactionId = ReceiptUtility.extractTransactionId(appReceipt: receipt) 25 | 26 | XCTAssertEqual(APP_RECEIPT_EXPECTED_TRANSACTION_ID, extractedTransactionId) 27 | } 28 | 29 | public func testTransactionReceiptExtraction() throws { 30 | let receipt = TestingUtility.readFile("resources/mock_signed_data/legacyTransaction") 31 | 32 | let extractedTransactionId = ReceiptUtility.extractTransactionId(transactionReceipt: receipt) 33 | 34 | XCTAssertEqual(TRANSACTION_RECEIPT_EXPECTED_TRANSACTION_ID, extractedTransactionId) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/TestingUtility.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import Foundation 4 | @testable import AppStoreServerLibrary 5 | 6 | import JWTKit 7 | import Crypto 8 | import XCTest 9 | 10 | public class TestingUtility { 11 | 12 | public static func readFile(_ path: String) -> String { 13 | let absolutePath = Bundle.module.url(forResource: path, withExtension: "")! 14 | return try! String(contentsOf: absolutePath, encoding: .utf8) 15 | } 16 | 17 | public static func readBytes(_ path: String) -> Data { 18 | let absolutePath = Bundle.module.url(forResource: path, withExtension: "")! 19 | return try! Data(contentsOf: absolutePath) 20 | } 21 | 22 | public static func getSignedDataVerifier(_ environment: AppStoreEnvironment, _ bundleId: String, _ appAppleId: Int64) -> SignedDataVerifier { 23 | return try! SignedDataVerifier(rootCertificates: [readBytes("resources/certs/testCA.der")], bundleId: bundleId, appAppleId: appAppleId, environment: environment, enableOnlineChecks: false) 24 | } 25 | 26 | public static func getSignedDataVerifier(_ environment: AppStoreEnvironment, _ bundleId: String) -> SignedDataVerifier { 27 | return getSignedDataVerifier(environment, bundleId, 1234) 28 | } 29 | 30 | public static func getSignedDataVerifier() -> SignedDataVerifier { 31 | return getSignedDataVerifier(.localTesting, "com.example") 32 | } 33 | 34 | public static func confirmCodableInternallyConsistent(_ codable: T) where T : Codable, T : Equatable { 35 | let type = type(of: codable) 36 | let parsedValue = try! getJsonDecoder().decode(type, from: getJsonEncoder().encode(codable)) 37 | XCTAssertEqual(parsedValue, codable) 38 | } 39 | 40 | public static func createSignedDataFromJson(_ path: String) -> String { 41 | let payload = readFile(path) 42 | let signingKey = Crypto.P256.Signing.PrivateKey() 43 | 44 | let header = JWTHeader(alg: "ES256") 45 | 46 | let encoder = JSONEncoder() 47 | let headerData = try! encoder.encode(header) 48 | let encodedHeader = headerData.base64EncodedString() 49 | let encodedPayload = base64ToBase64URL(payload.data(using: .utf8)!.base64EncodedString()) 50 | 51 | var signingInput = "\(encodedHeader).\(encodedPayload)" 52 | let signature = try! signingInput.withUTF8 { try signingKey.signature(for: $0) } 53 | return "\(signingInput).\(base64ToBase64URL(signature.rawRepresentation.base64EncodedString()))"; 54 | } 55 | 56 | private static func base64ToBase64URL(_ encodedString: String) -> String { 57 | return encodedString 58 | .replacingOccurrences(of: "+", with: "/") 59 | .replacingOccurrences(of: "-", with: "_") 60 | .replacingOccurrences(of: "=", with: "") 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/XcodeSignedDataVerifierTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023 Apple Inc. Licensed under MIT License. 2 | 3 | import XCTest 4 | @testable import AppStoreServerLibrary 5 | 6 | final class XcodeSignedDataVerifierTests: XCTestCase { 7 | 8 | private let XCODE_BUNDLE_ID = "com.example.naturelab.backyardbirds.example" 9 | 10 | public func testXcodeSignedAppTransaction() async throws { 11 | let verifier = TestingUtility.getSignedDataVerifier(.xcode, XCODE_BUNDLE_ID) 12 | let encodedAppTransaction = TestingUtility.readFile("resources/xcode/xcode-signed-app-transaction") 13 | 14 | let verifiedAppTransaction = await verifier.verifyAndDecodeAppTransaction(signedAppTransaction: encodedAppTransaction) 15 | 16 | guard case .valid(let appTransaction) = verifiedAppTransaction else { 17 | XCTAssertTrue(false) 18 | return 19 | } 20 | 21 | XCTAssertNotNil(appTransaction) 22 | XCTAssertNil(appTransaction.appAppleId) 23 | XCTAssertEqual(XCODE_BUNDLE_ID, appTransaction.bundleId) 24 | XCTAssertEqual("1", appTransaction.applicationVersion) 25 | XCTAssertNil(appTransaction.versionExternalIdentifier) 26 | compareXcodeDates(Date(timeIntervalSince1970: -62135769600), appTransaction.originalPurchaseDate) 27 | XCTAssertEqual("1", appTransaction.originalApplicationVersion) 28 | XCTAssertEqual("cYUsXc53EbYc0pOeXG5d6/31LGHeVGf84sqSN0OrJi5u/j2H89WWKgS8N0hMsMlf", appTransaction.deviceVerification) 29 | XCTAssertEqual(UUID(uuidString: "48c8b92d-ce0d-4229-bedf-e61b4f9cfc92"), appTransaction.deviceVerificationNonce) 30 | XCTAssertNil(appTransaction.preorderDate) 31 | XCTAssertEqual(.xcode, appTransaction.receiptType) 32 | XCTAssertEqual("Xcode", appTransaction.rawReceiptType) 33 | confirmCodableInternallyConsistentForXcode(appTransaction) 34 | } 35 | 36 | public func testXcodeSignedTransaction() async throws { 37 | let verifier = TestingUtility.getSignedDataVerifier(.xcode, XCODE_BUNDLE_ID) 38 | let encodedTransaction = TestingUtility.readFile("resources/xcode/xcode-signed-transaction") 39 | 40 | let verifiedTransaction = await verifier.verifyAndDecodeTransaction(signedTransaction: encodedTransaction) 41 | 42 | guard case .valid(let transaction) = verifiedTransaction else { 43 | XCTAssertTrue(false) 44 | return 45 | } 46 | 47 | XCTAssertEqual("0", transaction.originalTransactionId) 48 | XCTAssertEqual("0", transaction.transactionId) 49 | XCTAssertEqual("0", transaction.webOrderLineItemId) 50 | XCTAssertEqual(XCODE_BUNDLE_ID, transaction.bundleId) 51 | XCTAssertEqual("pass.premium", transaction.productId) 52 | XCTAssertEqual("6F3A93AB", transaction.subscriptionGroupIdentifier) 53 | compareXcodeDates(Date(timeIntervalSince1970: 1697679936.049), transaction.purchaseDate) 54 | compareXcodeDates(Date(timeIntervalSince1970: 1697679936.049), transaction.originalPurchaseDate) 55 | compareXcodeDates(Date(timeIntervalSince1970: 1700358336.049), transaction.expiresDate) 56 | XCTAssertEqual(1, transaction.quantity) 57 | XCTAssertEqual(ProductType.autoRenewableSubscription, transaction.type) 58 | XCTAssertEqual("Auto-Renewable Subscription", transaction.rawType) 59 | XCTAssertNil(transaction.appAccountToken) 60 | XCTAssertEqual(InAppOwnershipType.purchased, transaction.inAppOwnershipType) 61 | XCTAssertEqual("PURCHASED", transaction.rawInAppOwnershipType) 62 | compareXcodeDates(Date(timeIntervalSince1970: 1697679936.056), transaction.signedDate) 63 | XCTAssertNil(transaction.revocationReason) 64 | XCTAssertNil(transaction.revocationDate) 65 | XCTAssertEqual(false, transaction.isUpgraded) 66 | XCTAssertEqual(OfferType.introductoryOffer, transaction.offerType) 67 | XCTAssertEqual(1, transaction.rawOfferType) 68 | XCTAssertNil(transaction.offerIdentifier) 69 | XCTAssertEqual(AppStoreEnvironment.xcode, transaction.environment) 70 | XCTAssertEqual("Xcode", transaction.rawEnvironment) 71 | XCTAssertEqual("USA", transaction.storefront) 72 | XCTAssertEqual("143441", transaction.storefrontId) 73 | XCTAssertEqual(TransactionReason.purchase, transaction.transactionReason) 74 | XCTAssertEqual("PURCHASE", transaction.rawTransactionReason) 75 | confirmCodableInternallyConsistentForXcode(transaction) 76 | } 77 | 78 | public func testXcodeSignedRenewalInfo() async throws { 79 | let verifier = TestingUtility.getSignedDataVerifier(.xcode, XCODE_BUNDLE_ID) 80 | let encodedRenewalInfo = TestingUtility.readFile("resources/xcode/xcode-signed-renewal-info") 81 | 82 | let verifiedRenewalInfo = await verifier.verifyAndDecodeRenewalInfo(signedRenewalInfo: encodedRenewalInfo) 83 | 84 | guard case .valid(let renewalInfo) = verifiedRenewalInfo else { 85 | XCTAssertTrue(false) 86 | return 87 | } 88 | 89 | XCTAssertNil(renewalInfo.expirationIntent) 90 | XCTAssertEqual("0", renewalInfo.originalTransactionId) 91 | XCTAssertEqual("pass.premium", renewalInfo.autoRenewProductId) 92 | XCTAssertEqual("pass.premium", renewalInfo.productId) 93 | XCTAssertEqual(AutoRenewStatus.on, renewalInfo.autoRenewStatus) 94 | XCTAssertEqual(1, renewalInfo.rawAutoRenewStatus) 95 | XCTAssertNil(renewalInfo.isInBillingRetryPeriod) 96 | XCTAssertNil(renewalInfo.priceIncreaseStatus) 97 | XCTAssertNil(renewalInfo.gracePeriodExpiresDate) 98 | XCTAssertNil(renewalInfo.offerType) 99 | XCTAssertNil(renewalInfo.offerIdentifier) 100 | compareXcodeDates(Date(timeIntervalSince1970: 1697679936.711), renewalInfo.signedDate) 101 | XCTAssertEqual(AppStoreEnvironment.xcode, renewalInfo.environment) 102 | XCTAssertEqual("Xcode", renewalInfo.rawEnvironment) 103 | compareXcodeDates(Date(timeIntervalSince1970: 1697679936.049), renewalInfo.recentSubscriptionStartDate) 104 | compareXcodeDates(Date(timeIntervalSince1970: 1700358336.049), renewalInfo.renewalDate) 105 | confirmCodableInternallyConsistentForXcode(renewalInfo) 106 | } 107 | 108 | public func testXcodeSignedAppTransactionWithProductionEnvironment() async throws { 109 | let verifier = TestingUtility.getSignedDataVerifier(.production, XCODE_BUNDLE_ID) 110 | let encodedAppTransaction = TestingUtility.readFile("resources/xcode/xcode-signed-app-transaction") 111 | let verifiedAppTransaction = await verifier.verifyAndDecodeAppTransaction(signedAppTransaction: encodedAppTransaction) 112 | switch verifiedAppTransaction { 113 | case .valid(_): 114 | XCTAssert(false) 115 | case .invalid(let error): 116 | // Without a valid x5c header this won't even pass formatting checks 117 | XCTAssertEqual(VerificationError.INVALID_JWT_FORMAT, error) 118 | } 119 | } 120 | 121 | // Xcode-generated dates are not well formed, therefore we only compare to ms precision 122 | private func compareXcodeDates(_ first: Date, _ second: Date?) { 123 | XCTAssertEqual(floor((first.timeIntervalSince1970 * 1000)), floor(((second?.timeIntervalSince1970 ?? 0.0) * 1000))) 124 | } 125 | 126 | private func confirmCodableInternallyConsistentForXcode(_ codable: T) where T : Codable, T : Equatable { 127 | let type = type(of: codable) 128 | let encoder = JSONEncoder() 129 | // Xcode receipts contain a decimal value, we encode the value as encoded in those receipts 130 | encoder.dateEncodingStrategy = .millisecondsSince1970 131 | let parsedValue = try! getJsonDecoder().decode(type, from: encoder.encode(codable)) 132 | XCTAssertEqual(parsedValue, codable) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/certs/testCA.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apple/app-store-server-library-swift/5bd4298c085acef383305a7cb033e21e6090c875/Tests/AppStoreServerLibraryTests/resources/certs/testCA.der -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/certs/testSigningKey.p8: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgSpP55ELdXswj9JRZ 3 | APRwtTfS4CNRqpKIs+28rNHiPAqhRANCAASs8nLES7b+goKslppNVOurf0MonZdw 4 | 3pb6TxS8Z/5j+UNY1sWK1ChxpuwNS9I3R50cfdQo/lA9PPhw6XIg8ytd 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/mock_signed_data/legacyTransaction: -------------------------------------------------------------------------------- 1 | ewoicHVyY2hhc2UtaW5mbyIgPSAiZXdvaWRISmhibk5oWTNScGIyNHRhV1FpSUQwZ0lqTXpPVGt6TXprNUlqc0tmUW89IjsKfQo= -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/mock_signed_data/missingX5CHeaderClaim: -------------------------------------------------------------------------------- 1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1Y3dyb25nIjpbIk1JSUJvRENDQVVhZ0F3SUJBZ0lCRERBS0JnZ3Foa2pPUFFRREF6QkZNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1CNFhEVEl6TURFd05USXhNekV6TkZvWERUTXpNREV3TVRJeE16RXpORm93UFRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eERUQUxCZ05WQkFvTUJFeGxZV1l3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRpdFlIRWFZVnVjOGc5QWpUT3dFck12R3lQeWtQYStwdXZUSThoSlRIWlpETEdhczJxWDErRXJ4Z1FUSmdWWHY3Nm5tTGhoUkpIK2oyNUFpQUk4aUdzb3k4d0xUQUpCZ05WSFJNRUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVFCZ29xaGtpRzkyTmtCZ3NCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05JQURCRkFpQlg0YytUMEZwNW5KNVFSQ2xSZnU1UFNCeVJ2TlB0dWFUc2swdlBCM1dBSUFJaEFOZ2FhdUFqL1lQOXMwQWtFaHlKaHhRTy82UTJ6b3VaK0gxQ0lPZWhuTXpRIiwiTUlJQm56Q0NBVVdnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQXpBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1CNFhEVEl6TURFd05USXhNekV3TlZvWERUTXpNREV3TVRJeE16RXdOVm93UlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eEZUQVRCZ05WQkFvTURFbHVkR1Z5YldWa2FXRjBaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCQlVONVY5cktqZlJpTUFJb2pFQTBBdjVNcDBvRitPMGNMNGd6clRGMTc4aW5VSHVnajdFdDQ2TnJrUTdoS2dNVm5qb2dxNDVRMXJNcytjTUhWTklMV3FqTlRBek1BOEdBMVVkRXdRSU1BWUJBZjhDQVFBd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJBR0NpcUdTSWIzWTJRR0FnRUVBZ1VBTUFvR0NDcUdTTTQ5QkFNREEwZ0FNRVVDSVFDbXNJS1lzNDF1bGxzc0hYNHJWdmVVVDBaN0lzNS9oTEsxbEZQVHR1bjNoQUlnYzIrMlJHNStnTmNGVmNzK1hKZUVsNEdaK29qbDNST09tbGwreWU3ZHluUT0iLCJNSUlCZ2pDQ0FTbWdBd0lCQWdJSkFMVWM1QUxpSDVwYk1Bb0dDQ3FHU000OUJBTURNRFl4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh3SGhjTk1qTXdNVEExTWpFek1ESXlXaGNOTXpNd01UQXlNakV6TURJeVdqQTJNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVjKy9CbCtnb3NwbzZ0ZjlaN2lvNXRkS2RybE4xWWRWbnFFaEVEWERTaHpkQUpQUWlqYW1YSU1IZjh4V1dUYTF6Z29ZVHhPS3BidUp0RHBsejFYcmlUYU1nTUI0d0RBWURWUjBUQkFVd0F3RUIvekFPQmdOVkhROEJBZjhFQkFNQ0FRWXdDZ1lJS29aSXpqMEVBd01EUndBd1JBSWdlbVdRWG5NQWRUYWQySkRKV25nOVU0dUJCTDVtQTdXSTA1SDdvSDdjNmlRQ0lIaVJxTWpOZnpVQXlpdTloNnJPVS9LK2lUUjBJLzNZL05TV3NYSFgrYWNjIl19.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.1TFhjDR4WwQJNgizVGYXz3WE3ajxTdH1wKLQQ71MtrkadSxxOo3yPo_6L9Z03unIU7YK-NRNzSIb5bh5WqTprQ -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/mock_signed_data/renewalInfo: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJzaWduZWREYXRlIjoxNjcyOTU2MTU0MDAwfQ.FbK2OL-t6l4892W7fzWyus_g9mIl2CzWLbVt7Kgcnt6zzVulF8bzovgpe0v_y490blROGixy8KDoe2dSU53-Xw -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/mock_signed_data/testNotification: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImFwcEFwcGxlSWQiOjEyMzQsImVudmlyb25tZW50IjoiU2FuZGJveCIsImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsInNpZ25lZERhdGUiOjE2ODEzMTQzMjQwMDAsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.VVXYwuNm2Y3XsOUva-BozqatRCsDuykA7xIe_CCRw6aIAAxJ1nb2sw871jfZ6dcgNhUuhoZ93hfbc1v_5zB7Og -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/mock_signed_data/transactionInfo: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlIiwic2lnbmVkRGF0ZSI6MTY3Mjk1NjE1NDAwMH0.PnHWpeIJZ8f2Q218NSGLo_aR0IBEJvC6PxmxKXh-qfYTrZccx2suGl223OSNAX78e4Ylf2yJCG2N-FfU-NIhZQ -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/mock_signed_data/wrongBundleId: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUud3JvbmcifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.WWE31hTB_mcv2O_lf-xI-MNY3d8txc0MzpqFx4QnYDfFIxB95Lo2Fm3r46YSjLLdL7xCWdEJrJP5bHgRCejAGg -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/apiException.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 5000000, 3 | "errorMessage": "An unknown error occurred." 4 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/apiTooManyRequestsException.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 4290000, 3 | "errorMessage": "Rate limit exceeded." 4 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/apiUnknownError.json: -------------------------------------------------------------------------------- 1 | { 2 | "errorCode": 9990000, 3 | "errorMessage": "Testing error." 4 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/appTransaction.json: -------------------------------------------------------------------------------- 1 | { 2 | "receiptType": "LocalTesting", 3 | "appAppleId": 531412, 4 | "bundleId": "com.example", 5 | "applicationVersion": "1.2.3", 6 | "versionExternalIdentifier": 512, 7 | "receiptCreationDate": 1698148900000, 8 | "originalPurchaseDate": 1698148800000, 9 | "originalApplicationVersion": "1.1.2", 10 | "deviceVerification": "device_verification_value", 11 | "deviceVerificationNonce": "48ccfa42-7431-4f22-9908-7e88983e105a", 12 | "preorderDate": 1698148700000, 13 | "appTransactionId": "71134", 14 | "originalPlatform": "iOS" 15 | } 16 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestIdentifier": "758883e8-151b-47b7-abd0-60c4d804c2f5" 3 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/extendSubscriptionRenewalDateResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "originalTransactionId": "2312412", 3 | "webOrderLineItemId": "9993", 4 | "success": true, 5 | "effectiveDate": 1698148900000 6 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/getAllSubscriptionStatusesResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "environment": "LocalTesting", 3 | "bundleId": "com.example", 4 | "appAppleId": 5454545, 5 | "data": [ 6 | { 7 | "subscriptionGroupIdentifier": "sub_group_one", 8 | "lastTransactions": [ 9 | { 10 | "status": 1, 11 | "originalTransactionId": "3749183", 12 | "signedTransactionInfo": "signed_transaction_one", 13 | "signedRenewalInfo": "signed_renewal_one" 14 | }, 15 | { 16 | "status": 5, 17 | "originalTransactionId": "5314314134", 18 | "signedTransactionInfo": "signed_transaction_two", 19 | "signedRenewalInfo": "signed_renewal_two" 20 | } 21 | ] 22 | }, 23 | { 24 | "subscriptionGroupIdentifier": "sub_group_two", 25 | "lastTransactions": [ 26 | { 27 | "status": 2, 28 | "originalTransactionId": "3413453", 29 | "signedTransactionInfo": "signed_transaction_three", 30 | "signedRenewalInfo": "signed_renewal_three" 31 | } 32 | ] 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/getNotificationHistoryResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "paginationToken": "57715481-805a-4283-8499-1c19b5d6b20a", 3 | "hasMore": true, 4 | "notificationHistory": [ 5 | { 6 | "sendAttempts": [ 7 | { 8 | "attemptDate": 1698148900000, 9 | "sendAttemptResult": "NO_RESPONSE" 10 | }, { 11 | "attemptDate": 1698148950000, 12 | "sendAttemptResult": "SUCCESS" 13 | } 14 | ], 15 | "signedPayload": "signed_payload_one" 16 | }, 17 | { 18 | "sendAttempts": [ 19 | { 20 | "attemptDate": 1698148800000, 21 | "sendAttemptResult": "CIRCULAR_REDIRECT" 22 | } 23 | ], 24 | "signedPayload": "signed_payload_two" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/getRefundHistoryResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactions": [ 3 | "signed_transaction_one", 4 | "signed_transaction_two" 5 | ], 6 | "revision": "revision_output", 7 | "hasMore": true 8 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestIdentifier": "20fba8a0-2b80-4a7d-a17f-85c1854727f8", 3 | "complete": true, 4 | "completeDate": 1698148900000, 5 | "succeededCount": 30, 6 | "failedCount": 2 7 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/getTestNotificationStatusResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedPayload": "signed_payload", 3 | "sendAttempts": [ 4 | { 5 | "attemptDate": 1698148900000, 6 | "sendAttemptResult": "NO_RESPONSE" 7 | }, { 8 | "attemptDate": 1698148950000, 9 | "sendAttemptResult": "SUCCESS" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/lookupOrderIdResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 1, 3 | "signedTransactions": [ 4 | "signed_transaction_one", 5 | "signed_transaction_two" 6 | ] 7 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/requestTestNotificationResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "testNotificationToken": "ce3af791-365e-4c60-841b-1674b43c1609" 3 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/signedConsumptionRequestNotification.json: -------------------------------------------------------------------------------- 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/AppStoreServerLibraryTests/resources/models/signedExternalPurchaseTokenNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "EXTERNAL_PURCHASE_TOKEN", 3 | "subtype": "UNREPORTED", 4 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 5 | "version": "2.0", 6 | "signedDate": 1698148900000, 7 | "externalPurchaseToken": { 8 | "externalPurchaseId": "b2158121-7af9-49d4-9561-1f588205523e", 9 | "tokenCreationDate": 1698148950000, 10 | "appAppleId": 55555, 11 | "bundleId": "com.example" 12 | } 13 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/signedExternalPurchaseTokenSandboxNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "EXTERNAL_PURCHASE_TOKEN", 3 | "subtype": "UNREPORTED", 4 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 5 | "version": "2.0", 6 | "signedDate": 1698148900000, 7 | "externalPurchaseToken": { 8 | "externalPurchaseId": "SANDBOX_b2158121-7af9-49d4-9561-1f588205523e", 9 | "tokenCreationDate": 1698148950000, 10 | "appAppleId": 55555, 11 | "bundleId": "com.example" 12 | } 13 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/signedNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "SUBSCRIBED", 3 | "subtype": "INITIAL_BUY", 4 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 5 | "data": { 6 | "environment": "LocalTesting", 7 | "appAppleId": 41234, 8 | "bundleId": "com.example", 9 | "bundleVersion": "1.2.3", 10 | "signedTransactionInfo": "signed_transaction_info_value", 11 | "signedRenewalInfo": "signed_renewal_info_value", 12 | "status": 1 13 | }, 14 | "version": "2.0", 15 | "signedDate": 1698148900000 16 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/signedRenewalInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "expirationIntent": 1, 3 | "originalTransactionId": "12345", 4 | "autoRenewProductId": "com.example.product.2", 5 | "productId": "com.example.product", 6 | "autoRenewStatus": 1, 7 | "isInBillingRetryPeriod": true, 8 | "priceIncreaseStatus": 0, 9 | "gracePeriodExpiresDate": 1698148900000, 10 | "offerType": 2, 11 | "offerIdentifier": "abc.123", 12 | "signedDate": 1698148800000, 13 | "environment": "LocalTesting", 14 | "recentSubscriptionStartDate": 1698148800000, 15 | "renewalDate": 1698148850000, 16 | "renewalPrice": 9990, 17 | "currency": "USD", 18 | "offerDiscountType": "PAY_AS_YOU_GO", 19 | "eligibleWinBackOfferIds": [ 20 | "eligible1", 21 | "eligible2" 22 | ], 23 | "appTransactionId": "71134", 24 | "offerPeriod": "P1Y", 25 | "appAccountToken": "7e3fb20b-4cdb-47cc-936d-99d65f608138" 26 | } 27 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/signedSummaryNotification.json: -------------------------------------------------------------------------------- 1 | { 2 | "notificationType": "RENEWAL_EXTENSION", 3 | "subtype": "SUMMARY", 4 | "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", 5 | "version": "2.0", 6 | "signedDate": 1698148900000, 7 | "summary": { 8 | "environment": "LocalTesting", 9 | "appAppleId": 41234, 10 | "bundleId": "com.example", 11 | "productId": "com.example.product", 12 | "requestIdentifier": "efb27071-45a4-4aca-9854-2a1e9146f265", 13 | "storefrontCountryCodes": [ 14 | "CAN", 15 | "USA", 16 | "MEX" 17 | ], 18 | "succeededCount": 5, 19 | "failedCount": 2 20 | } 21 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/signedTransaction.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactionId":"23456", 3 | "originalTransactionId":"12345", 4 | "webOrderLineItemId":"34343", 5 | "bundleId":"com.example", 6 | "productId":"com.example.product", 7 | "subscriptionGroupIdentifier":"55555", 8 | "purchaseDate":1698148900000, 9 | "originalPurchaseDate":1698148800000, 10 | "expiresDate":1698149000000, 11 | "quantity":1, 12 | "type":"Auto-Renewable Subscription", 13 | "appAccountToken": "7e3fb20b-4cdb-47cc-936d-99d65f608138", 14 | "inAppOwnershipType":"PURCHASED", 15 | "signedDate":1698148900000, 16 | "revocationReason": 1, 17 | "revocationDate": 1698148950000, 18 | "isUpgraded": true, 19 | "offerType":1, 20 | "offerIdentifier": "abc.123", 21 | "environment":"LocalTesting", 22 | "transactionReason":"PURCHASE", 23 | "storefront":"USA", 24 | "storefrontId":"143441", 25 | "price": 10990, 26 | "currency": "USD", 27 | "offerDiscountType": "PAY_AS_YOU_GO", 28 | "appTransactionId": "71134", 29 | "offerPeriod": "P1Y" 30 | } 31 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/transactionHistoryResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "revision": "revision_output", 3 | "hasMore": true, 4 | "bundleId": "com.example", 5 | "appAppleId": 323232, 6 | "environment": "LocalTesting", 7 | "signedTransactions": [ 8 | "signed_transaction_value", 9 | "signed_transaction_value2" 10 | ] 11 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/transactionHistoryResponseWithMalformedAppAppleId.json: -------------------------------------------------------------------------------- 1 | { 2 | "revision": "revision_output", 3 | "hasMore": true, 4 | "bundleId": "com.example", 5 | "appAppleId": "blue", 6 | "environment": "LocalTesting", 7 | "signedTransactions": [ 8 | "signed_transaction_value", 9 | "signed_transaction_value2" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/transactionHistoryResponseWithMalformedEnvironment.json: -------------------------------------------------------------------------------- 1 | { 2 | "revision": "revision_output", 3 | "hasMore": true, 4 | "bundleId": "com.example", 5 | "appAppleId": 323232, 6 | "environment": "LocalTestingxxx", 7 | "signedTransactions": [ 8 | "signed_transaction_value", 9 | "signed_transaction_value2" 10 | ] 11 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/models/transactionInfoResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "signedTransactionInfo": "signed_transaction_info_value" 3 | } -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/xcode/xcode-app-receipt-empty: -------------------------------------------------------------------------------- 1 | MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSABIHhMYHeMA8CAQACAQEEBwwFWGNvZGUwCwIBAQIBAQQDAgEAMDUCAQICAQEELQwrY29tLmV4YW1wbGUubmF0dXJlbGFiLmJhY2t5YXJkYmlyZHMuZXhhbXBsZTALAgEDAgEBBAMMATEwEAIBBAIBAQQI0bz+zwQAAAAwHAIBBQIBAQQU4nEwK24WxZhKi0PSGTYgWoXOIqMwCgIBCAIBAQQCFgAwHgIBDAIBAQQWFhQyMDIzLTEwLTE5VDAxOjE4OjU0WjAeAgEVAgEBBBYWFDQwMDEtMDEtMDFUMDA6MDA6MDBaAAAAAAAAoIIDeDCCA3QwggJcoAMCAQICAQEwDQYJKoZIhvcNAQELBQAwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MB4XDTIwMDQwMTE3NTIzNVoXDTQwMDMyNzE3NTIzNVowXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23+QPCxzD9uXJkuTuwr4oSE+yGHZJMheH3U+2pPbMRqRgLm/5QzLPLsORGIm+gQptknnb+Ab5g1ozSVuw3YI9UoLrnp0PMSpC7PPYg/7tLz324ReKOtHDfHti6z1n7AJOKNue8smUAoa4YnRcnYLOUzLT27As1+3lbq5qF1KdKvvb0GlfgmNuj09zXBX2O3v1dp3yJMEHO8JiHhlzoHyjXLnBxpuJhL3MrENuziQawbE/A3llVDNkci6JfRYyYzhcdtKRfMtGZYDVoGmRO51d1tTz3isXbo+X1ArXCmM3cLXKhffIrTX5Hior6htp8HaaC1mzM8pC1As48L75l8SwQIDAQABozswOTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIChDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAsgDgPPHo6WK9wNYdQJ5XuTiQd3ZS0qhLcG64Z5n7s4pVn+8dKLhfKtFznzVHN7tG03YQ8vBp7M1imXH5YIqESDjEvYtnJbmrbDNlrdjCmnhID+nMwScNxs9kPG2AWTOMyjYGKhEbjUnOCP9mwEcoS+tawSsJViylqgkDezIx3OiFeEjOwMUSEWoPDK4vBcpvemR/ICx15kyxEtP94x9eDX24WNegfOR/Y6uXmivDKtjQsuHVWg05G29nKKkSg9aHeG2ZvV6zCuCYzvbqw45taeu3QIE9hz1wUdHEXY2l3H9qWBreYHY3Uuz/rBldDBUvig/1icjXKx0e7CuRBac9TzGCAY8wggGLAgEBMGQwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0AgEBMA0GCWCGSAFlAwQCAQUAMA0GCSqGSIb3DQEBCwUABIIBAIjP3bmY+TrOM0e8n7PeH3OEies1+spNT1n8om4424n/NyIJ9XRyj1QGxshxh6p2BQuUQV8mkWKpHYQJqPobVEcl72ndbHSfzkH2vM57jy/2bCopLt+zWQl0QMA9iKEB3G075wgyD6lcSveZnER/4J6E9+tO6O3R2YFVziwL2UmNR1XgfOhKyNwCfSV1CyVVoSUkkZI7fJ1S6Pce2nLKM1pf+oCWr5vAySd9E4givt/YagGJF+3RHZMEcrqHnnP8kQKi99xnXcIfYyK6VMD9uBb2+4N7MCRDhoY/8+vX9I75paW0UicS6MwacJPueNxLaAboOP4nFSlYhEhZuLiZrdIAAAAAAAA= -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/xcode/xcode-app-receipt-with-transaction: -------------------------------------------------------------------------------- 1 | MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSABIIBdjGCAXIwDwIBAAIBAQQHDAVYY29kZTALAgEBAgEBBAMCAQAwNQIBAgIBAQQtDCtjb20uZXhhbXBsZS5uYXR1cmVsYWIuYmFja3lhcmRiaXJkcy5leGFtcGxlMAsCAQMCAQEEAwwBMTAQAgEEAgEBBAjyv/X7DwAAADAcAgEFAgEBBBQWU6vLoHZxeVVlaOg/UEG2OOKahTAKAgEIAgEBBAIWADAeAgEMAgEBBBYWFDIwMjMtMTAtMTlUMDE6NDU6NDBaMIGRAgERAgEBBIGIMYGFMAwCAgalAgEBBAMCAQEwFwICBqYCAQEEDgwMcGFzcy5wcmVtaXVtMAwCAganAgEBBAMMATAwHwICBqgCAQEEFhYUMjAyMy0xMC0xOVQwMTo0NTozNlowHwICBqwCAQEEFhYUMjAyMy0xMS0xOVQwMTo0NTozNlowDAICBrcCAQEEAwIBATAeAgEVAgEBBBYWFDQwMDEtMDEtMDFUMDA6MDA6MDBaAAAAAAAAoIIDeDCCA3QwggJcoAMCAQICAQEwDQYJKoZIhvcNAQELBQAwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MB4XDTIwMDQwMTE3NTIzNVoXDTQwMDMyNzE3NTIzNVowXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23+QPCxzD9uXJkuTuwr4oSE+yGHZJMheH3U+2pPbMRqRgLm/5QzLPLsORGIm+gQptknnb+Ab5g1ozSVuw3YI9UoLrnp0PMSpC7PPYg/7tLz324ReKOtHDfHti6z1n7AJOKNue8smUAoa4YnRcnYLOUzLT27As1+3lbq5qF1KdKvvb0GlfgmNuj09zXBX2O3v1dp3yJMEHO8JiHhlzoHyjXLnBxpuJhL3MrENuziQawbE/A3llVDNkci6JfRYyYzhcdtKRfMtGZYDVoGmRO51d1tTz3isXbo+X1ArXCmM3cLXKhffIrTX5Hior6htp8HaaC1mzM8pC1As48L75l8SwQIDAQABozswOTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIChDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAsgDgPPHo6WK9wNYdQJ5XuTiQd3ZS0qhLcG64Z5n7s4pVn+8dKLhfKtFznzVHN7tG03YQ8vBp7M1imXH5YIqESDjEvYtnJbmrbDNlrdjCmnhID+nMwScNxs9kPG2AWTOMyjYGKhEbjUnOCP9mwEcoS+tawSsJViylqgkDezIx3OiFeEjOwMUSEWoPDK4vBcpvemR/ICx15kyxEtP94x9eDX24WNegfOR/Y6uXmivDKtjQsuHVWg05G29nKKkSg9aHeG2ZvV6zCuCYzvbqw45taeu3QIE9hz1wUdHEXY2l3H9qWBreYHY3Uuz/rBldDBUvig/1icjXKx0e7CuRBac9TzGCAY8wggGLAgEBMGQwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0AgEBMA0GCWCGSAFlAwQCAQUAMA0GCSqGSIb3DQEBCwUABIIBAMNY9TpOCg59NnKdDA6Xc4D74lEaa+YwQqD/z8ajAGxpw3efoQRvx8Q1qR6IVs9BcRYGyJmsFrau19QeSIRjjqaxhV8ZbRFenWp0Yps6OCPVHw94Ej3AstAL/8WIArBM1OS6OZJESJdQz5xpwavWLGm1rU2730glMdHzHfm2h0wNp/0BKV0ugV9SRQN4RsyAMNS+rCO1mtSDI6nx8E+dEVMIa4mUg+yhXRlg6KzdzKWnr9vDtRVmhdq0ANfP+jfvncsyC+d/c3cAsXOK066hKFwYWTKaRZ7M2eXus5TcU83/aaovHyKVyKKCRnKuP7VPt9d5eWLSg/7v2ctHJtjmhqsAAAAAAAA= -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/xcode/xcode-signed-app-transaction: -------------------------------------------------------------------------------- 1 | eyJ4NWMiOlsiTUlJQnpEQ0NBWEdnQXdJQkFnSUJBVEFLQmdncWhrak9QUVFEQWpCSU1TSXdJQVlEVlFRREV4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1TSXdJQVlEVlFRS0V4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1CNFhEVEl6TVRBeE9UQXhORFV6TmxvWERUSTBNVEF4T0RBeE5EVXpObG93U0RFaU1DQUdBMVVFQXhNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRFaU1DQUdBMVVFQ2hNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQktYRVFnWWpDb3VQdFRzdEdyS3BZOEk1M25IN3JiREhuY0lMR25vZ1NBdWxJSTNzXC91Zk0wZzlEYzNCY3I0OTdBVWd6R1R2V3Bpd0p4cGVCMzcxTmdWK2pUREJLTUJJR0ExVWRFd0VCXC93UUlNQVlCQWY4Q0FRQXdKQVlEVlIwUkJCMHdHNEVaVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEFPQmdOVkhROEJBZjhFQkFNQ0I0QXdDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBTVp2VllKNjRDRitoMmZtc213dnpBY2VQcklEMTNycElKR0JFVytXZ3BwdEFpRUF4V2l5NCtUMXp0MzdWc3UwdmI2WXVtMCtOTHREcUhsSzZycE1jdjZKZm5BPSJdLCJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkFwcGxlX1hjb2RlX0tleSJ9.eyJidW5kbGVJZCI6ImNvbS5leGFtcGxlLm5hdHVyZWxhYi5iYWNreWFyZGJpcmRzLmV4YW1wbGUiLCJhcHBsaWNhdGlvblZlcnNpb24iOiIxIiwiZGV2aWNlVmVyaWZpY2F0aW9uTm9uY2UiOiI0OGM4YjkyZC1jZTBkLTQyMjktYmVkZi1lNjFiNGY5Y2ZjOTIiLCJyZWNlaXB0VHlwZSI6Ilhjb2RlIiwicmVjZWlwdENyZWF0aW9uRGF0ZSI6MTY5NzY4MDEyMjI1Ny40NDcsImRldmljZVZlcmlmaWNhdGlvbiI6ImNZVXNYYzUzRWJZYzBwT2VYRzVkNlwvMzFMR0hlVkdmODRzcVNOME9ySmk1dVwvajJIODlXV0tnUzhOMGhNc01sZiIsInJlcXVlc3REYXRlIjoxNjk3NjgwMTIyMjU3LjQ0Nywib3JpZ2luYWxBcHBsaWNhdGlvblZlcnNpb24iOiIxIiwib3JpZ2luYWxQdXJjaGFzZURhdGUiOi02MjEzNTc2OTYwMDAwMH0.Dpdk_VsO2MUCevwyS407alJpPc1Nq_UIP9EiDHaQBxlyi35NFnsKUVNuFNcGWrGRCCImnb4QGBKHfQC2i4sPCg -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/xcode/xcode-signed-renewal-info: -------------------------------------------------------------------------------- 1 | eyJraWQiOiJBcHBsZV9YY29kZV9LZXkiLCJ0eXAiOiJKV1QiLCJ4NWMiOlsiTUlJQnpEQ0NBWEdnQXdJQkFnSUJBVEFLQmdncWhrak9QUVFEQWpCSU1TSXdJQVlEVlFRREV4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1TSXdJQVlEVlFRS0V4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1CNFhEVEl6TVRBeE9UQXhORFV6TmxvWERUSTBNVEF4T0RBeE5EVXpObG93U0RFaU1DQUdBMVVFQXhNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRFaU1DQUdBMVVFQ2hNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQktYRVFnWWpDb3VQdFRzdEdyS3BZOEk1M25IN3JiREhuY0lMR25vZ1NBdWxJSTNzXC91Zk0wZzlEYzNCY3I0OTdBVWd6R1R2V3Bpd0p4cGVCMzcxTmdWK2pUREJLTUJJR0ExVWRFd0VCXC93UUlNQVlCQWY4Q0FRQXdKQVlEVlIwUkJCMHdHNEVaVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEFPQmdOVkhROEJBZjhFQkFNQ0I0QXdDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBTVp2VllKNjRDRitoMmZtc213dnpBY2VQcklEMTNycElKR0JFVytXZ3BwdEFpRUF4V2l5NCtUMXp0MzdWc3UwdmI2WXVtMCtOTHREcUhsSzZycE1jdjZKZm5BPSJdLCJhbGciOiJFUzI1NiJ9.eyJkZXZpY2VWZXJpZmljYXRpb24iOiJ1K1cxb1FUcXZGSE9RK1pCZTRRMHhQTUMyOGtxRUZ2YmJzRVBwTEtEVlJGdjFHSkdlZ21yTkhWb09ZTU9QdmIyIiwicHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIiwiZGV2aWNlVmVyaWZpY2F0aW9uTm9uY2UiOiIzNDM5OTE5ZS04N2M5LTQ3YjYtYWVlZS0yODIzZjdhOWQzYzMiLCJyZW5ld2FsRGF0ZSI6MTcwMDM1ODMzNjA0OS43Mjk3LCJvcmlnaW5hbFRyYW5zYWN0aW9uSWQiOiIwIiwicmVjZW50U3Vic2NyaXB0aW9uU3RhcnREYXRlIjoxNjk3Njc5OTM2MDQ5LjcyOTcsImF1dG9SZW5ld1N0YXR1cyI6MSwic2lnbmVkRGF0ZSI6MTY5NzY3OTkzNjcxMS4wNzQ3LCJlbnZpcm9ubWVudCI6Ilhjb2RlIiwiYXV0b1JlbmV3UHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIn0.WnT3aB9Lwjbr0ICUGn_5CdglzedVd7eOkrqirhcWFvwJZzN1FajuMV6gFEbgD82aL0Ix6HGZcwkNDlVNLvYOEQ -------------------------------------------------------------------------------- /Tests/AppStoreServerLibraryTests/resources/xcode/xcode-signed-transaction: -------------------------------------------------------------------------------- 1 | eyJraWQiOiJBcHBsZV9YY29kZV9LZXkiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlCekRDQ0FYR2dBd0lCQWdJQkFUQUtCZ2dxaGtqT1BRUURBakJJTVNJd0lBWURWUVFERXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTVNJd0lBWURWUVFLRXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTUI0WERUSXpNVEF4T1RBeE5EVXpObG9YRFRJME1UQXhPREF4TkRVek5sb3dTREVpTUNBR0ExVUVBeE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEVpTUNBR0ExVUVDaE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCS1hFUWdZakNvdVB0VHN0R3JLcFk4STUzbkg3cmJESG5jSUxHbm9nU0F1bElJM3NcL3VmTTBnOURjM0JjcjQ5N0FVZ3pHVHZXcGl3SnhwZUIzNzFOZ1YralREQktNQklHQTFVZEV3RUJcL3dRSU1BWUJBZjhDQVFBd0pBWURWUjBSQkIwd0c0RVpVM1J2Y21WTGFYUWdWR1Z6ZEdsdVp5QnBiaUJZWTI5a1pUQU9CZ05WSFE4QkFmOEVCQU1DQjRBd0NnWUlLb1pJemowRUF3SURTUUF3UmdJaEFNWnZWWUo2NENGK2gyZm1zbXd2ekFjZVBySUQxM3JwSUpHQkVXK1dncHB0QWlFQXhXaXk0K1QxenQzN1ZzdTB2YjZZdW0wK05MdERxSGxLNnJwTWN2NkpmbkE9Il19.eyJpbkFwcE93bmVyc2hpcFR5cGUiOiJQVVJDSEFTRUQiLCJwdXJjaGFzZURhdGUiOjE2OTc2Nzk5MzYwNDkuNzI5Nywic3Vic2NyaXB0aW9uR3JvdXBJZGVudGlmaWVyIjoiNkYzQTkzQUIiLCJzaWduZWREYXRlIjoxNjk3Njc5OTM2MDU2LjQ4NSwib3JpZ2luYWxQdXJjaGFzZURhdGUiOjE2OTc2Nzk5MzYwNDkuNzI5NywiaXNVcGdyYWRlZCI6ZmFsc2UsImRldmljZVZlcmlmaWNhdGlvbiI6InNHRG5wZytvemI4dXdEU3VDRFoyb1ZabzFDS3JiQjh1alI4VnhDeGh5a1J3eUJJSzZ4NlhDeUVSbTh5V3J6RTgiLCJvZmZlclR5cGUiOjEsInF1YW50aXR5IjoxLCJ0cmFuc2FjdGlvbklkIjoiMCIsInR5cGUiOiJBdXRvLVJlbmV3YWJsZSBTdWJzY3JpcHRpb24iLCJ0cmFuc2FjdGlvblJlYXNvbiI6IlBVUkNIQVNFIiwicHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIiwiZXhwaXJlc0RhdGUiOjE3MDAzNTgzMzYwNDkuNzI5NywiZW52aXJvbm1lbnQiOiJYY29kZSIsInN0b3JlZnJvbnRJZCI6IjE0MzQ0MSIsIm9yaWdpbmFsVHJhbnNhY3Rpb25JZCI6IjAiLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlLm5hdHVyZWxhYi5iYWNreWFyZGJpcmRzLmV4YW1wbGUiLCJkZXZpY2VWZXJpZmljYXRpb25Ob25jZSI6IjdlZGVhODdkLTk4ZjAtNDJkMC05NjgyLTQ5Y2E4MTAyMmY3MyIsIndlYk9yZGVyTGluZUl0ZW1JZCI6IjAiLCJzdG9yZWZyb250IjoiVVNBIn0.rkJYnvujStteRkMHhoIR2ThmNFnyKcx5XxIakXYdh-1oKtEVEU5zQAiONaLDpBDO5JhLLrTbfp7LS5tMiqmgHw --------------------------------------------------------------------------------