Skip to content

Commit

Permalink
Merge pull request #22 from happenslol/parse-notification-typed-claims
Browse files Browse the repository at this point in the history
feat: Add typed parsing methods for notification payloads
  • Loading branch information
richzw authored Nov 1, 2024
2 parents 0aac0af + f797be4 commit 16a3235
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 6 deletions.
56 changes: 50 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
Apple App Store Server Golang Library
================
# Apple App Store Server Golang Library

The Golang server library for the [App Store Server API](https://developer.apple.com/documentation/appstoreserverapi) and [App Store Server Notifications](https://developer.apple.com/documentation/appstoreservernotifications).

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

Expand All @@ -18,6 +17,7 @@ go get github.com/richzw/appstore
### [Generate a Private Key](https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api)

> Log in to [App Store Connect](https://appstoreconnect.apple.com/login) 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.
Expand Down Expand Up @@ -70,8 +70,8 @@ func main() {
- 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
- handler error per [apple store server api error](https://developer.apple.com/documentation/appstoreserverapi/error_codes) document
- [error definition](./error.go)
- handler error per [apple store server api error](https://developer.apple.com/documentation/appstoreserverapi/error_codes) document
- [error definition](./error.go)

### Look Up Order ID

Expand Down Expand Up @@ -220,11 +220,55 @@ func main() {
}
```

### Parse signed notification payloads from App Store Server Notification request

```go
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+](https://developer.apple.com/documentation/appstoreserverapi)

# License

appstore is licensed under the MIT.

5 changes: 5 additions & 0 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ type SendTestNotificationResponse struct {
TestNotificationToken string `json:"testNotificationToken"`
}

// Notification body https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2
type NotificationV2 struct {
SignedPayload string `json:"signedPayload"`
}

// Notification signed payload
type NotificationPayload struct {
jwt.RegisteredClaims
Expand Down
43 changes: 43 additions & 0 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,49 @@ func (c *StoreClient) ParseNotificationV2WithClaim(tokenStr string) (jwt.Claims,
return result, err
}

// ParseSignedPayload parses any signed JWS payload from a server notification into
// a struct that implements the jwt.Claims interface.
func (c *StoreClient) ParseSignedPayload(tokenStr string, claims jwt.Claims) error {
_, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (any, error) {
return c.cert.extractPublicKeyFromToken(tokenStr)
})

return err
}

// ParseNotificationV2 parses the signedPayload field from an App Store Server Notification response body
// (https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2)
func (c *StoreClient) ParseNotificationV2Payload(signedPayload string) (*NotificationPayload, error) {
var result NotificationPayload
if err := c.ParseSignedPayload(signedPayload, &result); err != nil {
return nil, err
}

return &result, nil
}

// ParseNotificationV2 parses the signedTransactionInfo from decoded notification data
// (https://developer.apple.com/documentation/appstoreservernotifications/data)
func (c *StoreClient) ParseNotificationV2TransactionInfo(signedTransactionInfo string) (*JWSRenewalInfoDecodedPayload, error) {
var result JWSRenewalInfoDecodedPayload
if err := c.ParseSignedPayload(signedTransactionInfo, &result); err != nil {
return nil, err
}

return &result, nil
}

// ParseNotificationV2 parses the signedRenewalInfo from decoded notification data
// (https://developer.apple.com/documentation/appstoreservernotifications/data)
func (c *StoreClient) ParseNotificationV2RenewalInfo(signedRenewalInfo string) (*JWSTransaction, error) {
var result JWSTransaction
if err := c.ParseSignedPayload(signedRenewalInfo, &result); err != nil {
return nil, err
}

return &result, nil
}

// ParseSignedTransactions parse the jws singed transactions
// Per doc: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6
func (c *StoreClient) ParseSignedTransactions(transactions []string) ([]*JWSTransaction, error) {
Expand Down

0 comments on commit 16a3235

Please sign in to comment.