appstore

package module
v1.33.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 28, 2024 License: MIT Imports: 25 Imported by: 0

README

Apple App Store Server Golang Library

The Golang server library for the App Store Server API and App Store Server Notifications.

The App Store Server API is a REST API that you call from your server to request and provide information about your customers' in-app purchases.

The App Store Server API is independent of the app’s installation status on the customers’ devices. The App Store server returns information based on a customer’s in-app purchase history regardless of whether the customer installs, removes, or reinstalls the app on their devices.

Quick Start

Installation
go get github.com/richzw/appstore
Generate a Private Key

Log in to App Store Connect and complete the following steps:

  • Select Users and Access, and then select the Keys tab.
  • Select In-App Purchase under the Key Type.
  • Click Generate API Key or the Add (+) button.
  • Enter a name for the key. The name is for your reference only and isn’t part of the key itself. Click Generate.
  • Click Download API Key next to the new API key. And store your private key in a secure place.
Get Transaction Info
import(
	"github.com/richzw/appstore"
)

// ACCOUNTPRIVATEKEY is the key file generated from previous step
const ACCOUNTPRIVATEKEY = `
    -----BEGIN PRIVATE KEY-----
    FAKEACCOUNTKEYBASE64FORMAT
    -----END PRIVATE KEY-----
    `
func main() {
    c := &appstore.StoreConfig{
        KeyContent: []byte(ACCOUNTPRIVATEKEY),
        KeyID:      "FAKEKEYID",
        BundleID:   "fake.bundle.id",
        Issuer:     "xxxxx-xx-xx-xx-xxxxxxxxxx",
        Sandbox:    false,
    }
    transactionId := "FAKETRANSACTIONID"
    a := appstore.NewStoreClient(c)
    response, err := a.GetTransactionInfo(context.TODO(), transactionId)

    transactions, err := a.ParseSignedTransactions([]string{response.SignedTransactionInfo})
    if transactions[0].TransactionID == transactionId {
        // the transaction is valid
    }
}
  • Validate the receipt
    • One option could be to validate the receipt with the App Store server through GetTransactionInfo API, and then check the transactionId in the response matches the one you are looking for.
  • The App Store Server API differentiates between a sandbox and a production environment based on the base URL:
  • If you're unsure about the environment, follow these steps:
    • Initiate a call to the endpoint using the production URL. If the call is successful, the transaction identifier is associated with the production environment.
    • If you encounter an error code 4040010, indicating a TransactionIdNotFoundError, make a call to the endpoint using the sandbox URL.
  • Handle exceeded rate limits gracefully
    • If you exceed a per-hour limit, the API rejects the request with an HTTP 429 response, with a RateLimitExceededError in the body. Consider the following as you integrate the API:
      • If you periodically call the API, throttle your requests to avoid exceeding the per-hour limit for an endpoint.
      • Manage the HTTP 429 RateLimitExceededError in your error-handling process. For example, log the failure and queue the job to process it again at a later time.
      • Check the Retry-After header if you receive the HTTP 429 error. This header contains a UNIX time, in milliseconds, that informs you when you can next send a request.
  • Error handling
Look Up Order ID
import(
    "github.com/richzw/appstore"
)

// ACCOUNTPRIVATEKEY is the key file generated from previous step
const ACCOUNTPRIVATEKEY = `
    -----BEGIN PRIVATE KEY-----
    FAKEACCOUNTKEYBASE64FORMAT
    -----END PRIVATE KEY-----
    `

func main() {
    c := &appstore.StoreConfig{
        KeyContent: []byte(ACCOUNTPRIVATEKEY),
        KeyID:      "FAKEKEYID",
        BundleID:   "fake.bundle.id",
        Issuer:     "xxxxx-xx-xx-xx-xxxxxxxxxx",
        Sandbox:    false,
    }
    invoiceOrderId := "FAKEORDERID"

    a := appstore.NewStoreClient(c)
    rsp, err := a.LookupOrderID(context.TODO(), invoiceOrderId)

    orders, err := a.ParseSignedTransactions(rsp.SignedTransactions)
}
Get Transaction History
import(
    "github.com/richzw/appstore"
)

// ACCOUNTPRIVATEKEY is the key file generated from previous step
const ACCOUNTPRIVATEKEY = `
    -----BEGIN PRIVATE KEY-----
    FAKEACCOUNTKEYBASE64FORMAT
    -----END PRIVATE KEY-----
    `

func main() {
    c := &appstore.StoreConfig{
        KeyContent: []byte(ACCOUNTPRIVATEKEY),
        KeyID:      "FAKEKEYID",
        BundleID:   "fake.bundle.id",
        Issuer:     "xxxxx-xx-xx-xx-xxxxxxxxxx",
        Sandbox:    false,
    }
    originalTransactionId := "FAKEORDERID"
    a := appstore.NewStoreClient(c)
    query := &url.Values{}
    query.Set("productType", "AUTO_RENEWABLE")
    query.Set("productType", "NON_CONSUMABLE")
    gotRsp, err := a.GetTransactionHistory(context.TODO(), originalTransactionId, query)

    for _, rsp := range gotRsp {
       trans, err := a.ParseSignedTransactions(rsp.SignedTransactions)
    }
}
Get Refund History
import(
    "github.com/richzw/appstore"
)

// ACCOUNTPRIVATEKEY is the key file generated from previous step
const ACCOUNTPRIVATEKEY = `
    -----BEGIN PRIVATE KEY-----
    FAKEACCOUNTKEYBASE64FORMAT
    -----END PRIVATE KEY-----
    `

func main() {
    c := &appstore.StoreConfig{
        KeyContent: []byte(ACCOUNTPRIVATEKEY),
        KeyID:      "FAKEKEYID",
        BundleID:   "fake.bundle.id",
        Issuer:     "xxxxx-xx-xx-xx-xxxxxxxxxx",
        Sandbox:    false,
    }
    originalTransactionId := "FAKEORDERID"
    a := appstore.NewStoreClient(c)
    gotRsp, err := a.GetRefundHistory(context.TODO(), originalTransactionId)

    for _, rsp := range gotRsp {
       trans, err := a.ParseSignedTransactions(rsp.SignedTransactions)
    }
}
Parse Notification from App Store
import (
    "github.com/richzw/appstore"
    "github.com/golang-jwt/jwt/v4"
)

