Initial push

This commit is contained in:
tsuji_jumpei
2014-12-04 21:08:46 +09:00
parent 98092a4a43
commit 1f1601cd3c
6 changed files with 357 additions and 0 deletions

13
.travis.yml Normal file
View File

@@ -0,0 +1,13 @@
language: go
go:
- 1.3
before_install:
sudo pip install codecov
install:
- go get code.google.com/p/go.tools/cmd/cover
- go get ./...
script:
- make cover
after_success:
codecov

15
Makefile Normal file
View File

@@ -0,0 +1,15 @@
.PHONEY: all setup test cover
all: setup cover
setup:
go get code.google.com/p/go.tools/cmd/cover
go get ./...
test:
go test -v ./...
cover:
go test -v -coverprofile=coverage.txt -covermode=count ./appstore

View File

@@ -2,3 +2,40 @@ go-iap
====== ======
go-iap verifies the purchase receipt via AppStore or GooglePlayStore go-iap verifies the purchase receipt via AppStore or GooglePlayStore
# Installation
```
go get github.com/dogenzaka/go-iap
```
# Quick Start
### In App Purchase (via App Store)
```
import(
"github.com/dogenzaka/go-iap/appstore"
)
func main() {
client := appstore.New()
req := appstore.IAPRequest{
ReceiptData: "your receipt data encoded by base64",
}
resp, err := client.Verify(&req)
}
```
# ToDo
- [x] App Store Client
- [ ] Google Play Store Client
# Support
iOS7 or above
# License
Gorv is licensed under the MIT.

62
appstore/model.go Normal file
View File

@@ -0,0 +1,62 @@
package appstore
type (
// The IAPRequest type has the request parameter
IAPRequest struct {
ReceiptData string `json:"receipt-data"`
}
// The RequestDate type indicates the date and time that the request was sent
RequestDate struct {
RequestDate string `json:"request_date"`
RequestDateMS string `json:"request_date_ms"`
RequestDatePST string `json:"request_date_pst"`
}
// The PurchaseDate type indicates the date and time that the item was purchased
PurchaseDate struct {
PurchaseDate string `json:"purchase_date"`
PurchaseDateMS string `json:"purchase_date_ms"`
PurchaseDatePST string `json:"purchase_date_pst"`
}
// The OriginalPurchaseDate type indicates the beginning of the subscription period
OriginalPurchaseDate struct {
OriginalPurchaseDate string `json:"original_purchase_date"`
OriginalPurchaseDateMS string `json:"original_purchase_date_ms"`
OriginalPurchaseDatePST string `json:"original_purchase_date_pst"`
}
// The InApp type has the receipt attributes
InApp struct {
Quantity string `json:"quantity"`
ProductID string `json:"product_id"`
TransactionID string `json:"transaction_id"`
OriginalTransactionID string `json:"original_transaction_id"`
IsTrialPeriod string `json:"is_trial_period"`
ExpiresDate string `json:"expires_date"`
CancellationDate string `json:"cancellation_date"`
AppItemID string `json:"app_item_id"`
VersionExternalIdentifier string `json:"version_external_identifier"`
WebOrderLineItemID string `json:"web_order_line_item_id"`
PurchaseDate
OriginalPurchaseDate
}
// The Receipt type has whole data of receipt
Receipt struct {
ReceiptType string `json:"receipt_type"`
BundleID string `json:"bundle_id"`
ApplicationVersion string `json:"application_version"`
OriginalApplicationVersion string `json:"original_application_version"`
InApp []InApp `json:"in_app"`
RequestDate
}
// The IAPResponse type has the response properties
IAPResponse struct {
Status int `json:"status"`
Environment string `json:"environment"`
Receipt Receipt `json:"receipt"`
}
)

118
appstore/validator.go Normal file
View File

