Skip to content

Commit 9e03c37

Browse files
committed
feat(api): add ParseNotificationV2 to parse notification v2 body
1 parent 9555487 commit 9e03c37

File tree

3 files changed

+101
-0
lines changed

3 files changed

+101
-0
lines changed

cert.go

+41
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package appstore
22

33
import (
4+
"crypto/ecdsa"
45
"crypto/x509"
56
"encoding/base64"
67
"encoding/json"
78
"errors"
9+
"fmt"
810
"strings"
911
)
1012

@@ -92,3 +94,42 @@ func (c *Cert) verifyCert(rootCert, intermediaCert, leafCert *x509.Certificate)
9294

9395
return nil
9496
}
97+
98+
func (c *Cert) extractPublicKeyFromToken(token string) (*ecdsa.PublicKey, error) {
99+
rootCertBytes, err := c.extractCertByIndex(token, 2)
100+
if err != nil {
101+
return nil, err
102+
}
103+
rootCert, err := x509.ParseCertificate(rootCertBytes)
104+
if err != nil {
105+
return nil, fmt.Errorf("appstore failed to parse root certificate")
106+
}
107+
108+
intermediaCertBytes, err := c.extractCertByIndex(token, 1)
109+
if err != nil {
110+
return nil, err
111+
}
112+
intermediaCert, err := x509.ParseCertificate(intermediaCertBytes)
113+
if err != nil {
114+
return nil, fmt.Errorf("appstore failed to parse intermediate certificate")
115+
}
116+
117+
leafCertBytes, err := c.extractCertByIndex(token, 0)
118+
if err != nil {
119+
return nil, err
120+
}
121+
leafCert, err := x509.ParseCertificate(leafCertBytes)
122+
if err != nil {
123+
return nil, fmt.Errorf("appstore failed to parse leaf certificate")
124+
}
125+
if err = c.verifyCert(rootCert, intermediaCert, leafCert); err != nil {
126+
return nil, err
127+
}
128+
129+
switch pk := leafCert.PublicKey.(type) {
130+
case *ecdsa.PublicKey:
131+
return pk, nil
132+
default:
133+
return nil, errors.New("appstore public key must be of type ecdsa.PublicKey")
134+
}
135+
}

model.go

+54
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package appstore
22

3+
import "github.com/golang-jwt/jwt/v4"
4+
35
// OrderLookupResponse https://developer.apple.com/documentation/appstoreserverapi/orderlookupresponse
46
type OrderLookupResponse struct {
57
Status int `json:"status"`
@@ -196,3 +198,55 @@ const (
196198
type SendTestNotificationResponse struct {
197199
TestNotificationToken string `json:"testNotificationToken"`
198200
}
201+
202+
// Notification signed payload
203+
type NotificationPayload struct {
204+
jwt.RegisteredClaims
205+
NotificationType string `json:"notificationType"`
206+
Subtype string `json:"subtype"`
207+
NotificationUUID string `json:"notificationUUID"`
208+
NotificationVersion string `json:"notificationVersion"`
209+
Data NotificationData `json:"data"`
210+
}
211+
212+
// Notification Data
213+
type NotificationData struct {
214+
jwt.RegisteredClaims
215+
AppAppleID int `json:"appAppleId"`
216+
BundleID string `json:"bundleId"`
217+
BundleVersion string `json:"bundleVersion"`
218+
Environment string `json:"environment"`
219+
SignedRenewalInfo string `json:"signedRenewalInfo"`
220+
SignedTransactionInfo string `json:"signedTransactionInfo"`
221+
}
222+
223+
// Notification Transaction Info
224+
type TransactionInfo struct {
225+
jwt.RegisteredClaims
226+
TransactionId string `json:"transactionId"`
227+
OriginalTransactionID string `json:"originalTransactionId"`
228+
WebOrderLineItemID string `json:"webOrderLineItemId"`
229+
BundleID string `json:"bundleId"`
230+
ProductID string `json:"productId"`
231+
SubscriptionGroupIdentifier string `json:"subscriptionGroupIdentifier"`
232+
PurchaseDate int `json:"purchaseDate"`
233+
OriginalPurchaseDate int `json:"originalPurchaseDate"`
234+
ExpiresDate int `json:"expiresDate"`
235+
Type string `json:"type"`
236+
InAppOwnershipType string `json:"inAppOwnershipType"`
237+
SignedDate int `json:"signedDate"`
238+
Environment string `json:"environment"`
239+
}
240+
241+
// Notification Renewal Info
242+
type RenewalInfo struct {
243+
jwt.RegisteredClaims
244+
OriginalTransactionID string `json:"originalTransactionId"`
245+
ExpirationIntent int `json:"expirationIntent"`
246+
AutoRenewProductId string `json:"autoRenewProductId"`
247+
ProductID string `json:"productId"`
248+
AutoRenewStatus int `json:"autoRenewStatus"`
249+
IsInBillingRetryPeriod bool `json:"isInBillingRetryPeriod"`
250+
SignedDate int `json:"signedDate"`
251+
Environment string `json:"environment"`
252+
}

store.go

+6
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,12 @@ func (c *StoreClient) GetTestNotificationStatus(ctx context.Context, testNotific
304304
return c.Do(ctx, http.MethodGet, URL, nil)
305305
}
306306

307+
func (c *StoreClient) ParseNotificationV2(tokenStr string) (*jwt.Token, error) {
308+
return jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
309+
return c.cert.extractPublicKeyFromToken(tokenStr)
310+
})
311+
}
312+
307313
// ParseSignedTransactions parse the jws singed transactions
308314
// Per doc: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6
309315
func (c *StoreClient) ParseSignedTransactions(transactions []string) ([]*JWSTransaction, error) {

0 commit comments

Comments
 (0)