Skip to content

Commit 68724ec

Browse files
authored
Feat: receipt verification (#5)
* Feat: receipt verification * Add info about code * Fix typos * Chnage method name
1 parent 1b40eeb commit 68724ec

File tree

14 files changed

+708
-26
lines changed

14 files changed

+708
-26
lines changed

.github/workflows/test.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ jobs:
1414

1515
- name: Run tests
1616
run: |
17-
cd auth && go test .
17+
cd auth && go test . && cd ..
18+
cd receipt && go test . && cd ..

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ To use any apple service
1111
import github.com/canopas/apple-sdk-go/<service-name>
1212
```
1313

14-
## SignIn
14+
## Services
1515

16-
Find reference of signIn flow in [auth doc](https://github.com/canopas/apple-sdk-go/blob/main/auth/README.md).
16+
- [SignIn](https://github.com/canopas/apple-sdk-go/blob/main/auth/README.md)
17+
- [Appstore receipt verification](https://github.com/canopas/apple-sdk-go/blob/main/receipt/README.md)
1718

18-
For more information about apple sign in, please review [apple doc](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api).
1919

auth/README.md

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Go client library for signing in user
1+
# Go client library for signing in with apple
22

3-
Go client library for signing in user.
3+
For more information about apple sign in, please review [apple doc](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api).
44

55
## Install
66

@@ -21,8 +21,20 @@ go get github.com/canopas/apple-sdk-go/auth
2121

2222
```go
2323

24-
// create auth client
25-
req, err := auth.NewClient("team-id", "client-id", "key-id", "secret-key-file-path")
24+
// Create new secret request with default client
25+
req, err := auth.WithDefaultClient("team-id", "client-id", "key-id", "secret-key-file-path")
26+
27+
if err != nil {
28+
log.Fatal(err.Error())
29+
}
30+
31+
// OR
32+
// Create new secret request with custom client
33+
client := &http.Client{
34+
Timeout: 10 * time.Second,
35+
}
36+
37+
req, err := auth.WithCustomClient(client, "team-id", "client-id", "key-id", "secret-key-file-path")
2638

2739
if err != nil {
2840
log.Fatal(err.Error())

auth/client.go

+24-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
package auth
22

33
import (
4+
"errors"
45
"io/ioutil"
56
"net/http"
7+
"time"
68
)
79

8-
// Returns new secret request
9-
func NewClient(teamId, clientId, keyId, secretKeyPath string) (*Request, error) {
10+
var InvalidSecretFileMsg = "please specify secret key file path"
11+
12+
// Returns new secret request with default client
13+
func WithDefaultClient(teamId, clientId, keyId, secretKeyPath string) (*Request, error) {
14+
client := &http.Client{
15+
Timeout: 10 * time.Second,
16+
}
17+
return createRequest(teamId, clientId, keyId, secretKeyPath, client)
18+
}
19+
20+
// Returns new secret request with given client
21+
func WithCustomClient(client httpClient, teamId, clientId, keyId, secretKeyPath string) (*Request, error) {
22+
return createRequest(teamId, clientId, keyId, secretKeyPath, client)
23+
}
24+
25+
func createRequest(teamId, clientId, keyId, secretKeyPath string, client httpClient) (*Request, error) {
26+
27+
if secretKeyPath == "" {
28+
return nil, errors.New(InvalidSecretFileMsg)
29+
}
30+
1031
secretContent, err := ioutil.ReadFile(secretKeyPath)
1132

1233
if err != nil {
@@ -18,6 +39,6 @@ func NewClient(teamId, clientId, keyId, secretKeyPath string) (*Request, error)
1839
ClientID: clientId,
1940
KeyID: keyId,
2041
ClientSecret: secretContent,
21-
HttpClient: &http.Client{},
42+
HttpClient: client,
2243
}, nil
2344
}

auth/client_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package auth
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestCreateRequest(t *testing.T) {
12+
gotResp, gotErr := createRequest("1234567890", "com.example.app", "abc123def4", "", &http.Client{})
13+
expectedErr := errors.New(InvalidSecretFileMsg)
14+
15+
assert.Equal(t, expectedErr, gotErr)
16+
assert.NotEqual(t, nil, gotResp)
17+
}

go.mod

+1-6
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,4 @@ go 1.18
44

55
replace auth => ./auth
66

7-
require auth v0.0.0-00010101000000-000000000000
8-
9-
require (
10-
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
11-
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
12-
)
7+
replace receipt => ./receipt

go.sum

-9
Original file line numberDiff line numberDiff line change
@@ -1,9 +0,0 @@
1-
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2-
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
3-
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
4-
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
5-
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
6-
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7-
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
8-
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
9-
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

receipt/README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Go client library for verifying receipt-data from appstore
2+
3+
For more information about appstore receipts, please review [apple doc](https://developer.apple.com/documentation/appstorereceipts).
4+
5+
## Install
6+
7+
```bash
8+
go get github.com/canopas/apple-sdk-go/receipt
9+
```
10+
11+
## How to use?
12+
13+
- **ReceiptData** : base64 encoded receipt data
14+
15+
- **Password** : Appstore shared secret (Ex: 8h1b362b673b442d8ali4b2141912948)
16+
17+
```go
18+
19+
// Create new IAP request with default client
20+
client := receipt.WithDefaultClient()
21+
22+
// OR
23+
// Create new IAP request with custom client
24+
httpCli := &http.Client{
25+
Timeout: 10 * time.Second,
26+
}
27+
28+
client := receipt.WithCustomClient(httpCli)
29+
30+
// verify receipt data
31+
response, err := client.Verify(context.Background(), receipt.IAPRequest{
32+
ReceiptData: "your-receipt-data",
33+
Password: "shared-secret",
34+
})
35+
36+
if err != nil {
37+
log.Fatal(err.Error())
38+
}
39+
40+
// handle errors if any
41+
if response.Status != 0 {
42+
err = receipt.HandleErrors(response.Status)
43+
log.Fatal(err.Error())
44+
}
45+
46+
log.Println(response)
47+
48+
```

receipt/errors.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// errors contains all possible errors info when proccessing with app store receipt
2+
package receipt
3+
4+
import "errors"
5+
6+
// list of errors
7+
var (
8+
ErrAppStoreServer = errors.New("appStore server error")
9+
ErrInvalidJSON = errors.New("the App Store could not read the JSON object you provided")
10+
ErrInvalidReceiptData = errors.New("the data in the receipt-data property was malformed or missing")
11+
ErrReceiptUnauthenticated = errors.New("the receipt could not be authenticated")
12+
ErrInvalidSharedSecret = errors.New("the shared secret you provided does not match the shared secret on file for your account")
13+
ErrServerUnavailable = errors.New("the receipt server is not currently available")
14+
ErrReceiptIsForTest = errors.New("this receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead")
15+
ErrReceiptIsForProduction = errors.New("this receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead")
16+
ErrReceiptUnauthorized = errors.New("this receipt could not be authorized. Treat this the same as if a purchase was never made")
17+
ErrSubscriptionExpired = errors.New("subscription has expired")
18+
ErrDuplicateReceipt = errors.New("duplicate receipt")
19+
ErrInternalDataAccessError = errors.New("internal data access error")
20+
ErrUnknown = errors.New("an unknown error occurred")
21+
)
22+
23+
// Returns error message by status code
24+
// Use this method after verifying receipt, to check errors
25+
func HandleErrors(status int) error {
26+
var e error
27+
switch status {
28+
case 0:
29+
return nil
30+
case 21000:
31+
e = ErrInvalidJSON
32+
case 21002:
33+
e = ErrInvalidReceiptData
34+
case 21003:
35+
e = ErrReceiptUnauthenticated
36+
case 21004:
37+
e = ErrInvalidSharedSecret
38+
case 21005:
39+
e = ErrServerUnavailable
40+
case 21006:
41+
e = ErrSubscriptionExpired
42+
case 21007:
43+
e = ErrReceiptIsForTest
44+
case 21008:
45+
e = ErrReceiptIsForProduction
46+
case 21009:
47+
e = ErrInternalDataAccessError
48+
case 21010:
49+
e = ErrReceiptUnauthorized
50+
default:
51+
if status >= 21100 && status <= 21199 {
52+
e = ErrInternalDataAccessError
53+
} else {
54+
e = ErrUnknown
55+
}
56+
}
57+
58+
return e
59+
}

receipt/go.mod

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module github.com/canopas/apple-sdk-go/receipt
2+
3+
go 1.18
4+
5+
require (
6+
github.com/stretchr/testify v1.8.1
7+
github.com/tj/assert v0.0.3
8+
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/pmezard/go-difflib v1.0.0 // indirect
13+
github.com/stretchr/objx v0.5.0 // indirect
14+
gopkg.in/yaml.v3 v3.0.1 // indirect
15+
)

receipt/go.sum

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
5+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
7+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
8+
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
9+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
10+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
11+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
12+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
13+
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
14+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
15+
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
16+
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
18+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
19+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
20+
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
21+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
22+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)