Skip to content

Commit 16a3235

Browse files
authored
Merge pull request #22 from happenslol/parse-notification-typed-claims
feat: Add typed parsing methods for notification payloads
2 parents 0aac0af + f797be4 commit 16a3235

File tree

3 files changed

+98
-6
lines changed

3 files changed

+98
-6
lines changed

README.md

+50-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
Apple App Store Server Golang Library
2-
================
1+
# Apple App Store Server Golang Library
32

43
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).
54

6-
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.
5+
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.
76

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

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

2019
> Log in to [App Store Connect](https://appstoreconnect.apple.com/login) and complete the following steps:
20+
>
2121
> - Select Users and Access, and then select the Keys tab.
2222
> - Select In-App Purchase under the Key Type.
2323
> - Click Generate API Key or the Add (+) button.
@@ -70,8 +70,8 @@ func main() {
7070
- 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.
7171
- 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.
7272
- Error handling
73-
- handler error per [apple store server api error](https://developer.apple.com/documentation/appstoreserverapi/error_codes) document
74-
- [error definition](./error.go)
73+
- handler error per [apple store server api error](https://developer.apple.com/documentation/appstoreserverapi/error_codes) document
74+
- [error definition](./error.go)
7575

7676
### Look Up Order ID
7777

@@ -220,11 +220,55 @@ func main() {
220220
}
221221
```
222222

223+
### Parse signed notification payloads from App Store Server Notification request
224+
225+
```go
226+
import (
227+
"encoding/json"
228+
229+
"github.com/richzw/appstore"
230+
)
231+
232+
func main() {
233+
c := &appstore.StoreConfig{
234+
KeyContent: []byte(ACCOUNTPRIVATEKEY),
235+
KeyID: "FAKEKEYID",
236+
BundleID: "fake.bundle.id",
237+
Issuer: "xxxxx-xx-xx-xx-xxxxxxxxxx",
238+
Sandbox: false,
239+
}
240+
a := appstore.NewStoreClient(c)
241+
242+
reqBody := []byte{} // Request from App Store Server Notification
243+
var notification appstore.NotificationV2
244+
if _, err := json.Unmarshal(reqBody, &notification); err != nil {
245+
panic(err)
246+
}
247+
248+
// Parse the notification payload
249+
payload, err := a.ParseNotificationV2Payload(notification.SignedPayload)
250+
if err != nil {
251+
panic(err)
252+
}
253+
254+
// Parse the transaction info
255+
transactionInfo, err := a.ParseNotificationV2TransactionInfo(payload.Data.SignedTransactionInfo)
256+
if err != nil {
257+
panic(err)
258+
}
259+
260+
// Parse the renewal info
261+
renewalInfo, err := a.ParseNotificationV2RenewalInfo(payload.Data.SignedRenewalInfo)
262+
if err != nil {
263+
panic(err)
264+
}
265+
}
266+
```
267+
223268
# Support
224269

225270
App Store Server API [1.12+](https://developer.apple.com/documentation/appstoreserverapi)
226271

227272
# License
228273

229274
appstore is licensed under the MIT.
230-

model.go

+5
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ type SendTestNotificationResponse struct {
304304
TestNotificationToken string `json:"testNotificationToken"`
305305
}
306306

307+
// Notification body https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2
308+
type NotificationV2 struct {
309+
SignedPayload string `json:"signedPayload"`
310+
}
311+
307312
// Notification signed payload
308313
type NotificationPayload struct {
309314
jwt.RegisteredClaims

store.go

+43
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,49 @@ func (c *StoreClient) ParseNotificationV2WithClaim(tokenStr string) (jwt.Claims,
407407
return result, err
408408
}
409409

410+
// ParseSignedPayload parses any signed JWS payload from a server notification into
411+
// a struct that implements the jwt.Claims interface.
412+
func (c *StoreClient) ParseSignedPayload(tokenStr string, claims jwt.Claims) error {
413+
_, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (any, error) {
414+
return c.cert.extractPublicKeyFromToken(tokenStr)
415+
})
416+
417+
return err
418+
}
419+
420+
// ParseNotificationV2 parses the signedPayload field from an App Store Server Notification response body
421+
// (https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv2)
422+
func (c *StoreClient) ParseNotificationV2Payload(signedPayload string) (*NotificationPayload, error) {
423+
var result NotificationPayload
424+
if err := c.ParseSignedPayload(signedPayload, &result); err != nil {
425+
return nil, err
426+
}
427+
428+
return &result, nil
429+
}
430+
431+
// ParseNotificationV2 parses the signedTransactionInfo from decoded notification data
432+
// (https://developer.apple.com/documentation/appstoreservernotifications/data)
433+
func (c *StoreClient) ParseNotificationV2TransactionInfo(signedTransactionInfo string) (*JWSRenewalInfoDecodedPayload, error) {
434+
var result JWSRenewalInfoDecodedPayload
435+
if err := c.ParseSignedPayload(signedTransactionInfo, &result); err != nil {
436+
return nil, err
437+
}
438+
439+
return &result, nil
440+
}
441+
442+
// ParseNotificationV2 parses the signedRenewalInfo from decoded notification data
443+
// (https://developer.apple.com/documentation/appstoreservernotifications/data)
444+
func (c *StoreClient) ParseNotificationV2RenewalInfo(signedRenewalInfo string) (*JWSTransaction, error) {
445+
var result JWSTransaction
446+
if err := c.ParseSignedPayload(signedRenewalInfo, &result); err != nil {
447+
return nil, err
448+
}
449+
450+
return &result, nil
451+
}
452+
410453
// ParseSignedTransactions parse the jws singed transactions
411454
// Per doc: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6
412455
func (c *StoreClient) ParseSignedTransactions(transactions []string) ([]*JWSTransaction, error) {

0 commit comments

Comments
 (0)