├── .gitattributes ├── LICENSE ├── README.md ├── client.go ├── go.mod ├── go.sum ├── inapp_purchase_receipt.go ├── latest_receipt_info.go ├── notification.go ├── pending_renewal_info.go ├── receipt.go ├── receipt_request.go └── receipt_response.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # storekit-go 2 | 3 | [![GoDoc](https://godoc.org/github.com/Gurpartap/storekit-go?status.svg)](https://godoc.org/github.com/Gurpartap/storekit-go) 4 | 5 | Use this for verifying App Store receipts. 6 | 7 | - [x] Battle proven technology 8 | - [x] Blockchain free 9 | 10 | See [GoDoc](https://godoc.org/github.com/Gurpartap/storekit-go) for detailed API response reference. 11 | 12 | ## Usage example (auto-renewing subscriptions) 13 | 14 | ```go 15 | package main 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "os" 22 | "time" 23 | 24 | "github.com/Gurpartap/storekit-go" 25 | ) 26 | 27 | func main() { 28 | // Get it from https://AppsToReconnect.apple.com 🤯 29 | appStoreSharedSecret = os.GetEnv("APP_STORE_SHARED_SECRET") 30 | 31 | // Your own userId 32 | userId := "12345" 33 | 34 | // Input coming from either user device or subscription notifications webhook 35 | receiptData := []byte("...") 36 | 37 | err := verifyAndSave(appStoreSharedSecret, userId, receiptData) 38 | if err != nil { 39 | fmt.Println("could not verify receipt:", err) 40 | } 41 | } 42 | 43 | func verifyAndSave(appStoreSharedSecret, userId string, receiptData []byte) error { 44 | // Use .OnProductionEnv() when deploying 45 | // 46 | // storekit-go automatically retries sandbox server upon incompatible 47 | // environment error. This is necessary because App Store Reviewer's purchase 48 | // requests go through the sandbox server instead of production. 49 | // 50 | // Use .WithoutEnvAutoFix() to disable automatic env switching and retrying 51 | // (not recommended on production) 52 | client := storekit.NewVerificationClient().OnSandboxEnv() 53 | 54 | // respBody is raw bytes of response, useful for storing, auditing, and for 55 | // future verification checks. resp is the same parsed and mapped to a struct. 56 | ctx, _ := context.WithTimeout(context.Background(), 15*time.Second) 57 | respBody, resp, err := client.Verify(ctx, &storekit.ReceiptRequest{ 58 | ReceiptData: receiptData, 59 | Password: appStoreSharedSecret, 60 | ExcludeOldTransactions: true, 61 | }) 62 | if err != nil { 63 | return err // code: internal error 64 | } 65 | 66 | if resp.Status != 0 { 67 | return errors.New( 68 | fmt.Sprintf("receipt rejected by App Store with status = %d", resp.Status), 69 | ) // code: permission denied 70 | } 71 | 72 | // If receipt does not contain any active subscription info it is probably a 73 | // fraudulent attempt at activating subscription from a jailbroken device. 74 | if len(resp.LatestReceiptInfo) == 0 { 75 | // keep it 🤫 that we know what's going on 76 | return errors.New("unknown error") // code: internal (instead of invalid argument) 77 | } 78 | 79 | // resp.LatestReceiptInfo works for me. but, alternatively (as Apple devs also 80 | // recommend) you can loop over resp.Receipt.InAppPurchaseReceipt, and filter 81 | // for the receipt with the highest expiresAtMs to find the appropriate latest 82 | // subscription (not shown in this example). if you have multiple subscription 83 | // groups, look for transactions with expiresAt > time.Now(). 84 | for _, latestReceiptInfo := range resp.LatestReceiptInfo { 85 | productID := latestReceiptInfo.ProductId 86 | expiresAtMs := latestReceiptInfo.ExpiresDateMs 87 | // cancelledAtStr := latestReceiptInfo.CancellationDate 88 | 89 | // defensively check for necessary data, because StoreKit API responses can be a 90 | // bit adventurous 91 | if productID == "" { 92 | return errors.New("missing product_id in the latest receipt info") // code: internal error 93 | } 94 | if expiresAtMs == 0 { 95 | return errors.New("missing expiry date in latest receipt info") // code: internal error 96 | } 97 | 98 | expiresAt := time.Unix(0, expiresAtMs*1000000) 99 | 100 | fmt.Printf( 101 | "userId = %s has subscribed for product_id = %s which expires_at = %s", 102 | userId, 103 | productID, 104 | expiresAt, 105 | ) 106 | 107 | // ✅ Save or return productID, expiresAt, cancelledAt, respBody 108 | } 109 | } 110 | 111 | ``` 112 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package storekit 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "io/ioutil" 8 | "net/http" 9 | "unicode" 10 | 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | const ( 15 | sandboxReceiptVerificationURL = "https://sandbox.itunes.apple.com/verifyReceipt" 16 | productionReceiptVerificationURL = "https://buy.itunes.apple.com/verifyReceipt" 17 | ) 18 | 19 | type client struct { 20 | verificationURL string 21 | autofixEnvironment bool 22 | } 23 | 24 | // NewVerificationClient defaults to production verification URL with auto fix 25 | // enabled. 26 | // 27 | // Auto fix automatically handles the incompatible receipt environment error. It 28 | // subsequently gets disabled after the first attempt to avoid unexpected 29 | // looping. 30 | func NewVerificationClient() *client { 31 | return &client{ 32 | verificationURL: productionReceiptVerificationURL, 33 | autofixEnvironment: true, 34 | } 35 | } 36 | 37 | // OnProductionEnv sets the client to use sandbox URL for verification. 38 | func (c *client) OnSandboxEnv() *client { 39 | c.verificationURL = sandboxReceiptVerificationURL 40 | return c 41 | } 42 | 43 | // OnProductionEnv sets the client to use production URL for verification. 44 | func (c *client) OnProductionEnv() *client { 45 | c.verificationURL = productionReceiptVerificationURL 46 | return c 47 | } 48 | 49 | // WithoutEnvAutoFix disables automatic handling of incompatible receipt 50 | // environment error. 51 | func (c *client) WithoutEnvAutoFix() *client { 52 | c.autofixEnvironment = false 53 | return c 54 | } 55 | 56 | func (c *client) Verify(ctx context.Context, req *ReceiptRequest) ([]byte, *ReceiptResponse, error) { 57 | post: 58 | body, err := c.post(ctx, req) 59 | if err != nil { 60 | return nil, nil, err 61 | } 62 | 63 | resp := &ReceiptResponse{} 64 | err = json.Unmarshal( 65 | bytes.Map(func(r rune) rune { 66 | if unicode.IsControl(r) { 67 | return -1 68 | } 69 | return r 70 | }, body), 71 | resp, 72 | ) 73 | if err != nil { 74 | return nil, nil, errors.Wrap(err, "could not unmarshal app store response") 75 | } 76 | 77 | if c.autofixEnvironment { 78 | // Auto fix but only once. 79 | c.autofixEnvironment = false 80 | 81 | switch resp.Status { 82 | case ReceiptResponseStatusSandboxReceiptSentToProduction: 83 | // On a 21007 status, retry the request in the sandbox environment (only if the 84 | // current environment is production – to avoid unexpected loop). 85 | // 86 | // These are receipts from Apple review team. 87 | if c.isProduction() { 88 | c.verificationURL = sandboxReceiptVerificationURL 89 | goto post 90 | } 91 | case ReceiptResponseStatusProductionReceiptSentToSandbox: 92 | // On a 21008 status, retry the request in the production environment (only if 93 | // the current environment is sandbox – to avoid unexpected loop). 94 | if c.isSandbox() { 95 | c.verificationURL = productionReceiptVerificationURL 96 | goto post 97 | } 98 | default: 99 | // TODO: Retry at least once when an App Store internal error occurs here: 100 | // if resp.Status >= 21100 && resp.Status <= 21199 { 101 | // if resp.IsRetryable { 102 | // goto post 103 | // } 104 | // } 105 | break 106 | } 107 | } 108 | 109 | return body, resp, nil 110 | } 111 | 112 | func (c *client) post(ctx context.Context, receiptRequest *ReceiptRequest) ([]byte, error) { 113 | // Prepare request: 114 | 115 | reqJSON, err := json.Marshal(receiptRequest) 116 | if err != nil { 117 | return nil, errors.Wrap(err, "could not marshal receipt request") 118 | } 119 | 120 | // Dial the App Store server: 121 | 122 | buf := bytes.NewReader(reqJSON) 123 | 124 | req, err := http.NewRequest("POST", c.verificationURL, buf) 125 | if err != nil { 126 | return nil, err 127 | } 128 | req.Header.Set("Content-Type", "application/json") 129 | req = req.WithContext(ctx) 130 | r, err := http.DefaultClient.Do(req) 131 | if err != nil { 132 | // TODO: Handle this error (and probably retry at least once): 133 | // Post https://sandbox.itunes.apple.com/verifyReceipt: read tcp 10.1.11.101:36372->17.154.66.159:443: read: connection reset by peer 134 | return nil, errors.Wrap(err, "could not connect to app store server") 135 | } 136 | if r.StatusCode != http.StatusOK { 137 | return nil, errors.New("app store http error (" + r.Status + ")") 138 | } 139 | 140 | // Parse response: 141 | 142 | body, err := ioutil.ReadAll(r.Body) 143 | if err != nil { 144 | return nil, errors.Wrap(err, "could not read app store response") 145 | } 146 | 147 | return body, nil 148 | } 149 | 150 | func (c *client) isSandbox() bool { 151 | return c.verificationURL == sandboxReceiptVerificationURL 152 | } 153 | 154 | func (c *client) isProduction() bool { 155 | return c.verificationURL == productionReceiptVerificationURL 156 | } 157 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Gurpartap/storekit-go 2 | 3 | go 1.14 4 | 5 | require github.com/pkg/errors v0.9.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 2 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 3 | -------------------------------------------------------------------------------- /inapp_purchase_receipt.go: -------------------------------------------------------------------------------- 1 | package storekit 2 | 3 | // InAppPurchaseReceipt is an array that contains the in-app purchase receipt 4 | // fields for all in-app purchase transactions. 5 | // https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt/in_app 6 | type InAppPurchaseReceipt struct { 7 | // The time Apple customer support canceled a transaction, or the time an 8 | // auto-renewable subscription plan was upgraded, in a date-time format similar 9 | // to the ISO 8601. This field is only present for refunded transactions. 10 | CancellationDate string `json:"cancellation_date,omitempty"` 11 | 12 | // The time Apple customer support canceled a transaction, or the time an 13 | // auto-renewable subscription plan was upgraded, in UNIX epoch time format, in 14 | // milliseconds.This field is only present for refunded transactions.Use this 15 | // time format for processing dates. 16 | // information. 17 | CancellationDateMs int64 `json:"cancellation_date_ms,string,omitempty"` 18 | 19 | // The time Apple customer support canceled a transaction, or the time an 20 | // auto-renewable subscription plan was upgraded, in the Pacific Time zone. This 21 | // field is only present for refunded transactions. 22 | CancellationDatePst string `json:"cancellation_date_pst,omitempty"` 23 | 24 | // The reason for a refunded transaction. When a customer cancels a transaction, 25 | // the App Store gives them a refund and provides a value for this key. A value 26 | // of “1” indicates that the customer canceled their transaction due to an 27 | // actual or perceived issue within your app. A value of “0” indicates that the 28 | // transaction was canceled for another reason; for example, if the customer 29 | // made the purchase accidentally. 30 | CancellationReason string `json:"cancellation_reason,omitempty"` 31 | 32 | // The time a subscription expires or when it will renew, in a date-time format 33 | // similar to the ISO 8601. 34 | ExpiresDate string `json:"expires_date,omitempty"` 35 | 36 | // The time a subscription expires or when it will renew, in UNIX epoch time 37 | // format, in milliseconds.Use this time format for processing dates. 38 | ExpiresDateMs int64 `json:"expires_date_ms,string,omitempty"` 39 | 40 | // The time a subscription expires or when it will renew, in the Pacific Time 41 | // zone. 42 | ExpiresDatePst string `json:"expires_date_pst,omitempty"` 43 | 44 | // A value that indicates whether the user is the purchaser of the product, or 45 | // is a family member with access to the product through Family Sharing. 46 | // Possible Values: 47 | // - FAMILY_SHARED: The transaction belongs to a family member who 48 | // benefits from service. 49 | // - PURCHASED: The transaction belongs to the purchaser. 50 | InAppOwnershipType InAppOwnershipType `json:"in_app_ownership_type,omitempty"` 51 | 52 | // An indicator of whether an auto-renewable subscription is in the introductory 53 | // price period. 54 | IsInIntroOfferPeriod string `json:"is_in_intro_offer_period,omitempty"` 55 | 56 | // An indication of whether a subscription is in the free trial period. 57 | IsTrialPeriod string `json:"is_trial_period,omitempty"` 58 | 59 | // An indicator that a subscription has been canceled due to an upgrade. This 60 | // field is only present for upgrade transactions. 61 | // 62 | // Although not documented, this field helps maintain compatibility with LatestReceiptInfo 63 | IsUpgraded string `json:"is_upgraded,omitempty"` 64 | 65 | // The reference name of a subscription offer that you configured in App Store 66 | // Connect. This field is present when a customer redeemed a subscription offer 67 | // code. For more information about offer codes, see [Set Up Offer Codes](https://help.apple.com/app-store-connect/#/dev6a098e4b1), 68 | // and [Implementing Offer Codes in Your App](https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/implementing_offer_codes_in_your_app). 69 | // 70 | // Although not documented, this field helps maintain compatibility with LatestReceiptInfo 71 | OfferCodeRefName string `json:"offer_code_ref_name,omitempty"` 72 | 73 | // The time of the original in-app purchase, in a date-time format similar to 74 | // ISO 8601. 75 | OriginalPurchaseDate string `json:"original_purchase_date,omitempty"` 76 | 77 | // The time of the original in-app purchase, in UNIX epoch time format, in 78 | // milliseconds. For an auto-renewable subscription, this value indicates the 79 | // date of the subscription's initial purchase. The original purchase date 80 | // applies to all product types and remains the same in all transactions for the 81 | // same product ID. This value corresponds to the original transaction’s 82 | // transactionDate property in StoreKit. Use this time format for processing 83 | // dates. 84 | OriginalPurchaseDateMs int64 `json:"original_purchase_date_ms,string,omitempty"` 85 | 86 | // The time of the original in-app purchase, in the Pacific Time zone. 87 | OriginalPurchaseDatePst string `json:"original_purchase_date_pst,omitempty"` 88 | 89 | // OriginalTransactionIdentifier is the transaction identifier of the original 90 | // transaction for a transaction that restores a previous transaction. 91 | // Otherwise, identical to the transaction identifier. 92 | OriginalTransactionId string `json:"original_transaction_id,omitempty"` 93 | 94 | // The unique identifier of the product purchased. You provide this value when 95 | // creating the product in App Store Connect, and it corresponds to the 96 | // productIdentifier property of the SKPayment object stored in the 97 | // transaction's payment property. 98 | ProductId string `json:"product_id,omitempty"` 99 | 100 | // The identifier of the subscription offer redeemed by the user. 101 | PromotionalOfferId string `json:"promotional_offer_id,omitempty"` 102 | 103 | // The time the App Store charged the user's account for a purchased or restored 104 | // product, or the time the App Store charged the user’s account for a 105 | // subscription purchase or renewal after a lapse, in a date-time format similar 106 | // to ISO 8601. 107 | PurchaseDate string `json:"purchase_date,omitempty"` 108 | 109 | // For consumable, non-consumable, and non-renewing subscription products, the 110 | // time the App Store charged the user's account for a purchased or restored 111 | // product, in the UNIX epoch time format, in milliseconds. For auto-renewable 112 | // subscriptions, the time the App Store charged the user’s account for a 113 | // subscription purchase or renewal after a lapse, in the UNIX epoch time 114 | // format, in milliseconds. Use this time format for processing dates. 115 | PurchaseDateMs int64 `json:"purchase_date_ms,string,omitempty"` 116 | 117 | // The time the App Store charged the user's account for a purchased or restored 118 | // product, or the time the App Store charged the user’s account for a 119 | // subscription purchase or renewal after a lapse, in the Pacific Time zone. 120 | PurchaseDatePst string `json:"purchase_date_pst,omitempty"` 121 | 122 | // The number of consumable products purchased. This value corresponds to the 123 | // quantity property of the SKPayment object stored in the transaction's payment 124 | // property. The value is usually “1” unless modified with a mutable payment. 125 | // The maximum value is 10. 126 | Quantity int `json:"quantity,string,omitempty"` 127 | 128 | // The identifier of the subscription group to which the subscription belongs. The 129 | // value for this field is identical to the subscriptionGroupIdentifier property in 130 | // SKProduct. 131 | // 132 | // Although not documented, this field helps maintain compatibility with LatestReceiptInfo 133 | SubscriptionGroupIdentifier string `json:"subscription_group_identifier,omitempty"` 134 | 135 | // A unique identifier for a transaction such as a purchase, restore, or 136 | // renewal. 137 | // 138 | // This value corresponds to the transaction’s transactionIdentifier property. 139 | // 140 | // For a transaction that restores a previous transaction, this value is 141 | // different from the transaction identifier of the original purchase 142 | // transaction. In an auto-renewable subscription receipt, a new value for the 143 | // transaction identifier is generated every time the subscription automatically 144 | // renews or is restored on a new device. 145 | TransactionId string `json:"transaction_id,omitempty"` 146 | 147 | // A unique identifier for purchase events across devices, including 148 | // subscription-renewal events. This value is the primary key for identifying 149 | // subscription purchases. 150 | WebOrderLineItemId string `json:"web_order_line_item_id,omitempty"` 151 | } 152 | -------------------------------------------------------------------------------- /latest_receipt_info.go: -------------------------------------------------------------------------------- 1 | package storekit 2 | 3 | // InAppOwnershipType is the relationship of the user with the family-shared 4 | // purchase to which they have access. 5 | // 6 | // When family members benefit from a shared subscription, App Store updates 7 | // their receipt to include the family-shared purchase. Use the value of 8 | // in_app_ownership_type to understand whether a transaction belongs to the 9 | // purchaser or a family member who benefits. This field appears in the App 10 | // Store server notifications unified receipt 11 | // (unified_receipt.Latest_receipt_info) and in transaction receipts 12 | // (responseBody.Latest_receipt_info). For more information about Family 13 | // Sharing, see Supporting Family Sharing in Your App. 14 | type InAppOwnershipType string 15 | 16 | const ( 17 | InAppOwnershipTypeFamilyShared InAppOwnershipType = "FAMILY_SHARED" 18 | InAppOwnershipTypePurchased InAppOwnershipType = "PURCHASED" 19 | ) 20 | 21 | // LatestReceiptInfo is an array that contains all in-app purchase transactions. 22 | // https://developer.apple.com/documentation/appstorereceipts/responsebody/latest_receipt_info 23 | type LatestReceiptInfo struct { 24 | // The time Apple customer support canceled a transaction, in a date-time format 25 | // similar to the ISO 8601. This field is only present for refunded 26 | // transactions. 27 | CancellationDate string `json:"cancellation_date,omitempty"` 28 | 29 | // The time Apple customer support canceled a transaction, or the time an 30 | // auto-renewable subscription plan was upgraded, in UNIX epoch time format, in 31 | // milliseconds. This field is only present for refunded transactions. Use this 32 | // time format for processing dates. 33 | // information. 34 | CancellationDateMs int64 `json:"cancellation_date_ms,string,omitempty"` 35 | 36 | // The time Apple customer support canceled a transaction, in the Pacific Time 37 | // zone. This field is only present for refunded transactions. 38 | CancellationDatePst string `json:"cancellation_date_pst,omitempty"` 39 | 40 | // The reason for a refunded transaction. When a customer cancels a transaction, 41 | // the App Store gives them a refund and provides a value for this key. A value 42 | // of “1” indicates that the customer canceled their transaction due to an 43 | // actual or perceived issue within your app. A value of “0” indicates that the 44 | // transaction was canceled for another reason; for example, if the customer 45 | // made the purchase accidentally. 46 | CancellationReason string `json:"cancellation_reason,omitempty"` 47 | 48 | // The time a subscription expires or when it will renew, in a date-time format 49 | // similar to the ISO 8601. 50 | ExpiresDate string `json:"expires_date,omitempty"` 51 | 52 | // The time a subscription expires or when it will renew, in UNIX epoch time 53 | // format, in milliseconds. Use this time format for processing dates. 54 | ExpiresDateMs int64 `json:"expires_date_ms,string,omitempty"` 55 | 56 | // The time a subscription expires or when it will renew, in the Pacific Time 57 | // zone. 58 | ExpiresDatePst string `json:"expires_date_pst,omitempty"` 59 | 60 | // A value that indicates whether the user is the purchaser of the product, or 61 | // is a family member with access to the product through Family Sharing. 62 | // Possible Values: 63 | // - FAMILY_SHARED: The transaction belongs to a family member who 64 | // benefits from service. 65 | // - PURCHASED: The transaction belongs to the purchaser. 66 | InAppOwnershipType InAppOwnershipType `json:"in_app_ownership_type,omitempty"` 67 | 68 | // An indicator of whether an auto-renewable subscription is in the introductory 69 | // price period. 70 | IsInIntroOfferPeriod string `json:"is_in_intro_offer_period,omitempty"` 71 | 72 | // An indicator of whether a subscription is in the free trial period. 73 | IsTrialPeriod string `json:"is_trial_period,omitempty"` 74 | 75 | // An indicator that a subscription has been canceled due to an upgrade. This 76 | // field is only present for upgrade transactions. 77 | IsUpgraded string `json:"is_upgraded,omitempty"` 78 | 79 | // The reference name of a subscription offer that you configured in App Store 80 | // Connect. This field is present when a customer redeemed a subscription offer 81 | // code. For more information about offer codes, see [Set Up Offer Codes](https://help.apple.com/app-store-connect/#/dev6a098e4b1), 82 | // and [Implementing Offer Codes in Your App](https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/implementing_offer_codes_in_your_app). 83 | OfferCodeRefName string `json:"offer_code_ref_name,omitempty"` 84 | 85 | // The time of the original app purchase, in a date-time format similar to ISO 86 | // 8601. 87 | OriginalPurchaseDate string `json:"original_purchase_date,omitempty"` 88 | 89 | // The time of the original app purchase, in UNIX epoch time format, in 90 | // milliseconds. Use this time format for processing dates. For an 91 | // auto-renewable subscription, this value indicates the date of the 92 | // subscription's initial purchase. The original purchase date applies to all 93 | // product types and remains the same in all transactions for the same product 94 | // ID. This value corresponds to the original transaction’s transactionDate 95 | // property in StoreKit. 96 | OriginalPurchaseDateMs int64 `json:"original_purchase_date_ms,string,omitempty"` 97 | 98 | // The time of the original app purchase, in the Pacific Time zone. 99 | OriginalPurchaseDatePst string `json:"original_purchase_date_pst,omitempty"` 100 | 101 | // The transaction identifier of the original purchase. 102 | OriginalTransactionId string `json:"original_transaction_id,omitempty"` 103 | 104 | // The unique identifier of the product purchased. You provide this value when 105 | // creating the product in App Store Connect, and it corresponds to the 106 | // productIdentifier property of the SKPayment object stored in the transaction's 107 | // payment property. 108 | ProductId string `json:"product_id,omitempty"` 109 | 110 | // The identifier of the subscription offer redeemed by the user. 111 | PromotionalOfferId string `json:"promotional_offer_id,omitempty"` 112 | 113 | // The time the App Store charged the user's account for a purchased or restored 114 | // product, or the time the App Store charged the user’s account for a subscription 115 | // purchase or renewal after a lapse, in a date-time format similar to ISO 8601. 116 | PurchaseDate string `json:"purchase_date,omitempty"` 117 | 118 | // For consumable, non-consumable, and non-renewing subscription products, the time 119 | // the App Store charged the user's account for a purchased or restored product, in 120 | // the UNIX epoch time format, in milliseconds. For auto-renewable subscriptions, the 121 | // time the App Store charged the user’s account for a subscription purchase or 122 | // renewal after a lapse, in the UNIX epoch time format, in milliseconds. Use this 123 | // time format for processing dates. 124 | PurchaseDateMs int64 `json:"purchase_date_ms,string,omitempty"` 125 | 126 | // The time the App Store charged the user's account for a purchased or restored 127 | // product, or the time the App Store charged the user’s account for a subscription 128 | // purchase or renewal after a lapse, in the Pacific Time zone. 129 | PurchaseDatePst string `json:"purchase_date_pst,omitempty"` 130 | 131 | // The number of consumable products purchased. This value corresponds to the 132 | // quantity property of the SKPayment object stored in the transaction's payment 133 | // property. The value is usually “1” unless modified with a mutable payment. The 134 | // maximum value is 10. 135 | Quantity int `json:"quantity,string,omitempty"` 136 | 137 | // The identifier of the subscription group to which the subscription belongs. The 138 | // value for this field is identical to the subscriptionGroupIdentifier property in 139 | // SKProduct. 140 | SubscriptionGroupIdentifier string `json:"subscription_group_identifier,omitempty"` 141 | 142 | // A unique identifier for a transaction such as a purchase, restore, or renewal. 143 | TransactionId string `json:"transaction_id,omitempty"` 144 | 145 | // A unique identifier for purchase events across devices, including 146 | // subscription-renewal events. This value is the primary key for identifying 147 | // subscription purchases. 148 | WebOrderLineItemId string `json:"web_order_line_item_id,omitempty"` 149 | } 150 | -------------------------------------------------------------------------------- /notification.go: -------------------------------------------------------------------------------- 1 | package storekit 2 | 3 | // NotificationType is the type that describes the in-app purchase event for 4 | // which the App Store sent the notification. 5 | // 6 | // You receive and can react to server notifications in real time for the 7 | // subscription and refund events that these notification type values describe. 8 | // The notification_type appears in the responseBody. 9 | // 10 | // https://developer.apple.com/documentation/appstoreservernotifications/notification_type 11 | type NotificationType string 12 | 13 | const ( 14 | // Indicates that either Apple customer support canceled the subscription or the 15 | // user upgraded their subscription. The cancellation_date key contains the date 16 | // and time of the change. 17 | NotificationTypeCancel NotificationType = "CANCEL" 18 | 19 | // Indicates that the customer made a change in their subscription plan that 20 | // takes effect at the next renewal. The currently active plan isn’t affected. 21 | NotificationTypeDidChangeRenewalPref NotificationType = "DID_CHANGE_RENEWAL_PREF" 22 | 23 | // Indicates a change in the subscription renewal status. In the JSON response, 24 | // check auto_renew_status_change_date_ms to know the date and time of the last 25 | // status update. Check auto_renew_status to know the current renewal status. 26 | NotificationTypeDidChangeRenewalStatus NotificationType = "DID_CHANGE_RENEWAL_STATUS" 27 | 28 | // Indicates a subscription that failed to renew due to a billing issue. Check 29 | // is_in_billing_retry_period to know the current retry status of the 30 | // subscription. Check grace_period_expires_date to know the new service 31 | // expiration date if the subscription is in a billing grace period. 32 | NotificationTypeDidFailToRenew NotificationType = "DID_FAIL_TO_RENEW" 33 | 34 | // Indicates a successful automatic renewal of an expired subscription that 35 | // failed to renew in the past. Check expires_date to determine the next renewal 36 | // date and time. 37 | NotificationTypeDidRecover NotificationType = "DID_RECOVER" 38 | 39 | // Indicates that a customer’s subscription has successfully auto-renewed for a 40 | // new transaction period. 41 | NotificationTypeDidRenew NotificationType = "DID_RENEW" 42 | 43 | // Occurs at the user’s initial purchase of the subscription. Store 44 | // latest_receipt on your server as a token to verify the user’s subscription 45 | // status at any time by validating it with the App Store. 46 | NotificationTypeInitialBuy NotificationType = "INITIAL_BUY" 47 | 48 | // Indicates the customer renewed a subscription interactively, either by using 49 | // your app’s interface, or on the App Store in the account’s Subscriptions 50 | // settings. Make service available immediately. 51 | NotificationTypeInteractiveRenewal NotificationType = "INTERACTIVE_RENEWAL" 52 | 53 | // Indicates that App Store has started asking the customer to consent to your 54 | // app’s subscription price increase. In the 55 | // unified_receipt.Pending_renewal_info object, the price_consent_status value 56 | // is 0, indicating that App Store is asking for the customer’s consent, and 57 | // hasn't received it. The subscription won’t auto-renew unless the user agrees 58 | // to the new price. When the customer agrees to the price increase, the system 59 | // sets price_consent_status to 1. Check the receipt using verifyReceipt to view 60 | // the updated price-consent status. 61 | NotificationTypePriceIncreaseConsent NotificationType = "PRICE_INCREASE_CONSENT" 62 | 63 | // Indicates that App Store successfully refunded a transaction. The 64 | // cancellation_date_ms contains the timestamp of the refunded transaction. The 65 | // original_transaction_id and product_id identify the original transaction and 66 | // product. The cancellation_reason contains the reason. 67 | NotificationTypeRefund NotificationType = "REFUND" 68 | ) 69 | 70 | // UnifiedReceipt is an object that contains information about the most-recent, 71 | // in-app purchase transactions for the app. 72 | // 73 | // https://developer.apple.com/documentation/appstoreservernotifications/unified_receipt 74 | type UnifiedReceipt struct { 75 | // The environment for which App Store generated the receipt. 76 | // Possible values: Sandbox, Production 77 | Environment string `json:"environment,omitempty"` 78 | 79 | // The latest Base64-encoded app receipt. 80 | LatestReceipt []byte `json:"latest_receipt,omitempty"` 81 | 82 | // An array that contains the latest 100 in-app purchase transactions of the 83 | // decoded value in latest_receipt. This array excludes transactions for 84 | // consumable products your app has marked as finished. The contents of this 85 | // array are identical to those in LatestReceiptInfo in the 86 | // verifyReceipt endpoint response for receipt validation. 87 | LatestReceiptInfo []LatestReceiptInfo `json:"latest_receipt_info,omitempty"` 88 | 89 | // An array where each element contains the pending renewal information for each 90 | // auto-renewable subscription identified in product_id. The contents of this 91 | // array are identical to those in PendingRenewalInfo in the 92 | // verifyReceipt endpoint response for receipt validation. 93 | PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info,omitempty"` 94 | 95 | // The status code, where 0 indicates that the notification is valid. 96 | Status int `json:"status,omitempty"` 97 | } 98 | 99 | // Notification is the JSON data sent in the server notification from the App 100 | // Store. 101 | // 102 | // Use the information in the response body to react quickly to changes in your 103 | // users’ subscription states. The fields available in any one notification sent 104 | // to your server are dependent on the notification_type, which indicates the 105 | // event that triggered the notification. 106 | // 107 | // https://developer.apple.com/documentation/appstoreservernotifications/responsebody 108 | type Notification struct { 109 | // An identifier that App Store Connect generates and the App Store uses to 110 | // uniquely identify the auto-renewable subscription that the user’s 111 | // subscription renews. Treat this value as a 64-bit integer. 112 | AutoRenewAdamId string `json:"auto_renew_adam_id,omitempty"` 113 | 114 | // The product identifier of the auto-renewable subscription that the user’s 115 | // subscription renews. 116 | AutoRenewProductId string `json:"auto_renew_product_id,omitempty"` 117 | 118 | // The current renewal status for an auto-renewable subscription product. Note 119 | // that these values are different from those of the auto_renew_status in the 120 | // receipt. 121 | // Possible values: true, false 122 | AutoRenewStatus string `json:"auto_renew_status,omitempty"` 123 | 124 | // The time at which the user turned on or off the renewal status for an 125 | // auto-renewable subscription, in a date-time format similar to the ISO 8601 126 | // standard. 127 | AutoRenewStatusChangeDate string `json:"auto_renew_status_change_date,omitempty"` 128 | 129 | // The time at which the user turned on or off the renewal status for an 130 | // auto-renewable subscription, in UNIX epoch time format, in milliseconds. Use 131 | // this time format to process dates. 132 | AutoRenewStatusChangeDateMs int64 `json:"auto_renew_status_change_date_ms,string,omitempty"` 133 | 134 | // The time at which the user turned on or off the renewal status for an 135 | // auto-renewable subscription, in the Pacific time zone. 136 | AutoRenewStatusChangeDatePst string `json:"auto_renew_status_change_date_pst,omitempty"` 137 | 138 | // The environment for which App Store generated the receipt. 139 | // Possible values: Sandbox, PROD 140 | Environment string `json:"environment,omitempty"` 141 | 142 | // The reason a subscription expired. This field is only present for an expired 143 | // auto-renewable subscription. See expiration_intent for more information. 144 | ExpirationIntent ExpirationIntent `json:"expiration_intent,omitempty"` 145 | 146 | // The subscription event that triggered the notification. 147 | NotificationType NotificationType `json:"notification_type,omitempty"` 148 | 149 | // The same value as the shared secret you submit in the password field of the 150 | // requestBody when validating receipts. 151 | Password string `json:"password,omitempty"` 152 | 153 | // An object that contains information about the most-recent, in-app purchase 154 | // transactions for the app. 155 | UnifiedReceipt UnifiedReceipt `json:"unified_receipt,omitempty"` 156 | 157 | // A string that contains the app bundle ID. 158 | Bid string `json:"bid,omitempty"` 159 | 160 | // A string that contains the app bundle version. 161 | Bvrs string `json:"bvrs,omitempty"` 162 | } 163 | -------------------------------------------------------------------------------- /pending_renewal_info.go: -------------------------------------------------------------------------------- 1 | package storekit 2 | 3 | // AutoRenewStatus is returned in the JSON response, in the 4 | // responseBody.Pending_renewal_info array. 5 | // 6 | // The value for this field should not be interpreted as the customer’s 7 | // subscription status. You can use this value to display an alternative 8 | // subscription product in your app, such as a lower-level subscription plan to 9 | // which the user can downgrade from their current plan. Consider presenting an 10 | // attractive upgrade or downgrade offer in the same subscription group, if the 11 | // auto_renew_status value is “0”. See Engineering Subscriptions from WWDC 2018 12 | // for more information. 13 | // 14 | // https://developer.apple.com/documentation/appstorereceipts/auto_renew_status 15 | type AutoRenewStatus string 16 | 17 | const ( 18 | // The customer has turned off automatic renewal for the subscription. 19 | AutoRenewStatusOff AutoRenewStatus = "0" 20 | 21 | // The subscription will renew at the end of the current subscription period. 22 | AutoRenewStatusOn AutoRenewStatus = "1" 23 | ) 24 | 25 | // BillingRetryStatus indicates whether Apple is attempting to renew an expired 26 | // subscription automatically. 27 | // 28 | // If the customer’s subscription failed to renew because the App Store was 29 | // unable to complete the transaction, this value reflects whether the App Store 30 | // is still trying to renew the subscription. The subscription retry flag is 31 | // solely indicative of whether a subscription is in a billing retry state. Use 32 | // this value in conjunction with expiration_intent, expires_date, and 33 | // transaction_id for more insight. 34 | // 35 | // You can use this field to: 36 | // 37 | // - Determine that the user has been billed successfully, if this field has been 38 | // removed and there is a new transaction with a future expires_date. Inform the 39 | // user that there may be an issue with their billing information, if the value 40 | // is “1”. For example, an expired credit card or insufficient balance could 41 | // prevent this customer's account from being billed. 42 | // 43 | // - Implement a grace period to improve recovery, if the value is “1” and the 44 | // expires_date is in the past. A grace period is free or limited subscription 45 | // access while a subscriber is in a billing retry state. See Engineering 46 | // Subscriptions from WWDC 2018 for more information. 47 | // 48 | // https://developer.apple.com/documentation/appstorereceipts/is_in_billing_retry_period 49 | type BillingRetryStatus string 50 | 51 | const ( 52 | // The App Store has stopped attempting to renew the subscription. 53 | BillingRetryStatusStoppedAttemptingRenewal BillingRetryStatus = "0" 54 | 55 | // The App Store is attempting to renew the subscription. 56 | BillingRetryStatusAttemptingRenewal BillingRetryStatus = "1" 57 | ) 58 | 59 | // ExpirationIntent is the reason a subscription expired. 60 | type ExpirationIntent string 61 | 62 | const ( 63 | // The customer voluntarily canceled their subscription. 64 | ExpirationIntentVoluntarilyCancelled ExpirationIntent = "1" 65 | 66 | // Billing error for example, the customer's payment information was no longer 67 | // valid. 68 | ExpirationIntentBillingIssue ExpirationIntent = "2" 69 | 70 | // The customer did not agree to a recent price increase. 71 | ExpirationIntentDidNotAcceptPriceIncrease ExpirationIntent = "3" 72 | 73 | // The product was not available for purchase at the time of renewal. 74 | ExpirationIntentProductNotAvailable ExpirationIntent = "4" 75 | 76 | // Unknown error. 77 | ExpirationIntentUnknown ExpirationIntent = "5" 78 | ) 79 | 80 | type PriceConsentStatus string 81 | 82 | const ( 83 | // The App Store hasn't yet requested consent from the customer. 84 | PriceConsentStatusNotRequested PriceConsentStatus = "" 85 | 86 | // The App Store is asking for the customer's consent, and hasn't received it. 87 | PriceConsentStatusAwaitingConsent PriceConsentStatus = "0" 88 | 89 | // The App Store has received customer's consent. 90 | PriceConsentStatusConsented PriceConsentStatus = "1" 91 | ) 92 | 93 | // PendingRenewalInfo is an array of elements that refers to auto-renewable 94 | // subscription renewals that are open or failed in the past. 95 | // https://developer.apple.com/documentation/appstorereceipts/responsebody/pending_renewal_info 96 | type PendingRenewalInfo struct { 97 | // The current renewal preference for the auto-renewable subscription. The value 98 | // for this key corresponds to the productIdentifier property of the product 99 | // that the customer’s subscription renews. This field is only present if the 100 | // user downgrades or crossgrades to a subscription of a different duration for 101 | // the subsequent subscription period. 102 | AutoRenewProductId string `json:"auto_renew_product_id,omitempty"` 103 | 104 | // The current renewal status for the auto-renewable subscription. 105 | AutoRenewStatus AutoRenewStatus `json:"auto_renew_status,omitempty"` 106 | 107 | // The reason a subscription expired. This field is only present for a receipt 108 | // that contains an expired auto-renewable subscription. 109 | ExpirationIntent ExpirationIntent `json:"expiration_intent,omitempty"` 110 | 111 | // The time at which the grace period for subscription renewals expires, in a 112 | // date-time format similar to the ISO 8601. 113 | GracePeriodExpiresDate string `json:"grace_period_expires_date,omitempty"` 114 | 115 | // The time at which the grace period for subscription renewals expires, in UNIX 116 | // epoch time format, in milliseconds. This key is only present for apps that 117 | // have Billing Grace Period enabled and when the user experiences a billing 118 | // error at the time of renewal. Use this time format for processing dates. 119 | GracePeriodExpiresDateMs int64 `json:"grace_period_expires_date_ms,string,omitempty"` 120 | 121 | // The time at which the grace period for subscription renewals expires, in the 122 | // Pacific Time zone. 123 | GracePeriodExpiresDatePst string `json:"grace_period_expires_date_pst,omitempty"` 124 | 125 | // A flag that indicates Apple is attempting to renew an expired subscription 126 | // automatically. This field is only present if an auto-renewable subscription 127 | // is in the billing retry state. 128 | IsInBillingRetryPeriod BillingRetryStatus `json:"is_in_billing_retry_period,omitempty"` 129 | 130 | // The reference name of a subscription offer that you configured in App Store 131 | // Connect. This field is present when a customer redeemed a subscription offer 132 | // code. For more information about offer codes, see [Set Up Offer Codes](https://help.apple.com/app-store-connect/#/dev6a098e4b1), 133 | // and [Implementing Offer Codes in Your App](https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/implementing_offer_codes_in_your_app). 134 | OfferCodeRefName string `json:"offer_code_ref_name,omitempty"` 135 | 136 | // The transaction identifier of the original purchase. 137 | OriginalTransactionId string `json:"original_transaction_id,omitempty"` 138 | 139 | // The price consent status for a subscription price increase. This field is 140 | // only present if the customer was notified of the price increase. The default 141 | // value is "0" and changes to "1" if the customer consents. 142 | PriceConsentStatus PriceConsentStatus `json:"price_consent_status,omitempty"` 143 | 144 | // The unique identifier of the product purchased. You provide this value when 145 | // creating the product in App Store Connect, and it corresponds to the 146 | // productIdentifier property of the SKPayment object stored in the 147 | // transaction's payment property. 148 | ProductId string `json:"product_id,omitempty"` 149 | } 150 | -------------------------------------------------------------------------------- /receipt.go: -------------------------------------------------------------------------------- 1 | package storekit 2 | 3 | // Receipt is the decoded version of the encoded receipt data sent with the request to the App Store. 4 | // https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt 5 | type Receipt struct { 6 | // See app_item_id. 7 | AdamId int64 `json:"adam_id,omitempty"` 8 | 9 | // Generated by App Store Connect and used by the App Store to uniquely identify 10 | // the app purchased. Apps are assigned this identifier only in production. 11 | // Treat this value as a 64-bit long integer. 12 | AppItemId int64 `json:"app_item_id,omitempty"` 13 | 14 | // The app’s version number. The app's version number corresponds to the value 15 | // of CFBundleVersion (in iOS) or CFBundleShortVersionString (in macOS) in the 16 | // Info.plist. In production, this value is the current version of the app on 17 | // the device based on the receipt_creation_date_ms. In the sandbox, the value 18 | // is always "1.0". 19 | ApplicationVersion string `json:"application_version,omitempty"` 20 | 21 | // The bundle identifier for the app to which the receipt belongs. You provide 22 | // this string on App Store Connect. This corresponds to the value of 23 | // CFBundleIdentifier in the Info.plist file of the app. 24 | BundleId string `json:"bundle_id,omitempty"` 25 | 26 | // A unique identifier for the app download transaction. 27 | DownloadId int64 `json:"download_id,omitempty"` 28 | 29 | // The time the receipt expires for apps purchased through the Volume Purchase 30 | // Program, in a date-time format similar to the ISO 8601. 31 | ExpirationDate string `json:"expiration_date,omitempty"` 32 | 33 | // The time the receipt expires for apps purchased through the Volume Purchase 34 | // Program, in UNIX epoch time format, in milliseconds. If this key is not 35 | // present for apps purchased through the Volume Purchase Program, the receipt 36 | // does not expire. Use this time format for processing dates. 37 | ExpirationDateMs int64 `json:"expiration_date_ms,string,omitempty"` 38 | 39 | // The time the receipt expires for apps purchased through the Volume Purchase 40 | // Program, in the Pacific Time zone. 41 | ExpirationDatePst string `json:"expiration_date_pst,omitempty"` 42 | 43 | // An array that contains the in-app purchase receipt fields for all in-app 44 | // purchase transactions. 45 | InApp []InAppPurchaseReceipt `json:"in_app,omitempty"` 46 | 47 | // The version of the app that the user originally purchased. This value does 48 | // not change, and corresponds to the value of CFBundleVersion (in iOS) or 49 | // CFBundleShortVersionString (in macOS) in the Info.plist file of the original 50 | // purchase. In the sandbox environment, the value is always "1.0". 51 | OriginalApplicationVersion string `json:"original_application_version,omitempty"` 52 | 53 | // The time of the original app purchase, in a date-time format similar to ISO 54 | // 8601. 55 | OriginalPurchaseDate string `json:"original_purchase_date,omitempty"` 56 | 57 | // The time of the original app purchase, in UNIX epoch time format, in 58 | // milliseconds. Use this time format for processing dates. 59 | OriginalPurchaseDateMs int64 `json:"original_purchase_date_ms,string,omitempty"` 60 | 61 | // The time of the original app purchase, in the Pacific Time zone. 62 | OriginalPurchaseDatePst string `json:"original_purchase_date_pst,omitempty"` 63 | 64 | // The time the user ordered the app available for pre-order, in a date-time 65 | // format similar to ISO 8601. 66 | PreorderDate string `json:"preorder_date,omitempty"` 67 | 68 | // The time the user ordered the app available for pre-order, in UNIX epoch time 69 | // format, in milliseconds. This field is only present if the user pre-orders 70 | // the app. Use this time format for processing dates. 71 | PreorderDateMs int64 `json:"preorder_date_ms,string,omitempty"` 72 | 73 | // The time the user ordered the app available for pre-order, in the Pacific 74 | // Time zone. 75 | PreorderDatePst string `json:"preorder_date_pst,omitempty"` 76 | 77 | // The time the App Store generated the receipt, in a date-time format similar 78 | // to ISO 8601. 79 | ReceiptCreationDate string `json:"receipt_creation_date,omitempty"` 80 | 81 | // The time the App Store generated the receipt, in UNIX epoch time format, in 82 | // milliseconds. Use this time format for processing dates. This value does not 83 | // change. 84 | ReceiptCreationDateMs int64 `json:"receipt_creation_date_ms,string,omitempty"` 85 | 86 | // The time the App Store generated the receipt, in the Pacific Time zone. 87 | ReceiptCreationDatePst string `json:"receipt_creation_date_pst,omitempty"` 88 | 89 | // The type of receipt generated. The value corresponds to the environment in 90 | // which the app or VPP purchase was made. Possible values: Production, 91 | // ProductionVPP, ProductionSandbox, ProductionVPPSandbox 92 | ReceiptType string `json:"receipt_type,omitempty"` 93 | 94 | // The time the request to the verifyReceipt endpoint was processed and the 95 | // response was generated, in a date-time format similar to ISO 8601. 96 | RequestDate string `json:"request_date,omitempty"` 97 | 98 | // The time the request to the verifyReceipt endpoint was processed and the 99 | // response was generated, in UNIX epoch time format, in milliseconds. Use this 100 | // time format for processing dates. 101 | RequestDateMs int64 `json:"request_date_ms,string,omitempty"` 102 | 103 | // The time the request to the verifyReceipt endpoint was processed and the 104 | // response was generated, in the Pacific Time zone. 105 | RequestDatePst string `json:"request_date_pst,omitempty"` 106 | 107 | // An arbitrary number that identifies a revision of your app. In the sandbox, 108 | // this key's value is “0”. Use this value to identify the version of the app 109 | // that the customer bought. 110 | VersionExternalIdentifier int64 `json:"version_external_identifier,omitempty"` 111 | } 112 | -------------------------------------------------------------------------------- /receipt_request.go: -------------------------------------------------------------------------------- 1 | package storekit 2 | 3 | // ReceiptRequest is the JSON you submit with the request to the App Store. 4 | // 5 | // To receive a decoded receipt for validation, send a request with the encoded 6 | // receipt data to the App Store. For auto-renewable subscriptions, include the 7 | // app password and optionally an exclusion flag. Send this JSON data using the 8 | // HTTP POST request method. 9 | type ReceiptRequest struct { 10 | // ReceiptData contains the base64 encoded receipt data. Required. 11 | ReceiptData []byte `json:"receipt-data"` 12 | 13 | // Password is your app’s shared secret (a hexadecimal string). Required. 14 | // 15 | // Use this field only for receipts that contain auto-renewable subscriptions. 16 | Password string `json:"password"` 17 | 18 | // ExcludeOldTransactions is only used for app receipts that contain 19 | // auto-renewable or non-renewing subscriptions. 20 | // 21 | // Set this value to true for the response to include only the latest renewal transaction 22 | // for any subscriptions. 23 | ExcludeOldTransactions bool `json:"exclude-old-transactions,omitempty"` 24 | } 25 | -------------------------------------------------------------------------------- /receipt_response.go: -------------------------------------------------------------------------------- 1 | package storekit 2 | 3 | // ReceiptResponseStatus is the status of the app receipt. The value for status 4 | // is 0 if the receipt is valid, or a status code if there is an error. The 5 | // status code reflects the status of the app receipt as a whole. For example, 6 | // if you send a valid app receipt that contains an expired subscription, the 7 | // response is 0 because the receipt is valid. Status codes 21100-21199 are 8 | // various internal data access errors. 9 | // https://developer.apple.com/documentation/appstorereceipts/status 10 | type ReceiptResponseStatus int 11 | 12 | const ( 13 | // Undocumented but occurs 14 | ReceiptResponseStatusUnknown ReceiptResponseStatus = -1 15 | 16 | // Receipt is valid. 17 | ReceiptResponseStatusOK ReceiptResponseStatus = 0 18 | 19 | // The request to the App Store was not made using the HTTP POST request method. 20 | ReceiptResponseStatusAppStoreCannotRead ReceiptResponseStatus = 21000 21 | 22 | // This status code is no longer sent by the App Store. 23 | ReceiptResponseStatusNoLongerSent ReceiptResponseStatus = 21001 24 | 25 | // The data in the receipt-data property was malformed or the service 26 | // experienced a temporary issue. Try again. 27 | ReceiptResponseStatusDataMalformed ReceiptResponseStatus = 21002 28 | 29 | // The receipt could not be authenticated. 30 | ReceiptResponseStatusNotAuthenticated ReceiptResponseStatus = 21003 31 | 32 | // The shared secret you provided does not match the shared secret on file for 33 | // your account. 34 | ReceiptResponseStatusSharedSecretDoesNotMatch ReceiptResponseStatus = 21004 35 | 36 | // The receipt server was temporarily unable to provide the receipt. Try again. 37 | ReceiptResponseStatusReceiptServerUnavailable ReceiptResponseStatus = 21005 38 | 39 | // This receipt is valid but the subscription has expired. When this status code 40 | // is returned to your server, the receipt data is also decoded and returned as 41 | // part of the response. Only returned for iOS 6-style transaction receipts for 42 | // auto-renewable subscriptions. 43 | ReceiptResponseStatusValidButSubscriptionExpired ReceiptResponseStatus = 21006 44 | 45 | // This receipt is from the test environment, but it was sent to the production 46 | // environment for verification. 47 | ReceiptResponseStatusSandboxReceiptSentToProduction ReceiptResponseStatus = 21007 48 | 49 | // This receipt is from the production environment, but it was sent to the test 50 | // environment for verification. 51 | ReceiptResponseStatusProductionReceiptSentToSandbox ReceiptResponseStatus = 21008 52 | 53 | // Internal data access error. Try again later. 54 | ReceiptResponseStatusBadAccess ReceiptResponseStatus = 21009 55 | 56 | // The user account cannot be found or has been deleted. 57 | ReceiptResponseStatusCouldNotBeAuthorized ReceiptResponseStatus = 21010 58 | 59 | // Status codes 21100-21199 are various internal data access errors. 60 | ) 61 | 62 | // ReceiptResponse is the JSON data returned in the response from the App Store. 63 | // https://developer.apple.com/documentation/appstorereceipts/responsebody 64 | type ReceiptResponse struct { 65 | // The environment for which the receipt was generated. 66 | // Possible values: Sandbox, Production 67 | Environment string `json:"environment,omitempty"` 68 | 69 | // IsRetryable is an indicator that an error occurred during the request. A 70 | // value of 1 indicates a temporary issue; retry validation for this receipt at 71 | // a later time. A value of 0 indicates an unresolvable issue; do not retry 72 | // validation for this receipt. Only applicable to status codes 21100-21199. 73 | IsRetryable bool `json:"is-retryable,omitempty"` 74 | 75 | // The latest Base64 encoded app receipt. Only returned for receipts that 76 | // contain auto-renewable subscriptions. 77 | LatestReceipt []byte `json:"latest_receipt,omitempty"` 78 | 79 | // LatestReceiptInfo is an array that contains all in-app purchase transactions. 80 | // This excludes transactions for consumable products that have been marked as 81 | // finished by your app. Only returned for receipts that contain auto-renewable 82 | // subscriptions. 83 | LatestReceiptInfo []LatestReceiptInfo `json:"latest_receipt_info,omitempty"` 84 | 85 | // In the JSON file, an array where each element contains the pending renewal 86 | // information for each auto-renewable subscription identified by the 87 | // product_id. Only returned for app receipts that contain auto-renewable 88 | // subscriptions. 89 | PendingRenewalInfo []PendingRenewalInfo `json:"pending_renewal_info,omitempty"` 90 | 91 | // A JSON representation of the receipt that was sent for verification. 92 | Receipt Receipt `json:"receipt,omitempty"` 93 | 94 | // Either 0 if the receipt is valid, or a status code if there is an error. The 95 | // status code reflects the status of the app receipt as a whole. 96 | Status ReceiptResponseStatus `json:"status,omitempty"` 97 | } 98 | --------------------------------------------------------------------------------