@@ -0,0 +1,118 @@
package appstore
import (
"encoding/json"
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/parnurzeal/gorequest"
)
const (
sandboxURL string = "https://sandbox.itunes.apple.com/verifyReceipt"
productionURL string = "https://buy.itunes.apple.com/verifyReceipt"
)
// Config is a configuration to initialize client
type Config struct {
IsProduction bool
TimeOut time.Duration
}
// IAPClient is an interface to call validation API in App Store
type IAPClient interface {
Verify(IAPRequest) (IAPResponse, error)
}
// Client implements IAPClient
type Client struct {
URL string
TimeOut time.Duration
}
// HandleError returns error message by status code
func HandleError(status int) error {
var message string
switch status {
case 0:
return nil
case 21000:
message = "The App Store could not read the JSON object you provided."
case 21002:
message = "The data in the receipt-data property was malformed or missing."
case 21003:
message = "The receipt could not be authenticated."
case 21005:
message = "The receipt server is not currently available."
case 21007:
message = "This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead."
case 21008:
message = "This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead."
default:
message = "An unknown error ocurred"
}
return errors.New(message)
}
// New creates a client object
func New() *Client {
client := &Client{
URL: sandboxURL,
TimeOut: time.Second * 5,
}
if os.Getenv("IAP_ENVIRONMENT") == "production" {
client.URL = productionURL
}
return client
}
// NewWithConfig creates a client with configuration
func NewWithConfig(config Config) *Client {
if config.TimeOut == 0 {
config.TimeOut = time.Second * 5
}
client := &Client{
URL: sandboxURL,
TimeOut: config.TimeOut,
}
if config.IsProduction {
client.URL = productionURL
}
return client
}
// Verify sends receipts and gets validation result
func (c *Client) Verify(req *IAPRequest) (IAPResponse, error) {
result := IAPResponse{}
res, body, errs := gorequest.New().
Post(c.URL).
Send(req).
Timeout(c.TimeOut).
End()
if errs != nil {
return result, fmt.Errorf("%v", errs)
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
return result, errors.New("An error occurred in IAP - code:" + strconv.Itoa(res.StatusCode))
}
err := json.NewDecoder(strings.NewReader(body)).Decode(&result)
return result, err
}

112
appstore/validator_test.go Normal file
View File

@@ -0,0 +1,112 @@
package appstore
import (
"errors"
"reflect"
"testing"
"time"
)
func TestHandleError(t *testing.T) {
var expected, actual error
// status 0
expected = nil
actual = HandleError(0)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
// status 21000
expected = errors.New("The App Store could not read the JSON object you provided.")
actual = HandleError(21000)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
// status 21002
expected = errors.New("The data in the receipt-data property was malformed or missing.")
actual = HandleError(21002)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
// status 21003
expected = errors.New("The receipt could not be authenticated.")
actual = HandleError(21003)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
// status 21005
expected = errors.New("The receipt server is not currently available.")
actual = HandleError(21005)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
// status 21007
expected = 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.")
actual = HandleError(21007)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
// status 21008
expected = 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.")
actual = HandleError(21008)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
// status unkown
expected = errors.New("An unknown error ocurred")
actual = HandleError(100)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
}
func TestNew(t *testing.T) {
expected := &Client{
URL: "https://sandbox.itunes.apple.com/verifyReceipt",
TimeOut: time.Second * 5,
}
actual := New()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
}
func TestNewWithConfig(t *testing.T) {
config := Config{
IsProduction: true,
TimeOut: time.Second * 2,
}
expected := &Client{
URL: "https://buy.itunes.apple.com/verifyReceipt",
TimeOut: time.Second * 2,
}
actual := NewWithConfig(config)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
}
func TestVerify(t *testing.T) {
client := New()
expected := IAPResponse{
Status: 21002,
}
req := IAPRequest{
ReceiptData: "dummy data",
}
actual, _ := client.Verify(&req)
if !reflect.DeepEqual(actual, expected) {
t.Errorf("got %v\nwant %v", actual, expected)
}
}