func main() {
    c := &appstore.StoreConfig{
        KeyContent: []byte(ACCOUNTPRIVATEKEY),
        KeyID:      "FAKEKEYID",
        BundleID:   "fake.bundle.id",
        Issuer:     "xxxxx-xx-xx-xx-xxxxxxxxxx",
        Sandbox:    false,
    }
    tokenStr := "SignedRenewalInfo Encode String" // or SignedTransactionInfo string
    a := appstore.NewStoreClient(c)
    token, err := a.ParseNotificationV2(tokenStr)

    claims, ok := token.Claims.(jwt.MapClaims)
    for key, val := range claims {
        fmt.Printf("Key: %v, value: %v\n", key, val) // key value of TransactionInfo
    }
}
import (
    "github.com/richzw/appstore"
    "github.com/golang-jwt/jwt/v4"
)

func main() {
    c := &appstore.StoreConfig{
        KeyContent: []byte(ACCOUNTPRIVATEKEY),
        KeyID:      "FAKEKEYID",
        BundleID:   "fake.bundle.id",
        Issuer:     "xxxxx-xx-xx-xx-xxxxxxxxxx",
        Sandbox:    false,
    }
    tokenStr := "JWSTransactionDecodedPayload Encode String"
    a := appstore.NewStoreClient(c)

    jws, err := a.ParseNotificationV2WithClaim(tokenStr)
    // access the fields of JWSTransactionDecodedPayload from jws directly
}
Parse signed notification payloads from App Store Server Notification request
import (
    "encoding/json"

    "github.com/richzw/appstore"
)

func main() {
    c := &appstore.StoreConfig{
        KeyContent: []byte(ACCOUNTPRIVATEKEY),
        KeyID:      "FAKEKEYID",
        BundleID:   "fake.bundle.id",
        Issuer:     "xxxxx-xx-xx-xx-xxxxxxxxxx",
        Sandbox:    false,
    }
    a := appstore.NewStoreClient(c)

    reqBody := []byte{} // Request from App Store Server Notification
    var notification appstore.NotificationV2
    if _, err := json.Unmarshal(reqBody, &notification); err != nil {
        panic(err)
    }

    // Parse the notification payload
    payload, err := a.ParseNotificationV2Payload(notification.SignedPayload)
    if err != nil {
        panic(err)
    }

    // Parse the transaction info
    transactionInfo, err := a.ParseNotificationV2TransactionInfo(payload.Data.SignedTransactionInfo)
    if err != nil {
        panic(err)
    }

    // Parse the renewal info
    renewalInfo, err := a.ParseNotificationV2RenewalInfo(payload.Data.SignedRenewalInfo)
    if err != nil {
        panic(err)
    }
}

Support

App Store Server API 1.12+

License

appstore is licensed under the MIT.

Documentation

Index

Constants

View Source
const (
	TransactionReasonPurchase = "PURCHASE"
	TransactionReasonRenewal  = "RENEWAL"
)
View Source
const (
	UndeclaredExtendReasonCode = iota
	CustomerSatisfaction
	OtherReasons
	ServiceIssueOrOutage
)
View Source
const (
	HostSandBox    = "https://api.storekit-sandbox.itunes.apple.com"
	HostProduction = "https://api.storekit.itunes.apple.com"

	PathTransactionInfo                     = "/inApps/v1/transactions/{transactionId}"
	PathLookUp                              = "/inApps/v1/lookup/{orderId}"
	PathTransactionHistory                  = "/inApps/v2/history/{originalTransactionId}"
	PathRefundHistory                       = "/inApps/v2/refund/lookup/{originalTransactionId}"
	PathGetALLSubscriptionStatus            = "/inApps/v1/subscriptions/{originalTransactionId}"
	PathConsumptionInfo                     = "/inApps/v1/transactions/consumption/{originalTransactionId}"
	PathExtendSubscriptionRenewalDate       = "/inApps/v1/subscriptions/extend/{originalTransactionId}"
	PathExtendSubscriptionRenewalDateForAll = "/inApps/v1/subscriptions/extend/mass/"
	PathGetStatusOfSubscriptionRenewalDate  = "/inApps/v1/subscriptions/extend/mass/{productId}/{requestIdentifier}"
	PathGetNotificationHistory              = "/inApps/v1/notifications/history"
	PathRequestTestNotification             = "/inApps/v1/notifications/test"
	PathGetTestNotificationStatus           = "/inApps/v1/notifications/test/{testNotificationToken}"
)

Variables

