{"id":37218955,"url":"https://github.com/gurpartap/storekit-go","last_synced_at":"2026-01-15T01:14:02.062Z","repository":{"id":51535580,"uuid":"144151474","full_name":"Gurpartap/storekit-go","owner":"Gurpartap","description":"🧾 StoreKit API client for verifying in-app purchase receipts with Apple's App Store","archived":false,"fork":false,"pushed_at":"2024-07-06T04:39:19.000Z","size":53,"stargazers_count":25,"open_issues_count":1,"forks_count":10,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-13T20:16:24.528Z","etag":null,"topics":["appstore","go","golang","storekit"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Gurpartap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-09T12:45:10.000Z","updated_at":"2025-02-13T12:33:13.000Z","dependencies_parsed_at":"2022-09-01T23:10:56.241Z","dependency_job_id":null,"html_url":"https://github.com/Gurpartap/storekit-go","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Gurpartap/storekit-go","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gurpartap%2Fstorekit-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gurpartap%2Fstorekit-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gurpartap%2Fstorekit-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gurpartap%2Fstorekit-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Gurpartap","download_url":"https://codeload.github.com/Gurpartap/storekit-go/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gurpartap%2Fstorekit-go/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28441023,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-15T00:55:22.719Z","status":"ssl_error","status_checked_at":"2026-01-15T00:55:20.945Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["appstore","go","golang","storekit"],"created_at":"2026-01-15T01:14:00.501Z","updated_at":"2026-01-15T01:14:02.034Z","avatar_url":"https://github.com/Gurpartap.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# storekit-go\n\n[![GoDoc](https://godoc.org/github.com/Gurpartap/storekit-go?status.svg)](https://godoc.org/github.com/Gurpartap/storekit-go)\n\nUse this for verifying App Store receipts.\n\n- [x] Battle proven technology\n- [x] Blockchain free\n\nSee [GoDoc](https://godoc.org/github.com/Gurpartap/storekit-go) for detailed API response reference.\n\n## Usage example (auto-renewing subscriptions)\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/Gurpartap/storekit-go\"\n)\n\nfunc main() {\n\t// Get it from https://AppsToReconnect.apple.com 🤯\n\tappStoreSharedSecret = os.GetEnv(\"APP_STORE_SHARED_SECRET\")\n\n\t// Your own userId\n\tuserId := \"12345\"\n\n\t// Input coming from either user device or subscription notifications webhook\n\treceiptData := []byte(\"...\")\n\n\terr := verifyAndSave(appStoreSharedSecret, userId, receiptData)\n\tif err != nil {\n\t\tfmt.Println(\"could not verify receipt:\", err)\n\t}\n}\n\nfunc verifyAndSave(appStoreSharedSecret, userId string, receiptData []byte) error {\n\t// Use .OnProductionEnv() when deploying\n\t//\n\t// storekit-go automatically retries sandbox server upon incompatible\n\t// environment error. This is necessary because App Store Reviewer's purchase\n\t// requests go through the sandbox server instead of production.\n\t//\n\t// Use .WithoutEnvAutoFix() to disable automatic env switching and retrying\n\t// (not recommended on production)\n\tclient := storekit.NewVerificationClient().OnSandboxEnv()\n\n\t// respBody is raw bytes of response, useful for storing, auditing, and for\n\t// future verification checks. resp is the same parsed and mapped to a struct.\n\tctx, _ := context.WithTimeout(context.Background(), 15*time.Second)\n\trespBody, resp, err := client.Verify(ctx, \u0026storekit.ReceiptRequest{\n\t\tReceiptData:            receiptData,\n\t\tPassword:               appStoreSharedSecret,\n\t\tExcludeOldTransactions: true,\n\t})\n\tif err != nil {\n\t\treturn err // code: internal error\n\t}\n\n\tif resp.Status != 0 {\n\t\treturn errors.New(\n\t\t\tfmt.Sprintf(\"receipt rejected by App Store with status = %d\", resp.Status),\n\t\t) // code: permission denied\n\t}\n\n\t// If receipt does not contain any active subscription info it is probably a\n\t// fraudulent attempt at activating subscription from a jailbroken device.\n\tif len(resp.LatestReceiptInfo) == 0 {\n\t\t// keep it 🤫 that we know what's going on\n\t\treturn errors.New(\"unknown error\") // code: internal (instead of invalid argument)\n\t}\n\n\t// resp.LatestReceiptInfo works for me. but, alternatively (as Apple devs also\n\t// recommend) you can loop over resp.Receipt.InAppPurchaseReceipt, and filter\n\t// for the receipt with the highest expiresAtMs to find the appropriate latest\n\t// subscription (not shown in this example). if you have multiple subscription\n\t// groups, look for transactions with expiresAt \u003e time.Now().\n\tfor _, latestReceiptInfo := range resp.LatestReceiptInfo {\n\t\tproductID := latestReceiptInfo.ProductId\n\t\texpiresAtMs := latestReceiptInfo.ExpiresDateMs\n\t\t// cancelledAtStr := latestReceiptInfo.CancellationDate\n\n\t\t// defensively check for necessary data, because StoreKit API responses can be a\n\t\t// bit adventurous\n\t\tif productID == \"\" {\n\t\t\treturn errors.New(\"missing product_id in the latest receipt info\") // code: internal error\n\t\t}\n\t\tif expiresAtMs == 0 {\n\t\t\treturn errors.New(\"missing expiry date in latest receipt info\") // code: internal error\n\t\t}\n\n\t\texpiresAt := time.Unix(0, expiresAtMs*1000000)\n\n\t\tfmt.Printf(\n\t\t\t\"userId = %s has subscribed for product_id = %s which expires_at = %s\",\n\t\t\tuserId,\n\t\t\tproductID,\n\t\t\texpiresAt,\n\t\t)\n\n\t\t// ✅ Save or return productID, expiresAt, cancelledAt, respBody\n\t}\n}\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgurpartap%2Fstorekit-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgurpartap%2Fstorekit-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgurpartap%2Fstorekit-go/lists"}