forked from Mirrors/go-iap
According to https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPTURL Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive a 21007 status code. Following this approach ensures that you do not have to switch between URLs while your application is being tested or reviewed in the sandbox or is live in the App Store.
149 lines
3.5 KiB
Go
149 lines
3.5 KiB
Go
package appstore
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// SandboxURL is the endpoint for sandbox environment.
|
|
SandboxURL string = "https://sandbox.itunes.apple.com/verifyReceipt"
|
|
// ProductionURL is the endpoint for production environment.
|
|
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, interface{}) error
|
|
}
|
|
|
|
// Client implements IAPClient
|
|
type Client struct {
|
|
URL string
|
|
TimeOut time.Duration
|
|
SandboxURL string
|
|
}
|
|
|
|
// 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 21004:
|
|
message = "The shared secret you provided does not match the shared secret on file for your account."
|
|
|
|
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."
|
|
|
|
case 21010:
|
|
message = "This receipt could not be authorized. Treat this the same as if a purchase was never made."
|
|
|
|
default:
|
|
if status >= 21100 && status <= 21199 {
|
|
message = "Internal data access error."
|
|
} else {
|
|
message = "An unknown error occurred"
|
|
}
|
|
}
|
|
|
|
return errors.New(message)
|
|
}
|
|
|
|
// New creates a client object
|
|
func New() Client {
|
|
client := Client{
|
|
URL: SandboxURL,
|
|
TimeOut: time.Second * 5,
|
|
SandboxURL: SandboxURL,
|
|
}
|
|
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, result interface{}) error {
|
|
client := http.Client{
|
|
Timeout: c.TimeOut,
|
|
}
|
|
|
|
b := new(bytes.Buffer)
|
|
json.NewEncoder(b).Encode(req)
|
|
|
|
resp, err := client.Post(c.URL, "application/json; charset=utf-8", b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(result)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Always verify your receipt first with the production URL; proceed to verify with the sandbox URL if you receive
|
|
// a 21007 status code
|
|
//
|
|
// https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-RECEIPTURL
|
|
r, ok := result.(*IAPResponse)
|
|
if ok && r.Status == 21007 {
|
|
b = new(bytes.Buffer)
|
|
json.NewEncoder(b).Encode(req)
|
|
resp, err := client.Post(c.SandboxURL, "application/json; charset=utf-8", b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
return json.NewDecoder(resp.Body).Decode(result)
|
|
}
|
|
|
|
return nil
|
|
}
|