View Source
var (
	// Retryable errors
	AccountNotFoundRetryableError               = newError(4040002, "Account not found. Please try again.")
	AppNotFoundRetryableError                   = newError(4040004, "App not found. Please try again.")
	GeneralInternalRetryableError               = newError(5000001, "An unknown error occurred. Please try again.")
	OriginalTransactionIdNotFoundRetryableError = newError(4040006, "Original transaction id not found. Please try again.")
	// Errors
	AccountNotFoundError                             = newError(4040001, "Account not found.")
	AppNotFoundError                                 = newError(4040003, "App not found.")
	FamilySharedSubscriptionExtensionIneligibleError = newError(4030007, "Subscriptions that users obtain through Family Sharing can't get a renewal date extension directly.")
	GeneralInternalError                             = newError(5000000, "An unknown error occurred.")
	GeneralBadRequestError                           = newError(4000000, "Bad request.")
	InvalidAppIdentifierError                        = newError(4000002, "Invalid request app identifier.")
	InvalidEmptyStorefrontCountryCodeListError       = newError(4000027, "Invalid request. If provided, the list of storefront country codes must not be empty.")
	InvalidExtendByDaysError                         = newError(4000009, "Invalid extend by days value.")
	InvalidExtendReasonCodeError                     = newError(4000010, "Invalid extend reason code.")
	InvalidOriginalTransactionIdError                = newError(4000008, "Invalid original transaction id.")
	InvalidRequestIdentifierError                    = newError(4000011, "Invalid request identifier.")
	InvalidRequestRevisionError                      = newError(4000005, "Invalid request revision.")
	InvalidRevokedError                              = newError(4000030, "Invalid request. The revoked parameter is invalid.")
	InvalidStatusError                               = newError(4000031, "Invalid request. The status parameter is invalid.")
	InvalidStorefrontCountryCodeError                = newError(4000028, "Invalid request. A storefront country code was invalid.")
	InvalidTransactionIdError                        = newError(4000006, "Invalid transaction id.")
	OriginalTransactionIdNotFoundError               = newError(4040005, "Original transaction id not found.")
	RateLimitExceededError                           = newError(4290000, "Rate limit exceeded.")
	StatusRequestNotFoundError                       = newError(4040009, "The server didn't find a subscription-renewal-date extension request for this requestIdentifier and productId combination.")
	SubscriptionExtensionIneligibleError             = newError(4030004, "Forbidden - subscription state ineligible for extension.")
	SubscriptionMaxExtensionError                    = newError(4030005, "Forbidden - subscription has reached maximum extension count.")
	TransactionIdNotFoundError                       = newError(4040010, "Transaction id not found.")
	// Notification test and history errors
	InvalidEndDateError                     = newError(4000016, "Invalid request. The end date is not a timestamp value represented in milliseconds.")
	InvalidNotificationTypeError            = newError(4000018, "Invalid request. The notification type or subtype is invalid.")
	InvalidPaginationTokenError             = newError(4000014, "Invalid request. The pagination token is invalid.")
	InvalidStartDateError                   = newError(4000015, "Invalid request. The start date is not a timestamp value represented in milliseconds.")
	InvalidTestNotificationTokenError       = newError(4000020, "Invalid request. The test notification token is invalid.")
	InvalidInAppOwnershipTypeError          = newError(4000026, "Invalid request. The in-app ownership type parameter is invalid.")
	InvalidProductIdError                   = newError(4000023, "Invalid request. The product id parameter is invalid.")
	InvalidProductTypeError                 = newError(4000022, "Invalid request. The product type parameter is invalid.")
	InvalidSortError                        = newError(4000021, "Invalid request. The sort parameter is invalid.")
	InvalidSubscriptionGroupIdentifierError = newError(4000024, "Invalid request. The subscription group identifier parameter is invalid.")
	MultipleFiltersSuppliedError            = newError(4000019, "Invalid request. Supply either a transaction id or a notification type, but not both.")
	PaginationTokenExpiredError             = newError(4000017, "Invalid request. The pagination token is expired.")
	ServerNotificationURLNotFoundError      = newError(4040007, "No App Store Server Notification URL found for provided app. Check that a URL is configured in App Store Connect for this environment.")
	StartDateAfterEndDateError              = newError(4000013, "Invalid request. The end date precedes the start date or the dates are the same.")
	StartDateTooFarInPastError              = newError(4000012, "Invalid request. The start date is earlier than the allowed start date.")
	TestNotificationNotFoundError           = newError(4040008, "Either the test notification token is expired or the notification and status are not yet available.")
	InvalidAccountTenureError               = newError(4000032, "Invalid request. The account tenure field is invalid.")
	InvalidAppAccountTokenError             = newError(4000033, "Invalid request. The app account token field must contain a valid UUID or an empty string.")
	InvalidConsumptionStatusError           = newError(4000034, "Invalid request. The consumption status field is invalid.")
	InvalidCustomerConsentedError           = newError(4000035, "Invalid request. The customer consented field is required and must indicate the customer consented")
	InvalidDeliveryStatusError              = newError(4000036, "Invalid request. The delivery status field is invalid")
	InvalidLifetimeDollarsPurchasedError    = newError(4000037, "Invalid request. The lifetime dollars purchased field is invalid")
	InvalidLifetimeDollarsRefundedError     = newError(4000038, "Invalid request. The lifetime dollars refunded field is invalid")
	InvalidPlatformError                    = newError(4000039, "Invalid request. The platform field is invalid")
	InvalidPlayTimeError                    = newError(4000040, "Invalid request. The playtime field is invalid")
	InvalidSampleContentProvidedError       = newError(4000041, "Invalid request. The sample content provided field is invalid")
	InvalidUserStatusError                  = newError(4000042, "Invalid request. The user status field is invalid")
	InvalidTransactionNotConsumableError    = newError(4000043, "Invalid request. The transaction id parameter must represent a consumable in-app purchase")
	InvalidTransactionTypeNotSupportedError = newError(4000047, "Invalid request. The transaction id doesn't represent a supported in-app purchase type")
)
View Source
var (
	ErrAuthKeyInvalidPem  = errors.New("token: AuthKey must be a valid .p8 PEM file")
	ErrAuthKeyInvalidType = errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
)

Authorize Tokens For App Store Server API Request Doc: https://developer.apple.com/documentation/appstoreserverapi/generating_tokens_for_api_requests

Functions

func ShouldRetryDefault added in v1.9.0

func ShouldRetryDefault(status int, err error) bool

Types

type Backoff added in v1.9.0

type Backoff interface {
	Pause() time.Duration
}

type Cert

type Cert struct {
	// contains filtered or unexported fields
}

type CertPool added in v1.32.0

type CertPool struct {
	// contains filtered or unexported fields
}

func NewCertPool added in v1.32.0

func NewCertPool() (*CertPool, error)

func (*CertPool) GetCertPool added in v1.32.0

func (cp *CertPool) GetCertPool() *x509.CertPool

func (*CertPool) Init added in v1.32.0

func (cp *CertPool) Init() error

type ConsumptionRequestBody

