appstore

package module
v1.0.5 Latest Latest
Warning

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

Go to latest
Published: Dec 1, 2023 License: MIT Imports: 21 Imported by: 0

README

App Store Server API Golang Client

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/Jonathan-Mckenzie/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/Jonathan-Mckenzie/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)

    transantions, err := a.ParseSignedTransactions([]{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.
  • Error handling

Look Up Order ID
import(
    "github.com/Jonathan-Mckenzie/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/Jonathan-Mckenzie/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/Jonathan-Mckenzie/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/Jonathan-Mckenzie/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/Jonathan-Mckenzie/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
}

Support

App Store Server API 1.8

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/v1/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.")
)
View Source
var (
	ErrGetNotificationHistory = errors.New("failed to fetch notification history")
	ErrGetTransactionHistory  = errors.New("failed to fetch transaction history")
)
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

func ShouldRetryDefault(status int, err error) bool

Types

type Backoff

type Backoff interface {
	Pause() time.Duration
}

type Cert

type Cert struct {
}

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"`
}

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

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

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

func SetInitializer

func SetInitializer(c HTTPClient, init Initializer) DoFunc

func SetRequest

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

func SetRequestBody

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

func SetRequestBodyJSON

func SetRequestBodyJSON(c HTTPClient, v any) DoFunc

func SetResponseBodyHandler

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

func SetRetry

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

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

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

type Error

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

func (*Error) As

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

func (*Error) Error

func (e *Error) Error() string

func (*Error) ErrorCode

func (e *Error) ErrorCode() int

func (*Error) ErrorMessage

func (e *Error) ErrorMessage() string

func (*Error) Is

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

func (*Error) RetryAfter

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

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

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                   string      `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"`
}

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

func (JWSRenewalInfoDecodedPayload) Valid

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                       int32             `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

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

func (*JitterBackoff) Pause

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

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

type MassExtendRenewalDateRequest

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

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

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

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"`
	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

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"
)

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

type OfferDiscountType

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

type OneSecondBackoff

type OneSecondBackoff struct{}

func (*OneSecondBackoff) Pause

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

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

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 *http.Client) *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) 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

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) ParseJWSEncodeString

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

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

func (*StoreClient) ParseNotificationV2

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

func (*StoreClient) ParseNotificationV2WithClaim

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

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
}

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

	// 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

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

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

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

type TransactionReason

type TransactionReason string

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

type Unmarshaller

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