Привет, Хабр!

Меня зовут Энрике, и я работаю Go-разработчиком в RuStore. Сегодня хочу рассказать про библиотеку на Go для комфортной работы с API магазина приложений RuStore. 

Иногда авторизация через API и получение JWE-токена занимают больше времени, чем хотелось бы. С нашей новой библиотекой процесс становится намного проще — интегрируйте её в свой проект и используйте любые методы RuStore API.

В этой статье мы опишем небольшой пример использования методов из нашей библиотеки: как получить данные по платежу, загрузить APK/AAB-файл и оставить ответ на отзыв пользователя. Все доступные методы можно посмотреть в документации.

Как устроена библиотека

Библиотеку было решено писать на Go, так как этот язык достаточно прост в использовании и удобен для написания HTTP-запросов.

Библиотека RuStore API состоит из следующих директорий:

  1. client:  определяет структуру Client, в которой содержатся все необходимые поля для работы. У Client есть метод Auth(), с помощью которого и происходит авторизация.

  2. cmd/examples: здесь расположены примеры работы с библиотекой.

  • comments — работа с отзывами;

  • publishing —  загрузка и публикация приложений;

  • payments —  получение данных платежа и подписки.

Расположенные в корне проекта comments, payments, publishing — пакеты, в которых содержится логика формирования запросов и структура ответов для каждого из разделов RuStore API.

Начало работы

Представим себе, что вы Android-разработчик и планируете опубликовать ваше первое приложение в RuStore. В первую очередь вам нужно зарегистрироваться в RuStore Консоли.

Для использования библиотеки получите всего три параметра из Консоли:

  1. ID вашей компании;

  2. Ваш приватный ключ с разрешениями на работу с нужными разделами;

  3. ID вашего приватного ключа. Его вы получаете при генерации приватного ключа.

Сохраните эти параметры в файле формата JSON (например, options.json) в следующем виде:

{
	"companyID":"ID вашей компании",
	"privateKeyID":"ID вашего приватного ключа",
	"privateKey":"Ваш приватный ключ"
}

Готово! Вы установили параметры, которые будут использоваться при выполнении всех запросов к API.

Авторизация 

Авторизуйтесь и передайте параметры из options.json клиенту из пакета client:

package main


import ( 
   "context"
   "encoding/json"
   "flag"
   "fmt"
   "log"
   "net/url"
   "os"
   "time"
   "gitflic.ru/project/rustore/rustoreapi/client"
   "gitflic.ru/project/rustore/rustoreapi/publishing"
   "gitflic.ru/project/rustore/rustoreapi/comments"
   "gitflic.ru/project/rustore/rustoreapi/payments"
)


var (
// так мы парсим все параметры из файла options.json
   file = flag.String(
       "file",
       "",
       "file with options: privateKey, privateKeyID, companyID",
   ) 
   options Options 
)
// структура для того, чтобы сохранить параметры из options.json
type Options struct {
   PrivateKey   string `json:"privateKey"`
   PrivateKeyID string `json:"privateKeyID"`
   CompanyID    string `json:"companyID"`
}


