tkcashgame_v4/pkg/appstore/client.go

227 lines
6.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package appstore
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"sandc/pkg/bhttp"
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
)
type options struct {
token string
}
type Option func(*options)
func WithToken(b string) Option {
return func(c *options) {
c.token = b
}
}
type Client struct {
privateKeyByte []byte
issuer string
keyID string
token string
env string
}
// NewClient creates a new App Store Connect API client.
func NewClient(issuer, keyID, env string, privateKeyByte []byte, opts ...Option) (*Client, error) {
opt := options{
token: "",
}
for _, o := range opts {
o(&opt)
}
return &Client{
privateKeyByte: privateKeyByte,
issuer: issuer,
keyID: keyID,
env: env,
token: opt.token,
}, nil
}
// IsProdEnv returns true if the client is configured to use the production environment.
func (c *Client) IsProdEnv() bool {
return c.env == "prod"
}
// loadPrivateKey loads a private key from a file.
func (c *Client) loadPrivateKey(bytes []byte) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode(bytes)
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block containing private key")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
ecdsaKey, ok := key.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("not an ECDSA private key")
}
return ecdsaKey, nil
}
// GenerateToken generates a JWT token for the App Store Connect API.
func (c *Client) GenerateToken(pkg string, expire time.Duration) (string, error) {
privateKey, err := c.loadPrivateKey(c.privateKeyByte)
if err != nil {
return "", err
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
"iss": c.issuer,
"iat": time.Now().Unix(),
"exp": time.Now().Add(expire).Unix(),
"aud": "appstoreconnect-v1",
"bid": pkg,
})
token.Header["kid"] = c.keyID
signedToken, err := token.SignedString(privateKey)
if err != nil {
return "", err
}
c.token = signedToken
return signedToken, nil
}
// map[bundleId:com.hotpotgames.mergegangster.global environment:Sandbox inAppOwnershipType:PURCHASED originalPurchaseDate:1.689239628e+12 originalTransactionId:2000000368088682 productId:mergegangster_noads purchaseDate:1.689239628e+12 quantity:1 signedDate:1.689594496689e+12 storefront:HKG storefrontId:143463 transactionId:2000000368088682 transactionReason:PURCHASE type:Non-Consumable]
// TransactionInfo represents the transaction info for a given transaction id
type TransactionInfo struct {
BundleId string `json:"bundleId"`
Environment string `json:"environment"`
InAppOwnershipType string `json:"inAppOwnershipType"`
OriginalPurchaseDate int64 `json:"originalPurchaseDate"`
OriginalTransactionId string `json:"originalTransactionId"`
ProductId string `json:"productId"`
PurchaseDate int64 `json:"purchaseDate"`
Quantity int32 `json:"quantity"`
SignedDate int64 `json:"signedDate"`
Storefront string `json:"storefront"`
StorefrontId string `json:"storefrontId"`
TransactionId string `json:"transactionId"`
TransactionReason string `json:"transactionReason"`
Type string `json:"type"`
}
// TransactionInfoRes represents the transaction info response
type TransactionInfoRes struct {
SignedTransactionInfo string `json:"signedTransactionInfo,omitempty"`
ErrorCode int `json:"errorCode,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
}
// GetTransactionInfo returns the transaction info for a given transaction id
func (c *Client) GetTransactionInfo(transactionID, pkg string) (*TransactionInfo, error) {
var url string
if c.IsProdEnv() {
url = fmt.Sprintf("https://api.storekit.itunes.apple.com/inApps/v1/transactions/%s", transactionID)
} else {
url = fmt.Sprintf("https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/%s", transactionID)
}
bhttp, err := bhttp.NewBhttpClient()
if err != nil {
return nil, fmt.Errorf("bhttpClient init failed: %w", err)
}
if c.token == "" {
token, err := c.GenerateToken(pkg, 24*time.Hour)
if err != nil {
return nil, fmt.Errorf("generate token failed: %w", err)
}
c.token = token
fmt.Println("new")
}
fmt.Println("token: ", c.token)
bhttp.SetHeader("Authorization", fmt.Sprintf("Bearer %s", c.token))
if err != nil {
return nil, err
}
resJson, err := bhttp.DoGet(url)
if err != nil {
return nil, fmt.Errorf("getTransactionInfo http get error: %w", err)
}
var info TransactionInfoRes
err = json.Unmarshal(resJson, &info)
if err != nil {
return nil, fmt.Errorf("getTransactionInfo unmarshal err: %w, body: %s", err, string(resJson))
}
if info.ErrorMessage != "" {
return nil, fmt.Errorf("getTransactionInfo error: %s", info.ErrorMessage)
}
// signedTransactionInfo A customers in-app purchase transaction, signed by Apple, in JSON Web Signature (JWS) format.
signedPayload := info.SignedTransactionInfo
// decode signedTransactionInfo
segments := strings.Split(signedPayload, ".")
payloadBytes, err := base64.RawURLEncoding.DecodeString(segments[1])
if err != nil {
return nil, fmt.Errorf("error decoding payload: %w", err)
}
var payload TransactionInfo
err = json.Unmarshal(payloadBytes, &payload)
if err != nil {
return nil, fmt.Errorf("error unmarshaling payload: %w", err)
}
return &payload, nil
}
// GenerateToken generates a JWT token for the App Store Connect API.
func GenerateToken(issuer, keyID, pkg string, privateKeyByte []byte, expire time.Duration) (string, error) {
block, _ := pem.Decode(privateKeyByte)
if block == nil {
return "", fmt.Errorf("failed to decode PEM block containing private key")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", err
}
privateKey, ok := key.(*ecdsa.PrivateKey)
if !ok {
return "", fmt.Errorf("not an ECDSA private key")
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
"iss": issuer,
"iat": time.Now().Unix(),
"exp": time.Now().Add(expire).Unix(),
"aud": "appstoreconnect-v1",
"bid": pkg,
})
token.Header["kid"] = keyID
signedToken, err := token.SignedString(privateKey)
if err != nil {
return "", err
}
return signedToken, nil
}