type ConsumptionRequestBody struct {
	AccountTenure            int32  `json:"accountTenure"`
	AppAccountToken          string `json:"appAccountToken"`
	ConsumptionStatus        int32  `json:"consumptionStatus"`
	CustomerConsented        bool   `json:"customerConsented"`
	DeliveryStatus           int32  `json:"deliveryStatus"`
	LifetimeDollarsPurchased int32  `json:"lifetimeDollarsPurchased"`
	LifetimeDollarsRefunded  int32  `json:"lifetimeDollarsRefunded"`
	Platform                 int32  `json:"platform"`
	PlayTime                 int32  `json:"playTime"`
	SampleContentProvided    bool   `json:"sampleContentProvided"`
	UserStatus               int32  `json:"userStatus"`
	RefundPreference         int32  `json:"refundPreference"`
}

ConsumptionRequestBody https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest

type DoFunc

type DoFunc func(*http.Request) (*http.Response, error)

func AddHeader

func AddHeader(client HTTPClient, key string, value ...string) DoFunc

func RateLimit added in v1.6.0

func RateLimit(c HTTPClient, reqPerMin int) DoFunc

func RequireResponseBody

func RequireResponseBody(c HTTPClient) DoFunc

func RequireResponseStatus

func RequireResponseStatus(c HTTPClient, status ...int) DoFunc

func SetHeader added in v1.6.0

func SetHeader(c HTTPClient, key string, value ...string) DoFunc

func SetInitializer added in v1.6.0

func SetInitializer(c HTTPClient, init Initializer) DoFunc

func SetRequest added in v1.6.0

func SetRequest(ctx context.Context, c HTTPClient, method string, url string) DoFunc

func SetRequestBody added in v1.6.0

func SetRequestBody(c HTTPClient, m Marshaller, v any) DoFunc

func SetRequestBodyJSON added in v1.6.0

func SetRequestBodyJSON(c HTTPClient, v any) DoFunc

func SetResponseBodyHandler added in v1.6.0

func SetResponseBodyHandler(c HTTPClient, u Unmarshaller, ptr any) DoFunc

func SetResponseErrorHandler added in v1.20.0

func SetResponseErrorHandler(c HTTPClient, u Unmarshaller, ptr any) DoFunc

func SetRetry added in v1.9.0

func SetRetry(c HTTPClient, bo Backoff, shouldRetry func(int, error) bool) DoFunc

func (DoFunc) Do

func (d DoFunc) Do(req *http.Request) (*http.Response, error)

type Environment added in v1.12.0

type Environment string
const (
	Sandbox    Environment = "Sandbox"
	Production Environment = "Production"
)

Environment https://developer.apple.com/documentation/appstoreserverapi/environment

type Error added in v1.14.0

type Error struct {
	// contains filtered or unexported fields
}

func (*Error) As added in v1.14.0

func (e *Error) As(target interface{}) bool

func (*Error) Error added in v1.14.0

func (e *Error) Error() string

func (*Error) ErrorCode added in v1.14.0

func (e *Error) ErrorCode() int

func (*Error) ErrorMessage added in v1.14.0

func (e *Error) ErrorMessage() string

func (*Error) Is added in v1.14.0

func (e *Error) Is(target error) bool

func (*Error) RetryAfter added in v1.14.0

func (e *Error) RetryAfter() int64

type ExtendRenewalDateRequest

type ExtendRenewalDateRequest struct {
	ExtendByDays      int32            `json:"extendByDays"`
	ExtendReasonCode  ExtendReasonCode `json:"extendReasonCode"`
	RequestIdentifier string           `json:"requestIdentifier"`
}

ExtendRenewalDateRequest https://developer.apple.com/documentation/appstoreserverapi/extendrenewaldaterequest

type FirstSendAttemptResult

type FirstSendAttemptResult string

https://developer.apple.com/documentation/appstoreserverapi/firstsendattemptresult

const (
	FirstSendAttemptResultSuccess            FirstSendAttemptResult = "SUCCESS"
	FirstSendAttemptResultCircularRedirect   FirstSendAttemptResult = "CIRCULAR_REDIRECT"
	FirstSendAttemptResultInvalidResponse    FirstSendAttemptResult = "INVALID_RESPONSE"
	FirstSendAttemptResultNoResponse         FirstSendAttemptResult = "NO_RESPONSE"
	FirstSendAttemptResultOther              FirstSendAttemptResult = "OTHER"
	FirstSendAttemptResultPrematureClose     FirstSendAttemptResult = "PREMATURE_CLOSE"
	FirstSendAttemptResultSocketIssue        FirstSendAttemptResult = "SOCKET_ISSUE"
	FirstSendAttemptResultTimedOut           FirstSendAttemptResult = "TIMED_OUT"
	FirstSendAttemptResultTlsIssue           FirstSendAttemptResult = "TLS_ISSUE"
	FirstSendAttemptResultUnsupportedCharset FirstSendAttemptResult = "UNSUPPORTED_CHARSET"
)

type HTTPClient

type HTTPClient interface {
	Do(r *http.Request) (*http.Response, error)
}

type HistoryResponse

type HistoryResponse struct {
	AppAppleId         int64       `json:"appAppleId"`
	BundleId           string      `json:"bundleId"`
	Environment        Environment `json:"environment"`
	HasMore            bool        `json:"hasMore"`
	Revision           string      `json:"revision"`
	SignedTransactions []string    `json:"signedTransactions"`
}

HistoryResponse https://developer.apple.com/documentation/appstoreserverapi/historyresponse

type IAPType added in v1.12.0

type IAPType string

IAPType https://developer.apple.com/documentation/appstoreserverapi/type

const (
	AutoRenewable IAPType = "Auto-Renewable Subscription"
	NonConsumable IAPType = "Non-Consumable"
	Consumable    IAPType = "Consumable"
	NonRenewable  IAPType = "Non-Renewing Subscription"
)

type Initializer added in v1.6.0

type Initializer func(HTTPClient) (DoFunc, error)

type JWSDecodedHeader

type JWSDecodedHeader struct {
	Alg string   `json:"alg,omitempty"`
	Kid string   `json:"kid,omitempty"`
	X5C []string `json:"x5c,omitempty"`
}

