Skip to content

Commit efe3f16

Browse files
authored
Merge pull request #172 from richzw/master
feat(appstore): upgrade refund history api to v2 and add send consumption information api
2 parents 32ff8d7 + 7ce9d3c commit efe3f16

File tree

4 files changed

+92
-15
lines changed

4 files changed

+92
-15
lines changed

README.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,30 @@ import(
104104
"github.com/awa/go-iap/appstore/api"
105105
)
106106

107+
// For generate key file and download it, please refer to https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api
108+
const ACCOUNTPRIVATEKEY = `
109+
-----BEGIN PRIVATE KEY-----
110+
FAKEACCOUNTKEYBASE64FORMAT
111+
-----END PRIVATE KEY-----
112+
`
107113
func main() {
108-
// For generate key file and download it, please refer to https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api
109-
token := &api.Token{
110-
KeyContent : "key content", // Loads a .p8 certificate
111-
KeyID : "2X9R4HXF34", // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
112-
BundleID : "com.example.testbundleid2021", // Your app’s bundle ID
113-
Issuer : "57246542-96fe-1a63-e053-0824d011072a", // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
114-
Sandbox : true, // default is Production
114+
c := &api.StoreConfig{
115+
KeyContent: []byte(ACCOUNTPRIVATEKEY), // Loads a .p8 certificate
116+
KeyID: "FAKEKEYID", // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
117+
BundleID: "fake.bundle.id", // Your app’s bundle ID
118+
Issuer: "xxxxx-xx-xx-xx-xxxxxxxxxx",// Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
119+
Sandbox: false, // default is Production
120+
}
121+
originalTransactionId := "FAKETRANSACTIONID"
122+
a := api.NewStoreClient(c)
123+
query := &url.Values{}
124+
query.Set("productType", "AUTO_RENEWABLE")
125+
query.Set("productType", "NON_CONSUMABLE")
126+
responses, err := a.GetTransactionHistory(originalTransactionId, query)
127+
128+
for _, response := range responses {
129+
transantions, err := a.ParseSignedTransactions(response.SignedTransactions)
115130
}
116-
client := api.NewStoreClient(token)
117-
resp, err := client.GetTransactionHistory("transactionID")
118131
}
119132
```
120133

appstore/api/model.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ type LastTransactionsItem struct {
4343
SignedTransactionInfo string `json:"signedTransactionInfo"`
4444
}
4545

46+
// ConsumptionRequestBody https://developer.apple.com/documentation/appstoreserverapi/consumptionrequest
47+
type ConsumptionRequestBody struct {
48+
AccountTenure int `json:"accountTenure"`
49+
AppAccountToken string `json:"appAccountToken"`
50+
ConsumptionStatus int `json:"consumptionStatus"`
51+
CustomerConsented bool `json:"customerConsented"`
52+
DeliveryStatus int `json:"deliveryStatus"`
53+
LifetimeDollarsPurchased int `json:"lifetimeDollarsPurchased"`
54+
LifetimeDollarsRefunded int `json:"lifetimeDollarsRefunded"`
55+
Platform int `json:"platform"`
56+
PlayTime int `json:"playTime"`
57+
SampleContentProvided bool `json:"sampleContentProvided"`
58+
UserStatus int `json:"userStatus"`
59+
}
60+
4661
type JWSRenewalInfoDecodedPayload struct {
4762
}
4863

@@ -71,6 +86,9 @@ type JWSTransaction struct {
7186
SignedDate int64 `json:"signedDate,omitempty"`
7287
OfferType int64 `json:"offerType,omitempty"`
7388
OfferIdentifier string `json:"offerIdentifier,omitempty"`
89+
RevocationDate int64 `json:"revocationDate,omitempty"`
90+
RevocationReason int `json:"revocationReason,omitempty"`
91+
IsUpgraded bool `json:"isUpgraded,omitempty"`
7492
}
7593

7694
func (J JWSTransaction) Valid() error {

appstore/api/store.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package api
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
67
"github.com/golang-jwt/jwt/v4"
@@ -19,15 +20,27 @@ const (
1920
PathTransactionHistory = "/inApps/v1/history/{originalTransactionId}"
2021
PathRefundHistory = "/inApps/v2/refund/lookup/{originalTransactionId}"
2122
PathGetALLSubscriptionStatus = "/inApps/v1/subscriptions/{originalTransactionId}"
23+
PathConsumptionInfo = "/inApps/v1/transactions/consumption/{originalTransactionId}"
2224
)
2325

26+
type StoreConfig struct {
27+
KeyContent []byte // Loads a .p8 certificate
28+
KeyID string // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
29+
BundleID string // Your app’s bundle ID
30+
Issuer string // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
31+
Sandbox bool // default is Production
32+
}
33+
2434
type StoreClient struct {
2535
Token *Token
2636
cert *Cert
2737
}
2838

2939
// NewStoreClient create appstore server api client
30-
func NewStoreClient(token *Token) *StoreClient {
40+
func NewStoreClient(config *StoreConfig) *StoreClient {
41+
token := &Token{}
42+
token.WithConfig(config)
43+
3144
client := &StoreClient{
3245
Token: token,
3346
cert: &Cert{},
@@ -41,7 +54,7 @@ func (a *StoreClient) GetALLSubscriptionStatuses(originalTransactionId string) (
4154
if a.Token.Sandbox {
4255
URL = HostSandBox + PathGetALLSubscriptionStatus
4356
}
44-
URL = strings.Replace(URL, "{orderId}", originalTransactionId, -1)
57+
URL = strings.Replace(URL, "{originalTransactionId}", originalTransactionId, -1)
4558
statusCode, body, err := a.Do(http.MethodGet, URL, nil)
4659
if err != nil {
4760
return
@@ -86,7 +99,7 @@ func (a *StoreClient) LookupOrderID(invoiceOrderId string) (rsp *OrderLookupResp
8699
}
87100

88101
// GetTransactionHistory https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history
89-
func (a *StoreClient) GetTransactionHistory(originalTransactionId string) (responses []*HistoryResponse, err error) {
102+
func (a *StoreClient) GetTransactionHistory(originalTransactionId string, query *url.Values) (responses []*HistoryResponse, err error) {
90103
URL := HostProduction + PathTransactionHistory
91104
if a.Token.Sandbox {
92105
URL = HostSandBox + PathTransactionHistory
@@ -95,12 +108,14 @@ func (a *StoreClient) GetTransactionHistory(originalTransactionId string) (respo
95108
rsp := HistoryResponse{}
96109

97110
for {
98-
data := url.Values{}
111+
if query == nil {
112+
query = &url.Values{}
113+
}
99114
if rsp.HasMore && rsp.Revision != "" {
100-
data.Set("revision", rsp.Revision)
115+
query.Set("revision", rsp.Revision)
101116
}
102117

103-
statusCode, body, errOmit := a.Do(http.MethodGet, URL+"?"+data.Encode(), nil)
118+
statusCode, body, errOmit := a.Do(http.MethodGet, URL+"?"+query.Encode(), nil)
104119
if errOmit != nil {
105120
return nil, errOmit
106121
}
@@ -164,6 +179,29 @@ func (a *StoreClient) GetRefundHistory(originalTransactionId string) (responses
164179
return
165180
}
166181

182+
// SendConsumptionInfo https://developer.apple.com/documentation/appstoreserverapi/send_consumption_information
183+
func (a *StoreClient) SendConsumptionInfo(originalTransactionId string, body ConsumptionRequestBody) (statusCode int, err error) {
184+
URL := HostProduction + PathConsumptionInfo
185+
if a.Token.Sandbox {
186+
URL = HostSandBox + PathConsumptionInfo
187+
}
188+
URL = strings.Replace(URL, "{originalTransactionId}", originalTransactionId, -1)
189+
190+
bodyBuf := new(bytes.Buffer)
191+
err = json.NewEncoder(bodyBuf).Encode(body)
192+
if err != nil {
193+
return 0, err
194+
}
195+
196+
statusCode, _, err = a.Do(http.MethodPut, URL, bodyBuf)
197+
if err != nil {
198+
return statusCode, err
199+
}
200+
return statusCode, nil
201+
}
202+
203+
// ParseSignedTransactions parse the jws singed transactions
204+
// Per doc: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6
167205
func (a *StoreClient) ParseSignedTransactions(transactions []string) ([]*JWSTransaction, error) {
168206
result := make([]*JWSTransaction, 0)
169207
for _, v := range transactions {

appstore/api/token.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ type Token struct {
3636
Bearer string // Authorized bearer token
3737
}
3838

39+
func (t *Token) WithConfig(c *StoreConfig) {
40+
t.KeyContent = append(t.KeyContent[:0:0], c.KeyContent...)
41+
t.KeyID = c.KeyID
42+
t.BundleID = c.BundleID
43+
t.Issuer = c.Issuer
44+
t.Sandbox = c.Sandbox
45+
}
46+
3947
// GenerateIfExpired checks to see if the token is about to expire and generates a new token.
4048
func (t *Token) GenerateIfExpired() (string, error) {
4149
t.Lock()

0 commit comments

Comments
 (0)