golang: Google Play退款订单数据查询

Google Play Developer API

https://developers.google.com/android-publisher

查询退款数据

链接中开发者文档中Voided Purchases API已经给出了查询退款订单的步骤,本文给出了文档中没有提到的access_token的获取方式,并给出一个简单的demo

查询链接

GET https://www.googleapis.com/androidpublisher/v3/applications/${
     yourPkgName}/purchases/voidedpurchases?access_token=${
     AccessTokne}

接口限制

  1. 每天 6000 次查询(一天是从太平洋时间午夜开始,并于次日同一时间结束)
  2. 在任何时长为 30 秒的期限内查询 30 次。

其他参数说明

  • startTime 在响应中看到的最早作废的购买交易的时间。默认情况下,startTime 设为 30 天以前。最长只能查询到30天内的记录。
  • endTime 在响应中看到的最晚作废的购买交易的时间。默认情况下,endTime 设为当前时间。
  • maxResults: 每个响应中出现的已作废购买交易的数量上限。默认值为 1000,最大值1000。
  • token 之前响应中的继续令牌
  • type : 0/1。取值为0,系统只会返回已作废的应用内购买。取值为1 ,系统将返回已作废的应用内购买和已作废的订阅购买。默认值为 0。
    参数以&key=value的形式拼接在链接后

AccessToken获取
// PS:首先你应该能拿到应用的包名和授权账号密钥对应的公钥,这里使用的是.json格式的公钥

import (
	"context"
	"net/http"
	
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/androidpublisher/v3"
)
const credentials = []byte(``) // 公钥的json串
func getGoogleApiAccessToken() (token string, err error) {
     
	c := &http.Client{
     Timeout: 10 * time.Second}
	ctx := context.WithValue(context.Background(), oauth2.HTTPClient, c)

	conf, err := google.JWTConfigFromJSON(credentials, androidpublisher.AndroidpublisherScope)
	if err != nil {
     
		return "", err
	}

	val := conf.Client(ctx).Transport.(*oauth2.Transport)
	tokenObj, err := val.Source.Token()
	if err != nil {
     
		return "", err
	}

	return tokenObj.AccessToken, nil
}

获取退款数据
// 需要注意的地方已经写在代码里了

import (
	"context"
	"encoding/json"
	"github.com/gomodule/redigo/redis"
	"github.com/sirupsen/logrus"
	"golang.org/x/oauth2/google"
	"io/ioutil"
	"net/http"
	"strconv"
	"time"
)

// 注意数据项里的voidedSource和voidedReason,API文档里给的是字符串类型,但实际返回的为整型
// 返回数据json采取驼峰命名方式
type voidedPurchasesItem struct {
     
	Kind               string `json:"kind"`
	PurchaseToken      string `json:"purchaseToken"` // 收据信息
	PurchaseTimeMillis string `json:"purchaseTimeMillis"` // 购买时间
	VoidedTimeMillis   string `json:"voidedTimeMillis"` // 退款时间
	OrderId            string `json:"orderId"` // google 生成的orderId
	VoidedSource       int    `json:"voidedSource"`
	VoidedReason       int    `json:"voidedReason"`
}

type googleVoidedPurchases struct {
     
	TokenPagination struct {
     
		NextPageToken string `json:"next_page_token"` // next page token不为空时,证明当前时间区间内数据超过了1000条,但只返回了1000条,可以传递此token继续获取
	} `json:"tokenPagination"` // 数据少于1000条时,tokenPagination字段不存在
	VoidedPurchases []voidedPurchasesItem `json:"voidedPurchases"`
}

