Pull to refresh

Go-клиент для PayPal API

Reading time 6 min
Views 10K


Всем привет! Мы разрабатываем сервис для сбора, доставки и анализа логов, серверная часть которого написана на Go. В этой статье мы расскажем о проблеме, с которой мы столкнулись при подключении нашего проекта к платежной системе PayPal и о решении, которое мы разработали и успешно внедрили.

Итак, у многих есть опыт работы с API PayPal, использовать OAuth 2.0 довольно просто: подключаем библиотеку-клиент в свой проект и начинаем реализацию.

Для PHP, Java и Python существуют официальные SDK библиотеки, но наш сервис написан на GO, и в этом случае поиск SDK не дал нам приемлемых результатов(https://github.com/search?q=paypal+golang). В итоге найдено пять проектов на github, два из которых выглядят достойно, но имеют ограниченный функционал:


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

OAuth 2.0


На этапе разработки мы использовали PayPal sandbox, где проводили тестирование всех видов запросов API.

Первый этап — это работа с протоколом PayPal и авторизация. PayPal использует OAuth версии 2.0. Для начала нам необходимо получить приватные ключи (client_id и secret_key).

Авторизация осуществляется следующим образом: после получения client_id и secret_key необходимо сделать запрос в PayPal на получение access_token, который действителен в течении заданного времени. Далее все запросы в PayPal API должны сопровождаться этим токеном в заголовке запроса (-u ":").



Реализация с использованием нашего клиента:

import "github.com/logpacker/PayPal-Go-SDK"
// ...
// Create a client instance
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
accessToken, err := c.GetAccessToken()


Далее объект клиента будет иметь все доступные методы для работы с API. Например, чтобы создать платеж нам необходимо выполнить следующее:

paymentResponse, err := client.CreatePayment(p)


Мы работаем над тем, чтобы предоставить и описать все доступные операции API, при этом есть возможность вызвать любой конечный метод посредством базовых функций:

req, err := c.NewRequest(method, url, payload)
c.SendWithAuth(req, &resp)


Все запросы в PayPal можно логировать в файл, полный дамп запроса сохраняется вместе с заголовками:

c.SetLogFile("/tpm/paypal-debug.log")


Доступные функции API


Полный список функций PayPal API представлен в спецификации, все они делятся на группы, Payments, Orders, Vault. В клиенте мы реализовали встроенные функции для основных операций API:

POST /v1/oauth2/token — получение временного access_token

accessToken, err := c.GetAccessToken()


За сохранение ключа отвечает приложение, поэтому вместо получения нового ключа можно установить сохраненный.

token := "abcdef"
c.SetAccessToken(token)


POST /v1/payments/payment — создание платежа в PayPal. Мы предоставили две функции для создания платежа.

Внутренний PayPal платеж:

amount := paypalsdk.Amount{
    Total:    "7.00",
    Currency: "USD",
}
redirectURI := "http://example.com/redirect-uri"
cancelURI := "http://example.com/cancel-uri"
description := "Description for this payment"
paymentResult, err := c.CreateDirectPaypalPayment(amount, redirectURI, cancelURI, description)


2. Платеж любого типа:

p := paypalsdk.Payment{
    Intent: "sale",
    Payer: &paypalsdk.Payer{
        PaymentMethod: "credit_card",
        FundingInstruments: []paypalsdk.FundingInstrument{paypalsdk.FundingInstrument{
            CreditCard: &paypalsdk.CreditCard{
                Number:      "4111111111111111",
                Type:        "visa",
                ExpireMonth: "11",
                ExpireYear:  "2020",
                CVV2:        "777",
                FirstName:   "John",
                LastName:    "Doe",
            },
        }},
    },
    Transactions: []paypalsdk.Transaction{paypalsdk.Transaction{
        Amount: &paypalsdk.Amount{
            Currency: "USD",
            Total:    "7.00",
        },
        Description: "My Payment",
    }},
    RedirectURLs: &paypalsdk.RedirectURLs{
        ReturnURL: "http://...",
        CancelURL: "http://...",
    },
}
paymentResponse, err := client.CreatePayment(p)


GET /v1/payments/payment/ID — получение информации о платеже

payment, err := c.GetPayment(paymentID)


GET /v1/payments/payment — список всех платежей

payments, err := c.GetPayments()


GET /v1/payments/authorization/ID — получение информации об авторизации

authID := "2DC87612EK520411B"
auth, err := c.GetAuthorization(authID)


POST /v1/payments/authorization/ID/capture — блокировка авторизации

capture, err := c.CaptureAuthorization(authID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"}, true)


POST /v1/payments/authorization/ID/void — отмена авторизации

auth, err := c.VoidAuthorization(authID)


POST /v1/payments/authorization/ID/reauthorize — реавторизация

auth, err := c.ReauthorizeAuthorization(authID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})


GET /v1/payments/sale/ID — получение объекта продажи

saleID := "36C38912MN9658832"
sale, err := c.GetSale(saleID)


POST /v1/payments/sale/ID/refund — возврат средств для объекта продажи. Можно сделать как полный возврат платежа, так и частичный.

// Full
refund, err := c.RefundSale(saleID, nil)
// Partial
refund, err := c.RefundSale(saleID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})


