项目作者: Gurpartap

项目描述 :
🧾 StoreKit API client for verifying in-app purchase receipts with Apple's App Store
高级语言: Go
项目地址: git://github.com/Gurpartap/storekit-go.git
创建时间: 2018-08-09T12:45:10Z
项目社区:https://github.com/Gurpartap/storekit-go

开源协议:Apache License 2.0

下载


storekit-go

GoDoc

Use this for verifying App Store receipts.

  • Battle proven technology
  • Blockchain free

See GoDoc for detailed API response reference.

Usage example (auto-renewing subscriptions)

  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "os"
  7. "time"
  8. "github.com/Gurpartap/storekit-go"
  9. )
  10. func main() {
  11. // Get it from https://AppsToReconnect.apple.com 🤯
  12. appStoreSharedSecret = os.GetEnv("APP_STORE_SHARED_SECRET")
  13. // Your own userId
  14. userId := "12345"
  15. // Input coming from either user device or subscription notifications webhook
  16. receiptData := []byte("...")
  17. err := verifyAndSave(appStoreSharedSecret, userId, receiptData)
  18. if err != nil {
  19. fmt.Println("could not verify receipt:", err)
  20. }
  21. }
  22. func verifyAndSave(appStoreSharedSecret, userId string, receiptData []byte) error {
  23. // Use .OnProductionEnv() when deploying
  24. //
  25. // storekit-go automatically retries sandbox server upon incompatible
  26. // environment error. This is necessary because App Store Reviewer's purchase
  27. // requests go through the sandbox server instead of production.
  28. //
  29. // Use .WithoutEnvAutoFix() to disable automatic env switching and retrying
  30. // (not recommended on production)
  31. client := storekit.NewVerificationClient().OnSandboxEnv()
  32. // respBody is raw bytes of response, useful for storing, auditing, and for
  33. // future verification checks. resp is the same parsed and mapped to a struct.
  34. ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
  35. respBody, resp, err := client.Verify(ctx, &storekit.ReceiptRequest{
  36. ReceiptData: receiptData,
  37. Password: appStoreSharedSecret,
  38. ExcludeOldTransactions: true,
  39. })
  40. if err != nil {
  41. return err // code: internal error
  42. }
  43. if resp.Status != 0 {
  44. return errors.New(
  45. fmt.Sprintf("receipt rejected by App Store with status = %d", resp.Status),
  46. ) // code: permission denied
  47. }
  48. // If receipt does not contain any active subscription info it is probably a
  49. // fraudulent attempt at activating subscription from a jailbroken device.
  50. if len(resp.LatestReceiptInfo) == 0 {
  51. // keep it 🤫 that we know what's going on
  52. return errors.New("unknown error") // code: internal (instead of invalid argument)
  53. }
  54. // resp.LatestReceiptInfo works for me. but, alternatively (as Apple devs also
  55. // recommend) you can loop over resp.Receipt.InAppPurchaseReceipt, and filter
  56. // for the receipt with the highest expiresAtMs to find the appropriate latest
  57. // subscription (not shown in this example). if you have multiple subscription
  58. // groups, look for transactions with expiresAt > time.Now().
  59. for _, latestReceiptInfo := range resp.LatestReceiptInfo {
  60. productID := latestReceiptInfo.ProductId
  61. expiresAtMs := latestReceiptInfo.ExpiresDateMs
  62. // cancelledAtStr := latestReceiptInfo.CancellationDate
  63. // defensively check for necessary data, because StoreKit API responses can be a
  64. // bit adventurous
  65. if productID == "" {
  66. return errors.New("missing product_id in the latest receipt info") // code: internal error
  67. }
  68. if expiresAtMs == 0 {
  69. return errors.New("missing expiry date in latest receipt info") // code: internal error
  70. }
  71. expiresAt := time.Unix(0, expiresAtMs*1000000)
  72. fmt.Printf(
  73. "userId = %s has subscribed for product_id = %s which expires_at = %s",
  74. userId,
  75. productID,
  76. expiresAt,
  77. )
  78. // ✅ Save or return productID, expiresAt, cancelledAt, respBody
  79. }
  80. }