forked from Pallinder/go-iap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
goiap.go
166 lines (135 loc) · 4.89 KB
/
goiap.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Package goiap implements the ability to easily validate a receipt with apples verifyReceipt service
package goiap
import (
"bytes"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
)
// Receipt is information returned by Apple
//
// Documentation: https://developer.apple.com/library/ios/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html#//apple_ref/doc/uid/TP40010573-CH106-SW10
type Receipt struct {
BundleId string `json:"bundle_id"`
ApplicationVersion string `json:"application_version"`
InApp []PurchaseReceipt `json:"in_app"`
OriginalApplicationVersion string `json:"original_application_version"`
}
type PurchaseReceipt struct {
Quantity string `json:"quantity"`
ProductId string `json:"product_id"`
TransactionId string `json:"transaction_id"`
OriginalTransactionId string `json:"original_transaction_id"`
PurchaseDate string `json:"purchase_date"`
OriginalPurchaseDate string `json:"original_purchase_date"`
ExpiresDate string `json:"expires_date"`
AppItemId string `json:"app_item_id"`
VersionExternalIdentifier string `json:"version_external_identifier"`
WebOrderLineItemId string `json:"web_order_line_item_id"`
}
type receiptRequestData struct {
Receiptdata string `json:"receipt-data"`
}
const (
appleSandboxURL string = "https://sandbox.itunes.apple.com/verifyReceipt"
appleProductionURL string = "https://buy.itunes.apple.com/verifyReceipt"
)
// Simple interface to get the original error code from the error object
type ErrorWithCode interface {
Code() float64
}
type Error struct {
error
errCode float64
}
// Simple method to get the original error code from the error object
func (e *Error) Code() float64 {
return e.errCode
}
// Given receiptData (base64 encoded) it tries to connect to either the sandbox (useSandbox true) or
// apples ordinary service (useSandbox false) to validate the receipt. Returns either a receipt struct or an error.
func VerifyReceipt(receiptData string, useSandbox bool) (*Receipt, error) {
receipt, err := sendReceiptToApple(receiptData, verificationURL(useSandbox))
return receipt, err
}
// Selects the proper url to use when talking to apple based on if we should use the sandbox environment or not
func verificationURL(useSandbox bool) string {
if useSandbox {
return appleSandboxURL
}
return appleProductionURL
}
// Sends the receipt to apple, returns the receipt or an error upon completion
func sendReceiptToApple(receiptData, url string) (*Receipt, error) {
requestData, err := json.Marshal(receiptRequestData{receiptData})
if err != nil {
return nil, err
}
toSend := bytes.NewBuffer(requestData)
resp, err := http.Post(url, "application/json", toSend)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
var responseData struct {
Status float64 `json:"status"`
ReceiptContent *Receipt `json:"receipt"`
}
responseData.ReceiptContent = new(Receipt)
err = json.Unmarshal(body, &responseData)
if err != nil {
return nil, err
}
if responseData.Status != 0 {
return nil, verificationError(responseData.Status)
}
return responseData.ReceiptContent, nil
}
// Error codes as they returned by the App Store
const (
UnreadableJSON = 21000
MalformedData = 21002
AuthenticationError = 21003
UnmatchedSecret = 21004
ServerUnavailable = 21005
SubscriptionExpired = 21006
SandboxReceiptOnProd = 21007
ProdReceiptOnSandbox = 21008
)
// Generates the correct error based on a status error code
func verificationError(errCode float64) error {
var errorMessage string
switch errCode {
case UnreadableJSON:
errorMessage = "The App Store could not read the JSON object you provided."
break
case MalformedData:
errorMessage = "The data in the receipt-data property was malformed."
break
case AuthenticationError:
errorMessage = "The receipt could not be authenticated."
break
case UnmatchedSecret:
errorMessage = "The shared secret you provided does not match the shared secret on file for your account."
break
case ServerUnavailable:
errorMessage = "The receipt server is not currently available."
break
case SubscriptionExpired:
errorMessage = "This receipt is valid but the subscription has expired. When this status code is returned to your server, " +
"the receipt data is also decoded and returned as part of the response."
break
case SandboxReceiptOnProd:
errorMessage = "This receipt is a sandbox receipt, but it was sent to the production service for verification."
break
case ProdReceiptOnSandbox:
errorMessage = "This receipt is a production receipt, but it was sent to the sandbox service for verification."
break
default:
errorMessage = "An unknown error ocurred"
break
}
return &Error{errors.New(errorMessage), errCode}
}