JWSDecodedHeader https://developer.apple.com/documentation/appstoreserverapi/jwsdecodedheader

type JWSRenewalInfoDecodedPayload

type JWSRenewalInfoDecodedPayload struct {
	AutoRenewProductId          string            `json:"autoRenewProductId"`
	AutoRenewStatus             int32             `json:"autoRenewStatus"`
	Environment                 Environment       `json:"environment"`
	ExpirationIntent            int32             `json:"expirationIntent"`
	GracePeriodExpiresDate      int64             `json:"gracePeriodExpiresDate"`
	IsInBillingRetryPeriod      *bool             `json:"isInBillingRetryPeriod"`
	OfferIdentifier             string            `json:"offerIdentifier"`
	OfferType                   int32             `json:"offerType"`
	OriginalTransactionId       string            `json:"originalTransactionId"`
	PriceIncreaseStatus         *int32            `json:"priceIncreaseStatus"`
	ProductId                   string            `json:"productId"`
	RecentSubscriptionStartDate int64             `json:"recentSubscriptionStartDate"`
	RenewalDate                 int64             `json:"renewalDate"`
	SignedDate                  int64             `json:"signedDate"`
	RenewalPrice                int64             `json:"renewalPrice,omitempty"`
	Currency                    string            `json:"currency,omitempty"`
	OfferDiscountType           OfferDiscountType `json:"offerDiscountType,omitempty"`
	EligibleWinBackOfferIds     []string          `json:"eligibleWinBackOfferIds,omitempty"`
}

JWSRenewalInfoDecodedPayload https://developer.apple.com/documentation/appstoreserverapi/jwsrenewalinfodecodedpayload

func (JWSRenewalInfoDecodedPayload) Valid added in v1.15.0

type JWSTransaction

type JWSTransaction struct {
	TransactionID               string            `json:"transactionId,omitempty"`
	OriginalTransactionId       string            `json:"originalTransactionId,omitempty"`
	WebOrderLineItemId          string            `json:"webOrderLineItemId,omitempty"`
	BundleID                    string            `json:"bundleId,omitempty"`
	ProductID                   string            `json:"productId,omitempty"`
	SubscriptionGroupIdentifier string            `json:"subscriptionGroupIdentifier,omitempty"`
	PurchaseDate                int64             `json:"purchaseDate,omitempty"`
	OriginalPurchaseDate        int64             `json:"originalPurchaseDate,omitempty"`
	ExpiresDate                 int64             `json:"expiresDate,omitempty"`
	Quantity                    int32             `json:"quantity,omitempty"`
	Type                        IAPType           `json:"type,omitempty"`
	AppAccountToken             string            `json:"appAccountToken,omitempty"`
	InAppOwnershipType          string            `json:"inAppOwnershipType,omitempty"`
	SignedDate                  int64             `json:"signedDate,omitempty"`
	OfferType                   int32             `json:"offerType,omitempty"`
	OfferIdentifier             string            `json:"offerIdentifier,omitempty"`
	RevocationDate              int64             `json:"revocationDate,omitempty"`
	RevocationReason            *int32            `json:"revocationReason,omitempty"`
	IsUpgraded                  bool              `json:"isUpgraded,omitempty"`
	Storefront                  string            `json:"storefront,omitempty"`
	StorefrontId                string            `json:"storefrontId,omitempty"`
	TransactionReason           TransactionReason `json:"transactionReason,omitempty"`
	Environment                 Environment       `json:"environment,omitempty"`
	Price                       int64             `json:"price,omitempty"`
	Currency                    string            `json:"currency,omitempty"`
	OfferDiscountType           OfferDiscountType `json:"offerDiscountType,omitempty"`
}

JWSTransaction https://developer.apple.com/documentation/appstoreserverapi/jwstransaction

func (JWSTransaction) Valid

func (J JWSTransaction) Valid() error

type JitterBackoff added in v1.9.0

type JitterBackoff struct {
	Initial    time.Duration
	Max        time.Duration
	Multiplier float64
	// contains filtered or unexported fields
}

func (*JitterBackoff) Pause added in v1.9.0

func (bo *JitterBackoff) Pause() time.Duration

type LastTransactionsItem

type LastTransactionsItem struct {
	OriginalTransactionId string `json:"originalTransactionId"`
	Status                int32  `json:"status"`
	SignedRenewalInfo     string `json:"signedRenewalInfo"`
	SignedTransactionInfo string `json:"signedTransactionInfo"`
}

type Marshaller added in v1.6.0

type Marshaller func(v any) ([]byte, error)

type MassExtendRenewalDateRequest added in v1.12.0

type MassExtendRenewalDateRequest struct {
	RequestIdentifier      string   `json:"requestIdentifier"`
	ExtendByDays           int32    `json:"extendByDays"`
	ExtendReasonCode       int32    `json:"extendReasonCode"`
	ProductId              string   `json:"productId"`
	StorefrontCountryCodes []string `json:"storefrontCountryCodes"`
}

MassExtendRenewalDateRequest https://developer.apple.com/documentation/appstoreserverapi/massextendrenewaldaterequest

type MassExtendRenewalDateStatusResponse added in v1.12.0

type MassExtendRenewalDateStatusResponse struct {
	RequestIdentifier string `json:"requestIdentifier"`
	Complete          bool   `json:"complete"`
	CompleteDate      int64  `json:"completeDate,omitempty"`
	FailedCount       int64  `json:"failedCount,omitempty"`
	SucceededCount    int64  `json:"succeededCount,omitempty"`
}

MassExtendRenewalDateStatusResponse https://developer.apple.com/documentation/appstoreserverapi/massextendrenewaldatestatusresponse

type NotificationData added in v1.10.0

