package iap import ( "bytes" "encoding/json" "errors" "io/ioutil" "net/http" "strings" "sync" "time" "repositories.action2quare.com/ayo/gocommon/logger" ) // Ref https://onestore-dev.gitbook.io/dev/tools/tools/v21/06.-api-api-v7#accesstoken const shouldGenerateAccessTokenSec = 600 type OneStoreIAPVerificationInfo struct { ConsumptionState int `json:"consumptionState"` DeveloperPayload string `json:"developerPayload"` PurchaseState int `json:"purchaseState"` PurchaseTime int64 `json:"purchaseTime"` PurchaseId string `json:"purchaseId"` AcknowledgeState int `json:"acknowledgeState"` Quantity int `json:"quantity"` } type OneStoreIAPClientPurcharinfo struct { OrderId string `json:"orderId"` PackageName string `json:"packageName"` ProductId string `json:"productId"` PurchaseTime int64 `json:"purchaseTime"` PurchaseId string `json:"purchaseId"` PurchaseToken string `json:"purchaseToken"` DeveloperPayload string `json:"developerPayload"` Quantity int `json:"quantity"` } type OneStoreConfig struct { OneClientID string OneClientSecret string } type OneStoreTokenData struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type,omitempty"` ClientId string `json:"client_id,omitempty"` ExpiresIn int `json:"expires_in,omitempty"` Scope string `json:"scope,omitempty"` Expiry time.Time } func (s *OnestoreAccessToken) HostURL() string { if s.isSandBox { //-- Sandbox return "https://sbpp.onestore.co.kr" } else { //-- RealURL return "https://iap-apis.onestore.net" } } func (s *OnestoreAccessToken) GenerateNewToken() error { var newToken OneStoreTokenData //== HostURL := s.HostURL() data := []byte("grant_type=client_credentials&client_id=" + s.config.OneClientID + "&client_secret=" + s.config.OneClientSecret) client := &http.Client{} req, err := http.NewRequest("POST", HostURL+"/v7/oauth/token", bytes.NewBuffer(data)) if err != nil { return err } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("x-market-code", s.marketCode) resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return err } json.Unmarshal(body, &newToken) newExpiredSec := (newToken.ExpiresIn - shouldGenerateAccessTokenSec) if newExpiredSec < 0 { newExpiredSec = 0 } newToken.Expiry = time.Now().Round(0).Add(time.Duration(newExpiredSec) * time.Second) s.data = &newToken logger.Println("IAP - OneStore : Generate Access Token :", s.data) return nil } type OnestoreAccessToken struct { mu sync.Mutex // guards t data *OneStoreTokenData marketCode string isSandBox bool config *OneStoreConfig } func (t *OnestoreAccessToken) Valid() bool { return t != nil && t.data != nil && t.data.AccessToken != "" && !t.expired() } func (t *OnestoreAccessToken) expired() bool { return t.data.Expiry.Before(time.Now()) } func (s *OnestoreAccessToken) Token() (string, error) { s.mu.Lock() defer s.mu.Unlock() if s.Valid() { return s.data.AccessToken, nil } //== Expire 되면 새로 얻어 오자.. err := s.GenerateNewToken() if err != nil { return "", err } return s.data.AccessToken, nil } type OnestoreIAPVerifier struct { config OneStoreConfig SandBox_OnestoreAccessToken_MKT_ONE OnestoreAccessToken SandBox_OnestoreAccessToken_MKT_GLB OnestoreAccessToken Live_OnestoreAccessToken_MKT_ONE OnestoreAccessToken Live_OnestoreAccessToken_MKT_GLB OnestoreAccessToken } func (s *OnestoreIAPVerifier) Init(clientId string, clientSecret string) { s.config.OneClientID = clientId s.config.OneClientSecret = clientSecret s.SandBox_OnestoreAccessToken_MKT_ONE.marketCode = "MKT_ONE" s.SandBox_OnestoreAccessToken_MKT_GLB.marketCode = "MKT_GLB" s.Live_OnestoreAccessToken_MKT_ONE.marketCode = "MKT_ONE" s.Live_OnestoreAccessToken_MKT_GLB.marketCode = "MKT_GLB" s.SandBox_OnestoreAccessToken_MKT_ONE.isSandBox = true s.SandBox_OnestoreAccessToken_MKT_GLB.isSandBox = true s.Live_OnestoreAccessToken_MKT_ONE.isSandBox = false s.Live_OnestoreAccessToken_MKT_GLB.isSandBox = false s.SandBox_OnestoreAccessToken_MKT_ONE.config = &s.config s.SandBox_OnestoreAccessToken_MKT_GLB.config = &s.config s.Live_OnestoreAccessToken_MKT_ONE.config = &s.config s.Live_OnestoreAccessToken_MKT_GLB.config = &s.config } func (s *OnestoreIAPVerifier) Verify(marketCode string, isSandBox bool, packageName string, clientInfo OneStoreIAPClientPurcharinfo) (bool, error) { var accesstoken string if marketCode != "MKT_ONE" && marketCode != "MKT_GLB" { return false, errors.New("marketCode is wrong") } if clientInfo.ProductId == "" { return false, errors.New("productId is empty") } if clientInfo.PurchaseToken == "" { return false, errors.New("purchaseToken is empty") } if packageName != clientInfo.PackageName { return false, errors.New("packageName is wrong") } var err error var HostURL string if marketCode == "MKT_ONE" { if isSandBox { accesstoken, err = s.SandBox_OnestoreAccessToken_MKT_ONE.Token() HostURL = s.SandBox_OnestoreAccessToken_MKT_ONE.HostURL() } else { accesstoken, err = s.Live_OnestoreAccessToken_MKT_ONE.Token() HostURL = s.Live_OnestoreAccessToken_MKT_ONE.HostURL() } } else { if isSandBox { accesstoken, err = s.SandBox_OnestoreAccessToken_MKT_GLB.Token() HostURL = s.SandBox_OnestoreAccessToken_MKT_GLB.HostURL() } else { accesstoken, err = s.Live_OnestoreAccessToken_MKT_GLB.Token() HostURL = s.Live_OnestoreAccessToken_MKT_GLB.HostURL() } } if err != nil { return false, err } client := &http.Client{} req, err := http.NewRequest("GET", HostURL+"/v7/apps/"+s.config.OneClientID+"/purchases/inapp/products/"+clientInfo.ProductId+"/"+clientInfo.PurchaseToken, nil) if err != nil { return false, err } req.Header.Add("Authorization", "Bearer "+accesstoken) req.Header.Add("Content-Type", "application/json") req.Header.Add("x-market-code", marketCode) resp, err := client.Do(req) if err != nil { return false, err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return false, err } var verificatioinfo OneStoreIAPVerificationInfo json.Unmarshal(body, &verificatioinfo) // fmt.Println("================") // fmt.Println(string(body)) // fmt.Println("================") if verificatioinfo.PurchaseState != 0 { return false, errors.New("canceled payment") } if !strings.EqualFold(clientInfo.DeveloperPayload, verificatioinfo.DeveloperPayload) { return false, errors.New("developerPayload is wrong :" + clientInfo.DeveloperPayload + "/" + verificatioinfo.DeveloperPayload) } if clientInfo.PurchaseId != verificatioinfo.PurchaseId { return false, errors.New("purchaseId is wrong") } if clientInfo.PurchaseTime != verificatioinfo.PurchaseTime { return false, errors.New("purchaseTime is wrong") } if clientInfo.Quantity != verificatioinfo.Quantity { return false, errors.New("quantity is wrong") } return true, nil }