Categorygithub.com/Gurpartap/storekit-go
modulepackage
0.0.0-20201205024111-36b6cd5c6a21
Repository: https://github.com/gurpartap/storekit-go.git
Documentation: pkg.go.dev

# README

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)

package main

import (
	"context"
	"errors"
	"fmt"
	"os"
	"time"

	"github.com/Gurpartap/storekit-go"
)

func main() {
	// Get it from https://AppsToReconnect.apple.com 🤯
	appStoreSharedSecret = os.GetEnv("APP_STORE_SHARED_SECRET")

	// Your own userId
	userId := "12345"

	// Input coming from either user device or subscription notifications webhook
	receiptData := []byte("...")

	err := verifyAndSave(appStoreSharedSecret, userId, receiptData)
	if err != nil {
		fmt.Println("could not verify receipt:", err)
	}
}

func verifyAndSave(appStoreSharedSecret, userId string, receiptData []byte) error {
	// Use .OnProductionEnv() when deploying
	//
	// storekit-go automatically retries sandbox server upon incompatible
	// environment error. This is necessary because App Store Reviewer's purchase
	// requests go through the sandbox server instead of production.
	//
	// Use .WithoutEnvAutoFix() to disable automatic env switching and retrying
	// (not recommended on production)
	client := storekit.NewVerificationClient().OnSandboxEnv()

	// respBody is raw bytes of response, useful for storing, auditing, and for
	// future verification checks. resp is the same parsed and mapped to a struct.
	ctx, _ := context.WithTimeout(context.Background(), 15*time.Second)
	respBody, resp, err := client.Verify(ctx, &storekit.ReceiptRequest{
		ReceiptData:            receiptData,
		Password:               appStoreSharedSecret,
		ExcludeOldTransactions: true,
	})
	if err != nil {
		return err // code: internal error
	}

	if resp.Status != 0 {
		return errors.New(
			fmt.Sprintf("receipt rejected by App Store with status = %d", resp.Status),
		) // code: permission denied
	}

	// If receipt does not contain any active subscription info it is probably a
	// fraudulent attempt at activating subscription from a jailbroken device.
	if len(resp.LatestReceiptInfo) == 0 {
		// keep it 🤫 that we know what's going on
		return errors.New("unknown error") // code: internal (instead of invalid argument)
	}

	// resp.LatestReceiptInfo works for me. but, alternatively (as Apple devs also
	// recommend) you can loop over resp.Receipt.InAppPurchaseReceipt, and filter
	// for the receipt with the highest expiresAtMs to find the appropriate latest
	// subscription (not shown in this example). if you have multiple subscription
	// groups, look for transactions with expiresAt > time.Now().
	for _, latestReceiptInfo := range resp.LatestReceiptInfo {
		productID := latestReceiptInfo.ProductId
		expiresAtMs := latestReceiptInfo.ExpiresDateMs
		// cancelledAtStr := latestReceiptInfo.CancellationDate

		// defensively check for necessary data, because StoreKit API responses can be a
		// bit adventurous
		if productID == "" {
			return errors.New("missing product_id in the latest receipt info") // code: internal error
		}
		if expiresAtMs == 0 {
			return errors.New("missing expiry date in latest receipt info") // code: internal error
		}

		expiresAt := time.Unix(0, expiresAtMs*1000000)

		fmt.Printf(
			"userId = %s has subscribed for product_id = %s which expires_at = %s",
			userId,
			productID,
			expiresAt,
		)

		// ✅ Save or return productID, expiresAt, cancelledAt, respBody
	}
}

# Functions

NewVerificationClient defaults to production verification URL with auto fix enabled.

# Constants

The customer has turned off automatic renewal for the subscription.
The subscription will renew at the end of the current subscription period.
The App Store is attempting to renew the subscription.
The App Store has stopped attempting to renew the subscription.
Billing error for example, the customer's payment information was no longer valid.
The customer did not agree to a recent price increase.
The product was not available for purchase at the time of renewal.
Unknown error.
The customer voluntarily canceled their subscription.
No description provided by the author
No description provided by the author
Indicates that either Apple customer support canceled the subscription or the user upgraded their subscription.
Indicates that the customer made a change in their subscription plan that takes effect at the next renewal.
Indicates a change in the subscription renewal status.
Indicates a subscription that failed to renew due to a billing issue.
Indicates a successful automatic renewal of an expired subscription that failed to renew in the past.
Indicates that a customer’s subscription has successfully auto-renewed for a new transaction period.
Occurs at the user’s initial purchase of the subscription.
Indicates the customer renewed a subscription interactively, either by using your app’s interface, or on the App Store in the account’s Subscriptions settings.
Indicates that App Store has started asking the customer to consent to your app’s subscription price increase.
Indicates that App Store successfully refunded a transaction.
The App Store is asking for the customer's consent, and hasn't received it.
The App Store has received customer's consent.
The App Store hasn't yet requested consent from the customer.
The request to the App Store was not made using the HTTP POST request method.
Internal data access error.
The user account cannot be found or has been deleted.
The data in the receipt-data property was malformed or the service experienced a temporary issue.
This status code is no longer sent by the App Store.
The receipt could not be authenticated.
Receipt is valid.
This receipt is from the production environment, but it was sent to the test environment for verification.
The receipt server was temporarily unable to provide the receipt.
This receipt is from the test environment, but it was sent to the production environment for verification.
The shared secret you provided does not match the shared secret on file for your account.
Undocumented but occurs.
This receipt is valid but the subscription has expired.

# Structs

InAppPurchaseReceipt is an array that contains the in-app purchase receipt fields for all in-app purchase transactions.
LatestReceiptInfo is an array that contains all in-app purchase transactions.
Notification is the JSON data sent in the server notification from the App Store.
PendingRenewalInfo is an array of elements that refers to auto-renewable subscription renewals that are open or failed in the past.
Receipt is the decoded version of the encoded receipt data sent with the request to the App Store.
ReceiptRequest is the JSON you submit with the request to the App Store.
ReceiptResponse is the JSON data returned in the response from the App Store.
UnifiedReceipt is an object that contains information about the most-recent, in-app purchase transactions for the app.

# Type aliases

AutoRenewStatus is returned in the JSON response, in the responseBody.Pending_renewal_info array.
BillingRetryStatus indicates whether Apple is attempting to renew an expired subscription automatically.
ExpirationIntent is the reason a subscription expired.
InAppOwnershipType is the relationship of the user with the family-shared purchase to which they have access.
NotificationType is the type that describes the in-app purchase event for which the App Store sent the notification.
No description provided by the author
ReceiptResponseStatus is the status of the app receipt.