const googleQueryApi = "https://www.googleapis.com/androidpublisher/v3/applications/${pkg_name}/purchases/voidedpurchases?access_token="
func pollingGoogleRefundOrder() {
     
	accessToken, err := getGoogleApiAccessToken([]byte(credentials))
	if err != nil {
     
		logrus.Errorf("[pollingGoogleRefundOrder] get access token error:%s", err.Error())
		return
	}
	// 获取上一次调用时间和token,用于增量拉取数据
	// 这里使用redis记录数据,当然也可以使用文件、内存、数据库等其他方式存储
	// time为上次调用时间 token用于30次循环仍没有处理完数据的情况
	latestPollingTimeKey, purchasesTokenKey := "time", "token"
	paramMap,err := redis.StringMap(client2.GetRedisConn().Do("HGetAll","google_polling_param"))
	// 没有正确取到运行的参数,直接返回。若不返回使用默认参数去取,会处理较多的重复数据
	if err != nil && err != redis.ErrNil {
     
		logrus.Errorf("[pollingGoogleRefundOrder] get latest purchases tokene error:%s", err.Error())
		return
	}
	client := &http.Client{
     Timeout: time.Second * 5}
	startRunningTime := strconv.Itoa(time.Now().Nanosecond()/1e6)
	// API限制:1. 每天 6000 次查询(一天是从太平洋时间午夜开始,并于次日同一时间结束)。2.在任何时长为 30 秒的期限内查询 30 次。
	// startTime 在响应中看到的最早作废的购买交易的时间。默认情况下,startTime 设为 30 天以前
	// endTime 在响应中看到的最晚作废的购买交易的时间。默认情况下,endTime 设为当前时间。
	// maxResults 每个响应中出现的已作废购买交易的数量上限。默认情况下,此值为 1000。最大值1000。
	// token 之前响应中的继续令牌
	// type 0,则系统只会返回已作废的应用内购买。1 时,系统将返回已作废的应用内购买和已作废的订阅购买。默认值为 0。
	circleQueryCount := 0 
	latestPollingTime, latestPurchaseToken := paramMap[latestPollingTimeKey], paramMap[purchasesTokenKey]
	for circleQueryCount < 30 {
      // 对应API限制1
		queryUrl := googleQueryApi + accessToken
		if latestPollingTime != "" {
     
			queryUrl += "&startTime=" + latestPollingTime
		}
		if latestPurchaseToken != "" {
     
			queryUrl += "&token=" + latestPurchaseToken
		}
		res, err := client.Get(queryUrl)
		if err != nil {
     
			logrus.Errorf("[pollingGoogleRefundOrder] query url error:%s", err.Error())
			time.Sleep(time.Second)
			continue
		}
		if res.Body == nil || res.StatusCode != http.StatusOK {
     
			logrus.WithField("res", res).Errorf("[pollingGoogleRefundOrder] request failed")
			continue
		}
		bodyContent, err := ioutil.ReadAll(res.Body)
		if err != nil {
     
			logrus.WithField("res", res).Errorf("[pollingGoogleRefundOrder] read res body error:%s", err.Error())
			res.Body.Close()
			time.Sleep(time.Second)
			continue
		}
		var voidedPurchasesInfo googleVoidedPurchases
		err = json.Unmarshal(bodyContent, &voidedPurchasesInfo)
		if err != nil {
     
			logrus.WithField("res", res).Errorf("[pollingGoogleRefundOrder] unmarshal purchases info error:%s", err.Error())
			res.Body.Close()
			time.Sleep(time.Second)
			continue
		}
		res.Body.Close()

		for _, purchasesItem := range voidedPurchasesInfo.VoidedPurchases {
     
		// todo something 执行更新订单状态等业务操作
			logrus.Infoln(purchasesItem)
		}

		latestPurchaseToken = voidedPurchasesInfo.TokenPagination.NextPageToken
		// 没有下一页,直接推出循环
		if voidedPurchasesInfo.TokenPagination.NextPageToken == "" || len(voidedPurchasesInfo.VoidedPurchases) < 1000 {
     
			break
		}
		circleQueryCount++
	}

	// 如果本时间区间的数据已经读取完成,记录下次轮训开始时间为本次执行时间
	if latestPurchaseToken == "" {
     
		latestPollingTime = startRunningTime
	}
	// 记录下一次的执行参数
	client2.GetRedisConn().Do("HMSet",redis.Args{
     }.Add("google_polling_param").AddFlat([]interface{
     }{
     latestPollingTimeKey, latestPollingTime, purchasesTokenKey, latestPurchaseToken})...)
	if err != nil {
     
		logrus.WithFields(logrus.Fields{
     "ori": paramMap, "latest_polling_time":latestPollingTime,"latest_purchases_token":latestPurchaseToken}).Errorf(
			"[pollingGoogleRefundOrder] cache polling param error:%s", err.Error())
	}
	// TODO 这里可以输出一些统计数据
	logrus.Infof("[pollingGoogleRefundOrder] finish")
}

你可能感兴趣的:(随笔,golang,google,play)