Skip to content

Commit

Permalink
Feat/apple client (#328)
Browse files Browse the repository at this point in the history
* apple update
  • Loading branch information
iGoogle-ink authored Jul 3, 2023
1 parent 71c4094 commit 0574937
Show file tree
Hide file tree
Showing 31 changed files with 755 additions and 233 deletions.
30 changes: 30 additions & 0 deletions apple/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package apple

import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
)

// ParseECPrivateKeyFromPEM parses a PEM encoded Elliptic Curve Private Key Structure
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, errors.New("ErrKeyMustBePEMEncoded")
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil {
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
return nil, err
}
}
pkey, ok := parsedKey.(*ecdsa.PrivateKey)
if !ok {
return nil, errors.New("ErrNotECPrivateKey")
}
return pkey, nil
}
101 changes: 101 additions & 0 deletions apple/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package apple

import (
"context"
"crypto/ecdsa"
"net/http"

"github.com/go-pay/gopay"
"github.com/go-pay/gopay/pkg/util"
"github.com/go-pay/gopay/pkg/xhttp"
)

// Client AppleClient
type Client struct {
iss string // Your issuer ID from the Keys page in App Store Connect (Ex: "57246542-96fe-1a63-e053-0824d011072a")
bid string // Your app’s bundle ID (Ex: “com.example.testbundleid2021”)
kid string // Your private key ID from App Store Connect (Ex: 2X9R4HXF34)
isProd bool // 是否是正式环境
privateKey *ecdsa.PrivateKey
}

// NewClient 初始化Apple客户端
// iss:issuer ID
// bid:bundle ID
// kid:private key ID
// privateKey:私钥文件读取后的字符串内容
// isProd:是否是正式环境
func NewClient(iss, bid, kid, privateKey string, isProd bool) (client *Client, err error) {
if iss == util.NULL || bid == util.NULL || kid == util.NULL || privateKey == util.NULL {
return nil, gopay.MissAppleInitParamErr
}
ecPrivateKey, err := ParseECPrivateKeyFromPEM([]byte(privateKey))
if err != nil {
return nil, err
}
//ecPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
//if err != nil {
// panic(err)
//}
client = &Client{
iss: iss,
bid: bid,
kid: kid,
privateKey: ecPrivateKey,
}
return client, nil
}

func (c *Client) doRequestGet(ctx context.Context, path string) (res *http.Response, bs []byte, err error) {
uri := hostUrl + path
if !c.isProd {
uri = sandBoxHostUrl + path
}
token, err := c.generatingToken()
if err != nil {
return nil, nil, err
}
cli := xhttp.NewClient()
cli.Header.Set("Authorization", "Bearer "+token)
res, bs, err = cli.Type(xhttp.TypeJSON).Get(uri).EndBytes(ctx)
if err != nil {
return nil, nil, err
}
return res, bs, nil
}

func (c *Client) doRequestPost(ctx context.Context, path string, bm gopay.BodyMap) (res *http.Response, bs []byte, err error) {
uri := hostUrl + path
if !c.isProd {
uri = sandBoxHostUrl + path
}
token, err := c.generatingToken()
if err != nil {
return nil, nil, err
}
cli := xhttp.NewClient()
cli.Header.Set("Authorization", "Bearer "+token)
res, bs, err = cli.Type(xhttp.TypeJSON).Post(uri).SendBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, nil, err
}
return res, bs, nil
}

func (c *Client) doRequestPut(ctx context.Context, path string, bm gopay.BodyMap) (res *http.Response, bs []byte, err error) {
uri := hostUrl + path
if !c.isProd {
uri = sandBoxHostUrl + path
}
token, err := c.generatingToken()
if err != nil {
return nil, nil, err
}
cli := xhttp.NewClient()
cli.Header.Set("Authorization", "Bearer "+token)
res, bs, err = cli.Type(xhttp.TypeJSON).Put(uri).SendBodyMap(bm).EndBytes(ctx)
if err != nil {
return nil, nil, err
}
return res, bs, nil
}
36 changes: 36 additions & 0 deletions apple/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package apple

import (
"context"
"os"
"testing"

"github.com/go-pay/gopay/pkg/xlog"
)

var (
ctx = context.Background()
client *Client
err error

iss = "57246542-96fe-1a63-e053-0824d011072a"
bid = "com.example.testbundleid2021"
kid = "2X9R4HXF34"
)

func TestMain(m *testing.M) {
xlog.Level = xlog.DebugLevel
// 初始化客户端
// iss:issuer ID
// bid:bundle ID
// kid:private key ID
// privateKey:私钥文件读取后的字符串内容
// isProd:是否是正式环境
client, err = NewClient(iss, bid, kid, "privateKey", false)
if err != nil {
xlog.Error(err)
return
}

os.Exit(m.Run())
}
27 changes: 27 additions & 0 deletions apple/constant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package apple

const (
hostUrl = "https://api.storekit.itunes.apple.com"
sandBoxHostUrl = "https://api.storekit-sandbox.itunes.apple.com"

// Get Transaction History
getTransactionHistory = "/inApps/v1/history/%s" // transactionId

// Get Transaction Info
getTransactionInfo = "/inApps/v1/transactions/%s" // transactionId

// Get All Subscription Statuses
getAllSubscriptionStatuses = "/inApps/v1/subscriptions/%s" // transactionId

// Send Consumption Information
sendConsumptionInformation = "/inApps/v1/transactions/consumption/%s" // transactionId

// Look Up Order ID
lookUpOrderID = "/inApps/v1/lookup/%s" // orderId

// Get Subscription Status
getRefundHistory = "/inApps/v2/refund/lookup/%s" // transactionId

// Get Notification History
getNotificationHistory = "/inApps/v1/notifications/history"
)
12 changes: 0 additions & 12 deletions apple/consts.go

This file was deleted.

23 changes: 23 additions & 0 deletions apple/consumption.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package apple

import (
"context"
"fmt"
"net/http"

"github.com/go-pay/gopay"
)

// SendConsumptionInformation Send Consumption Information
// Doc: https://developer.apple.com/documentation/appstoreserverapi/send_consumption_information
func (c *Client) SendConsumptionInformation(ctx context.Context, transactionId string, bm gopay.BodyMap) (err error) {
path := fmt.Sprintf(sendConsumptionInformation, transactionId)
res, _, err := c.doRequestPut(ctx, path, bm)
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
return fmt.Errorf("http.stauts_code = %d", res.StatusCode)
}
return nil
}
31 changes: 31 additions & 0 deletions apple/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package apple

import "fmt"

// StatusCodeErr 用于判断Apple的status_code错误
type StatusCodeErr struct {
ErrorCode int `json:"errorCode,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
}

// statusCodeErrCheck 检查状态码是否为非200错误
func statusCodeErrCheck(errRsp StatusCodeErr) error {
if errRsp.ErrorCode != 0 {
return &StatusCodeErr{
ErrorCode: errRsp.ErrorCode,
ErrorMessage: errRsp.ErrorMessage,
}
}
return nil
}

func (e *StatusCodeErr) Error() string {
return fmt.Sprintf(`{"errorCode":"%d","errorMessage":"%s"}`, e.ErrorCode, e.ErrorMessage)
}

func IsStatusCodeError(err error) (*StatusCodeErr, bool) {
if bizErr, ok := err.(*StatusCodeErr); ok {
return bizErr, true
}
return nil, false
}
Loading

0 comments on commit 0574937

Please sign in to comment.