type NotificationData struct {
	jwt.RegisteredClaims
	AppAppleID               int    `json:"appAppleId"`
	BundleID                 string `json:"bundleId"`
	BundleVersion            string `json:"bundleVersion"`
	ConsumptionRequestReason string `json:"consumptionRequestReason"`
	Environment              string `json:"environment"`
	SignedRenewalInfo        string `json:"signedRenewalInfo"`
	SignedTransactionInfo    string `json:"signedTransactionInfo"`
	Status                   int    `json:"status"`
}

Notification Data

type NotificationHistoryRequest

type NotificationHistoryRequest struct {
	StartDate             int64              `json:"startDate"`
	EndDate               int64              `json:"endDate"`
	OriginalTransactionId string             `json:"originalTransactionId,omitempty"`
	NotificationType      NotificationTypeV2 `json:"notificationType,omitempty"`
	NotificationSubtype   SubtypeV2          `json:"notificationSubtype,omitempty"`
	OnlyFailures          bool               `json:"onlyFailures,omitempty"`
	TransactionId         string             `json:"transactionId,omitempty"`
}

NotificationHistoryRequest https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryrequest

type NotificationHistoryResponseItem

type NotificationHistoryResponseItem struct {
	SignedPayload          string                 `json:"signedPayload"`
	FirstSendAttemptResult FirstSendAttemptResult `json:"firstSendAttemptResult"`
	SendAttempts           []SendAttemptItem      `json:"sendAttempts"`
}

NotificationHistoryResponseItem https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponseitem

type NotificationHistoryResponses

type NotificationHistoryResponses struct {
	HasMore             bool                              `json:"hasMore"`
	PaginationToken     string                            `json:"paginationToken"`
	NotificationHistory []NotificationHistoryResponseItem `json:"notificationHistory"`
}

NotificationHistoryResponses https://developer.apple.com/documentation/appstoreserverapi/notificationhistoryresponse

type NotificationPayload added in v1.10.0

type NotificationPayload struct {
	jwt.RegisteredClaims
	NotificationType    string           `json:"notificationType"`
	Subtype             string           `json:"subtype"`
	NotificationUUID    string           `json:"notificationUUID"`
	NotificationVersion string           `json:"notificationVersion"`
	Data                NotificationData `json:"data"`
}

Notification signed payload

type NotificationTypeV2

type NotificationTypeV2 string
const (
	NotificationTypeV2ConsumptionRequest     NotificationTypeV2 = "CONSUMPTION_REQUEST"
	NotificationTypeV2DidChangeRenewalPref   NotificationTypeV2 = "DID_CHANGE_RENEWAL_PREF"
	NotificationTypeV2DidChangeRenewalStatus NotificationTypeV2 = "DID_CHANGE_RENEWAL_STATUS"
	NotificationTypeV2DidFailToRenew         NotificationTypeV2 = "DID_FAIL_TO_RENEW"
	NotificationTypeV2DidRenew               NotificationTypeV2 = "DID_RENEW"
	NotificationTypeV2Expired                NotificationTypeV2 = "EXPIRED"
	NotificationTypeV2GracePeriodExpired     NotificationTypeV2 = "GRACE_PERIOD_EXPIRED"
	NotificationTypeV2OfferRedeemed          NotificationTypeV2 = "OFFER_REDEEMED"
	NotificationTypeV2PriceIncrease          NotificationTypeV2 = "PRICE_INCREASE"
	NotificationTypeV2Refund                 NotificationTypeV2 = "REFUND"
	NotificationTypeV2RefundDeclined         NotificationTypeV2 = "REFUND_DECLINED"
	NotificationTypeV2RenewalExtended        NotificationTypeV2 = "RENEWAL_EXTENDED"
	NotificationTypeV2Revoke                 NotificationTypeV2 = "REVOKE"
	NotificationTypeV2Subscribed             NotificationTypeV2 = "SUBSCRIBED"
	NotificationTypeV2OneTimeCharge          NotificationTypeV2 = "ONE_TIME_CHARGE"
	NotificationTypeV2RefundReversed         NotificationTypeV2 = "REFUND_REVERSED"
	NotificationTypeV2ExternalPurchaseToken  NotificationTypeV2 = "EXTERNAL_PURCHASE_TOKEN"
	NotificationTypeV2RenewalExtension       NotificationTypeV2 = "RENEWAL_EXTENSION"
	NotificationTypeV2Test                   NotificationTypeV2 = "TEST"
)

list of notificationType https://developer.apple.com/documentation/appstoreservernotifications/notificationtype

type NotificationV2 added in v1.33.0

type NotificationV2 struct {
	SignedPayload string `json:"signedPayload"`
}

Notification body https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2

type OfferDiscountType added in v1.18.0

type OfferDiscountType string
const (
	OfferDiscountTypeFreeTrial  OfferDiscountType = "FREE_TRIAL"
	OfferDiscountTypePayAsYouGo OfferDiscountType = "PAY_AS_YOU_GO"
	OfferDiscountTypePayUpFront OfferDiscountType = "PAY_UP_FRONT"
)

type OneSecondBackoff added in v1.9.0

type OneSecondBackoff struct{}

func (*OneSecondBackoff) Pause added in v1.9.0

func (bo *OneSecondBackoff) Pause() time.Duration

type OrderLookupResponse

type OrderLookupResponse struct {
	Status             int      `json:"status"`
	SignedTransactions []string `json:"signedTransactions"`
}

OrderLookupResponse https://developer.apple.com/documentation/appstoreserverapi/orderlookupresponse

type RefundLookupResponse

type RefundLookupResponse struct {
	HasMore            bool     `json:"hasMore"`
	Revision           string   `json:"revision"`
	SignedTransactions []string `json:"signedTransactions"`
}

RefundLookupResponse same as the RefundHistoryResponse https://developer.apple.com/documentation/appstoreserverapi/refundhistoryresponse

type RenewalInfo added in v1.10.0

type RenewalInfo struct {
	jwt.RegisteredClaims
	OriginalTransactionID  string `json:"originalTransactionId"`
	ExpirationIntent       int    `json:"expirationIntent"`
	AutoRenewProductId     string `json:"autoRenewProductId"`
	ProductID              string `json:"productId"`
	AutoRenewStatus        int    `json:"autoRenewStatus"`
	IsInBillingRetryPeriod *bool  `json:"isInBillingRetryPeriod"`
	SignedDate             int    `json:"signedDate"`
	Environment            string `json:"environment"`
}

