2024-04-25 11:37:07 +09:00
|
|
|
package iap
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
2024-06-03 09:35:55 +09:00
|
|
|
|
|
|
|
|
"repositories.action2quare.com/ayo/gocommon/logger"
|
2024-04-25 11:37:07 +09:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|