GET /v1/payments/refund/ID — получение информации о возврате

orderID := "O-4J082351X3132253H"
refund, err := c.GetRefund(orderID)


GET /v1/payments/orders/ID — получение информации о заказе

order, err := c.GetOrder(orderID)


POST /v1/payments/orders/ID/authorize — авторизация заказа

auth, err := c.AuthorizeOrder(orderID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})


POST /v1/payments/orders/ID/capture — блокировка заказа (может быть частичной или полной, в зависимости от переданных Amount и IsFinalTransaction)

capture, err := c.CaptureOrder(orderID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"}, true, nil)


POST /v1/payments/orders/ID/do-void — отмена заказа

order, err := c.VoidOrder(orderID)


Также можно воспользоваться godoc документацией для ознакомления со всеми функциями клиента: https://godoc.org/github.com/logpacker/PayPal-Go-SDK

Тестирование и CI


В проекте реализованы два типа тестов: Unit и Integration. Unit тесты позволяют проверить работоспособность внутренних условий и валидацию.
Пример проверки входных параметров в функции NewClient:

_, err := NewClient("", "", "")
if err == nil {
	t.Errorf("All arguments are required in NewClient()")
} else {
	fmt.Println(err.Error())
}


Интеграционные тесты работают непосредственно с тестовыми данными на PayPal Sandbox, проверяют ответы сервера и их преобразования в go-структуры.

Данный процесс представлен на схеме ниже:



Пример проверки ответа функции CreateDirectPaypalPayment:

c, _ := NewClient(testClientID, testSecret, APIBaseSandBox)
c.GetAccessToken()

amount := Amount{
	Total:    "15.11",
	Currency: "USD",
}

p, err := c.CreateDirectPaypalPayment(amount, "http://example.com", "http://example.com", "test payment")

if err != nil || p.ID == "" {
	t.Errorf("Test paypal payment is not created")
}


Мы создали тестовый аккаунт в песочнице PayPal и используем тестовые ID для каждого вида запроса. Например, на платеже с ID PAY-5YK922393D847794YKER7MUI можно тестировать получение информации о нем. Для того, чтобы сообщить клиенту, что вы работаете с Sandbox, вам необходимо установить базовый URL API (и после тестирования поменять его на Live URL):

c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)


Тесты могут быть запущены локально командой go test, но нельзя быть всегда уверенным, что код в репозитории будет всегда стабильным. Поэтому мы используем Continuous Integration (CI) для автоматического запуска теста при каждом пуше в репозиторий. Мы используем TravisCI, он легко интегрируется с GitHub репозиторием, в корне нашего проекта лежит конфигурация .travis.yml:

language: go
go:
 - 1.5
install:
 - export PATH=$PATH:$HOME/gopath/bin
script:
 - go test -v


Open Source и ближайшие планы


Все наши наработки вы можете найти на GitHub, они опубликованы под MIT лицнзией. В планах создать некую стандартную библиотеку для Go, обеспечить полное покрытие API (+webapps и т.д.)

Актуальную документацию можно найти на странице проекта в GitHub.

Ждем ваших коммитов и pull-реквестов на logpacker/PayPal-Go-SDK.
Tags:
Hubs:
+15
Comments 1
Comments Comments 1

Articles