Notification Renewal Info

type SendAttemptItem added in v1.12.0

type SendAttemptItem struct {
	AttemptDate       int64                  `json:"attemptDate"`
	SendAttemptResult FirstSendAttemptResult `json:"sendAttemptResult"`
}

SendAttemptItem https://developer.apple.com/documentation/appstoreserverapi/sendattemptitem

type SendTestNotificationResponse

type SendTestNotificationResponse struct {
	TestNotificationToken string `json:"testNotificationToken"`
}

SendTestNotificationResponse https://developer.apple.com/documentation/appstoreserverapi/sendtestnotificationresponse

type StatusResponse

type StatusResponse struct {
	Environment Environment                       `json:"environment"`
	AppAppleId  int64                             `json:"appAppleId"`
	BundleId    string                            `json:"bundleId"`
	Data        []SubscriptionGroupIdentifierItem `json:"data"`
}

StatusResponse https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses

type StoreClient

type StoreClient struct {
	Token *Token
	// contains filtered or unexported fields
}

func NewStoreClient

func NewStoreClient(config *StoreConfig) *StoreClient

NewStoreClient create a appstore server api client

func NewStoreClientWithHTTPClient

func NewStoreClientWithHTTPClient(config *StoreConfig, httpClient HTTPClient) *StoreClient

NewStoreClientWithHTTPClient creates a appstore server api client with a custom http client.

func (*StoreClient) Do

func (c *StoreClient) Do(ctx context.Context, method string, url string, body io.Reader) (int, []byte, error)

Per doc: https://developer.apple.com/documentation/appstoreserverapi#topics

func (*StoreClient) ExtendSubscriptionRenewalDate

func (c *StoreClient) ExtendSubscriptionRenewalDate(ctx context.Context, originalTransactionId string, body ExtendRenewalDateRequest) (statusCode int, err error)

ExtendSubscriptionRenewalDate https://developer.apple.com/documentation/appstoreserverapi/extend_a_subscription_renewal_date

func (*StoreClient) ExtendSubscriptionRenewalDateForAll added in v1.14.0

func (c *StoreClient) ExtendSubscriptionRenewalDateForAll(ctx context.Context, body MassExtendRenewalDateRequest) (statusCode int, err error)

ExtendSubscriptionRenewalDateForAll https://developer.apple.com/documentation/appstoreserverapi/extend_subscription_renewal_dates_for_all_active_subscribers

func (*StoreClient) GetALLSubscriptionStatuses

func (c *StoreClient) GetALLSubscriptionStatuses(ctx context.Context, originalTransactionId string) (*StatusResponse, error)

GetALLSubscriptionStatuses https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses

func (*StoreClient) GetRefundHistory

func (c *StoreClient) GetRefundHistory(ctx context.Context, originalTransactionId string) (responses []*RefundLookupResponse, err error)

GetRefundHistory https://developer.apple.com/documentation/appstoreserverapi/get_refund_history

func (*StoreClient) GetSubscriptionRenewalDataStatus added in v1.14.0

func (c *StoreClient) GetSubscriptionRenewalDataStatus(ctx context.Context, productId, requestIdentifier string) (statusCode int, rsp *MassExtendRenewalDateStatusResponse, err error)

GetSubscriptionRenewalDataStatus https://developer.apple.com/documentation/appstoreserverapi/get_status_of_subscription_renewal_date_extensions

func (*StoreClient) GetTestNotificationStatus

func (c *StoreClient) GetTestNotificationStatus(ctx context.Context, testNotificationToken string) (int, []byte, error)

GetTestNotificationStatus https://developer.apple.com/documentation/appstoreserverapi/get_test_notification_status

func (*StoreClient) GetTransactionHistory

func (c *StoreClient) GetTransactionHistory(ctx context.Context, originalTransactionId string, query *url.Values) (responses []*HistoryResponse, err error)

GetTransactionHistory https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history

func (*StoreClient) GetTransactionInfo added in v1.11.0

func (c *StoreClient) GetTransactionInfo(ctx context.Context, transactionId string) (*TransactionInfoResponse, error)

GetTransactionInfo https://developer.apple.com/documentation/appstoreserverapi/get_transaction_info

func (*StoreClient) ParseJWSEncodeString added in v1.15.0

func (c *StoreClient) ParseJWSEncodeString(jwsEncode string) (interface{}, error)

ParseJWSEncodeString parse the jws encode string, such as JWSTransaction and JWSRenewalInfoDecodedPayload

func (*StoreClient) ParseNotificationV2 added in v1.10.0

func (c *StoreClient) ParseNotificationV2(tokenStr string) (*jwt.Token, error)

func (*StoreClient) ParseNotificationV2Payload added in v1.33.0

func (c *StoreClient) ParseNotificationV2Payload(signedPayload string) (*NotificationPayload, error)

ParseNotificationV2 parses the signedPayload field from an App Store Server Notification response body (https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2)

func (*StoreClient) ParseNotificationV2RenewalInfo added in v1.33.0

func (c *StoreClient) ParseNotificationV2RenewalInfo(signedRenewalInfo string) (*JWSRenewalInfoDecodedPayload, error)