func main() {

// парсим всё из options.json
   flag.Parse() 
   data, err := os.ReadFile(*file)
   if err != nil {
       panic(err)
   }
   if err = json.Unmarshal(data, &options); err != nil {
       panic(err)
   }

// создаём клиента с переданными из options.json параметрами
   cl := client.New(
       options.PrivateKeyID,
       options.PrivateKey,
       options.CompanyID,
   )
   err = cl.Auth()
   if err != nil {
       log.Fatal(err)
   }

Схематично процесс авторизации выглядит следующим образом:

Авторизация с помощью JWE-токена
Авторизация с помощью JWE-токена

После авторизации наш JWE-токен будет передаваться в заголовке при каждом вызове метода RuStore API.

Итак, всё самое сложное позади: вы зарегистрировались в Консоли и авторизовались с полученными параметрами.

Загрузка APK-файла

Первое, что нужно сделать, чтобы познакомить пользователей с вашим приложением — загрузить APK-файл.

Чтобы начать работать с методами загрузки и публикации приложений, нужно создать новую сущность (pub в примере), которой вы передадите созданного клиента и packageName вашего приложения.

pub := publishing.New(
       cl,
       packageName,
   )

Таким образом, вы сможете использовать любые методы для загрузки и публикации приложений.

Теперь вам нужно вызвать метод загрузки APK и передать ему необходимые параметры:

var (
//id версии вашего приложения
  versionID    = 123            
// packageName вашего приложения
  packageName  = "app.example" 
// параметры в URL. По умолчанию пустые
  urlValues = url.Values{} 
// путь к APK, который вы загружаете
  fileNameAPK = "Desktop/app-release.apk" 
// isMainAPK - признак, который присваивается основному APK-файлу
  isMainAPK = true
)

// контекст, который передаётся во все методы
ctx, cancel := context.WithTimeout(
		context.Background(),
		client.TimeOutSeconds*10*time.Second,
	)

	defer cancel()

// загружаем APK-файл:
   uploadAPK, err := pub.UploadAPKFile(
       ctx,
       versionID,
       fileNameAPK,
	   isMainAPK,
       urlValues,
   )
   if err != nil {
       log.Fatal(err)
   }


   fmt.Printf("\nзагрузили  APK файл: %+v\n", uploadAPK)
   if uploadAPK.Message != nil {
       fmt.Println(*uploadAPK.Message)
   }

Вывод:

загрузили  APK файл: {Code:OK Message:<nil> Timestamp:2024-03-20 15:31:57.586533733 +0300 MSK}

Вот и всё! Доступ к любому полю ответа вы можете получить с помощью uploadAPK, например: 

fmt.Println("Получить код ответа:", uploadAPK.Code)

 Вывод:

OK

Вывод, в случае, если есть ошибка (uploadAPK.Message != nil):

File was not uploaded successfully: The Version Code of the downloaded file must be greater than the previous version

Для запуска программы вы можете воспользоваться командой go run и указать там файл, из которого считываются данные с помощью -file “Имя файла”, например, так:

go run main.go -file options.json

Загрузка AAB файла

В RuStore можно загружать файлы формата не только APK, но и AAB. Порядок действий идентичен загрузке файла APK, за исключением названия метода.

Важно: перед загрузкой AAB файла убедитесь, что у вас загружена подпись для публикации Android App Bundle (AAB) через RuStore Консоль.

uploadAAB, err := pub.UploadAABFile(
       ctx,
       versionID,
// путь к AAB, который вы загружаете
       fileNameAAB,
   )
   if err != nil {
       log.Fatal(err)
   }


   fmt.Printf("\nзагрузили  AAB файл: %+v\n", uploadAAB)
   if uploadAAB.Message != nil {
       fmt.Println(*uploadAAB.Message)
   }

Вывод:

загрузили  AAB файл: {Code:OK Message:<nil> Timestamp:2024-03-20 15:35:31.486143514 +0300 MSK}

Ответ на отзыв

Итак, файл самого приложения загружен, ваши пользователи начинают реагировать на продукт, и теперь вы хотите отвечать на их отзывы.

Теперь создайте сущность coms, передав в неё клиента и packageName:

coms := comments.New(cl, packageName)   

Далее нужно вызвать метод AnswerComment, передав ID комментария, на который вы хотите ответить, и текст ответа:

var (
// текст, которым вы хотите ответить на отзыв
feedbackText= "Спасибо вам большое!"
// id отзыва, на который вы хотите ответить
commentID   = "321"  
)
answerComment, err := coms.AnswerComment(
       ctx,
       commentID,
       feedbackText,
   )
   if err != nil {
       log.Fatal(err)
   }


   fmt.Printf("\nответили на отзыв: %+v\n", answerComment)

Вывод:

ответили на отзыв: {Code:OK Message:<nil> Body:{ID:1233211} Timestamp:2024-03-20 16:14:00.06460532 +0300 MSK}

Получение информации о платеже

Вы загрузили приложение, ответили на отзывы, самое время посмотреть информацию по платежам ваших пользователей. 

Как и в предыдущих примерах, создайте сущность для работы с платежами:

pay := payments.New(cl, packageName)

Получить информацию о платеже можно с помощью метода GetPaymentInfo:

Здесь вам понадобится purchaseToken — связка из userID и invoiceID в формате “userID.invoiceID”.

var purchaseToken = "12345"
paymentInfo, err := pay.GetPaymentInfo(
       ctx,
       purchaseToken,
   )
   if err != nil {
       log.Fatal(err)
   }


   fmt.Printf("\nИнформация по платежу:%+v\n", paymentInfo)

Вывод:

Информация по платежу:
{Code:OK
Message:<nil>
Body:
{InvoiceID:12345
InvoiceDate:2024-03-04T17:06:40+03
InvoiceStatus:confirmed
ApplicationCode:2414512
ApplicationName:MyApp
OwnerCode:3123124
OwnerName:Иванов Иван Иванович
PaymentInfo:{PaymentDate:2024-03-04T17:07:21+03
PaymentID:123456
PaymentParams:<nil>
LoyaltyInfo:<nil>
CardID: <nil>
PaysysCode:RBS-shortname
MaskedPan:XX1234
ExpiryDate:102313
...

Заключение

Эти и многие другие методы для работы с RuStore API вы можете найти на странице проекта.

Если у вас есть вопросы и предложения по развитию библиотеки, обязательно делитесь ими в комментариях!

Комментарии (5)


  1. micronull
    29.03.2024 08:48
    +2

    http клиент нельзя подменить в https://gitflic.ru/project/rustore/rustoreapi/blob?file=client%2Fclient.go&branch=master

    Сделайте его зависимостью через интерфейс:

    type doer interface {
      Do(*http.Request) (*http.Response, error)
    }

    Второй момент, это не расширяете контекст ошибки.

    Если у вас произойдёт ошибка в Auth() то как понять к чему именно она относится?

    К вызову GetEncodedSignature или GetJWEToken ?

    Воспользуйтесь:

    fmt.Errorf("more info: %w", err)

    Дальше смотреть не стал, но вашей библитеке требуется ревью.


    1. EnrikeMorekhon Автор
      29.03.2024 08:48
      +2

      Спасибо за комментарий!
      Обязательно исправим.


      1. micronull
        29.03.2024 08:48
        +1

        Чтоб не ломать обратную совместимость, подмену http клиента можно сделать через опции.

        Так же рекомендую присмотреться к http.DefaultTransport для дефолтного http клиента, так как там выставлены таймауты, а у вашего их нет. Это может быть больно)


  1. milast
    29.03.2024 08:48

    Немного не по теме, но хотел зарегистрироваться в русторе и выложить свое некоммерческое приложение. Но когда дошел до шага верификации, желание подолжить отпало само.

    Простите, но отправлять вам фото своего паспорта я не готов. Во-первых - в стравнении с Гуглом (где кстати нет подобной верификации), у вас слишком мало клиентов при меньшей функциональности. Во-вторых - я опасаюсь за свои персональные данные. Сейчас они утекают на лево и направо, кто бы что ни говорил.

    Добавить еще один потенциальный источник утечки или передачи третьим лицам моих перс.данных ради публикации частного некоммерческого приложения это неоправдано.


    1. RuStore
      29.03.2024 08:48

      Понимаем ваше беспокойство по поводу защиты личных данных. У нас в RuStore процедура верификации абсолютно конфиденциальна. Мы используем ваши данные только согласно нашей политике конфиденциальности, без каких-либо дополнительных целей.
      https://www.rustore.ru/help/legal/privacy-policy-developers/

      Если у вас всё еще есть вопросы или опасения, пишите нам на support@rustore.ru. Мы можем помочь вам зарегистрироваться без предоставления фотографии паспорта.