ParseNotificationV2 parses the signedRenewalInfo from decoded notification data (https://developer.apple.com/documentation/appstoreservernotifications/data)

func (*StoreClient) ParseNotificationV2TransactionInfo added in v1.33.0

func (c *StoreClient) ParseNotificationV2TransactionInfo(signedTransactionInfo string) (*JWSTransaction, error)

ParseNotificationV2 parses the signedTransactionInfo from decoded notification data (https://developer.apple.com/documentation/appstoreservernotifications/data)

func (*StoreClient) ParseNotificationV2WithClaim added in v1.17.0

func (c *StoreClient) ParseNotificationV2WithClaim(tokenStr string) (jwt.Claims, error)

func (*StoreClient) ParseSignedPayload added in v1.33.0

func (c *StoreClient) ParseSignedPayload(tokenStr string, claims jwt.Claims) error

ParseSignedPayload parses any signed JWS payload from a server notification into a struct that implements the jwt.Claims interface.

func (*StoreClient) ParseSignedTransactions

func (c *StoreClient) ParseSignedTransactions(transactions []string) ([]*JWSTransaction, error)

ParseSignedTransactions parse the jws singed transactions Per doc: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6

func (*StoreClient) SendConsumptionInfo

func (c *StoreClient) SendConsumptionInfo(ctx context.Context, originalTransactionId string, body ConsumptionRequestBody) (statusCode int, err error)

SendConsumptionInfo https://developer.apple.com/documentation/appstoreserverapi/send_consumption_information

func (*StoreClient) SendRequestTestNotification

func (c *StoreClient) SendRequestTestNotification(ctx context.Context) (int, []byte, error)

SendRequestTestNotification https://developer.apple.com/documentation/appstoreserverapi/request_a_test_notification

type StoreConfig

type StoreConfig struct {
	KeyContent         []byte         // Loads a .p8 certificate
	KeyID              string         // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
	BundleID           string         // Your app’s bundle ID
	Issuer             string         // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
	Sandbox            bool           // default is Production
	TokenIssuedAtFunc  func() int64   // The token’s creation time func. Default is current timestamp.
	TokenExpiredAtFunc func() int64   // The token’s expiration time func. Default is one hour later.
	TrustedCertPool    *x509.CertPool // The pool of trusted root certificates. Default is a pool containing only Apple Root CA - G3.
}

type SubscriptionGroupIdentifierItem

type SubscriptionGroupIdentifierItem struct {
	SubscriptionGroupIdentifier string                 `json:"subscriptionGroupIdentifier"`
	LastTransactions            []LastTransactionsItem `json:"lastTransactions"`
}

type SubtypeV2

type SubtypeV2 string

SubtypeV2 is type

const (
	SubTypeV2InitialBuy        SubtypeV2 = "INITIAL_BUY"
	SubTypeV2Resubscribe       SubtypeV2 = "RESUBSCRIBE"
	SubTypeV2Downgrade         SubtypeV2 = "DOWNGRADE"
	SubTypeV2Upgrade           SubtypeV2 = "UPGRADE"
	SubTypeV2AutoRenewEnabled  SubtypeV2 = "AUTO_RENEW_ENABLED"
	SubTypeV2AutoRenewDisabled SubtypeV2 = "AUTO_RENEW_DISABLED"
	SubTypeV2Voluntary         SubtypeV2 = "VOLUNTARY"
	SubTypeV2BillingRetry      SubtypeV2 = "BILLING_RETRY"
	SubTypeV2PriceIncrease     SubtypeV2 = "PRICE_INCREASE"
	SubTypeV2GracePeriod       SubtypeV2 = "GRACE_PERIOD"
	SubTypeV2BillingRecovery   SubtypeV2 = "BILLING_RECOVERY"
	SubTypeV2Pending           SubtypeV2 = "PENDING"
	SubTypeV2Accepted          SubtypeV2 = "ACCEPTED"
)

list of subtypes https://developer.apple.com/documentation/appstoreservernotifications/subtype

type Token

type Token struct {
	sync.Mutex

	KeyContent    []byte       // Loads a .p8 certificate
	KeyID         string       // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
	BundleID      string       // Your app’s bundle ID
	Issuer        string       // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
	Sandbox       bool         // default is Production
	IssuedAtFunc  func() int64 // The token’s creation time func. Default is current timestamp.
	ExpiredAtFunc func() int64 // The token’s expiration time func.

	// internal variables
	AuthKey   *ecdsa.PrivateKey // .p8 private key
	ExpiredAt int64             // The token’s expiration time, in UNIX time. Tokens that expire more than 60 minutes after the time in iat are not valid (Ex: 1623086400)
	Bearer    string            // Authorized bearer token
}

Token represents an Apple Provider Authentication Token (JSON Web Token).

func (*Token) Expired

func (t *Token) Expired() bool

Expired checks to see if the token has expired.

func (*Token) Generate

func (t *Token) Generate() error

Generate creates a new token.

func (*Token) GenerateIfExpired

func (t *Token) GenerateIfExpired() (string, error)

GenerateIfExpired checks to see if the token is about to expire and generates a new token.

func (*Token) WithConfig

func (t *Token) WithConfig(c *StoreConfig)

type TransactionInfo added in v1.10.0

type TransactionInfo struct {
	jwt.RegisteredClaims
	TransactionId               string `json:"transactionId"`
	OriginalTransactionID       string `json:"originalTransactionId"`
	WebOrderLineItemID          string `json:"webOrderLineItemId"`
	BundleID                    string `json:"bundleId"`
	ProductID                   string `json:"productId"`
	SubscriptionGroupIdentifier string `json:"subscriptionGroupIdentifier"`
	PurchaseDate                int    `json:"purchaseDate"`
	OriginalPurchaseDate        int    `json:"originalPurchaseDate"`
	ExpiresDate                 int    `json:"expiresDate"`
	Type                        string `json:"type"`
	InAppOwnershipType          string `json:"inAppOwnershipType"`
	SignedDate                  int    `json:"signedDate"`
	Environment                 string `json:"environment"`
}

Notification Transaction Info

type TransactionInfoResponse added in v1.11.0

type TransactionInfoResponse struct {
	SignedTransactionInfo string `json:"signedTransactionInfo"`
}

TransactionInfoResponse https://developer.apple.com/documentation/appstoreserverapi/transactioninforesponse

type TransactionReason added in v1.12.0

type TransactionReason string

TransactionReason indicates the cause of a purchase transaction, https://developer.apple.com/documentation/appstoreservernotifications/transactionreason

type Unmarshaller added in v1.6.0

type Unmarshaller func(b []byte, v any) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL