Всем привет! Мы разрабатываем сервис для сбора, доставки и анализа логов, серверная часть которого написана на Go. В этой статье мы расскажем о проблеме, с которой мы столкнулись при подключении нашего проекта к платежной системе PayPal и о решении, которое мы разработали и успешно внедрили.
Итак, у многих есть опыт работы с API PayPal, использовать OAuth 2.0 довольно просто: подключаем библиотеку-клиент в свой проект и начинаем реализацию.
Для PHP, Java и Python существуют официальные SDK библиотеки, но наш сервис написан на GO, и в этом случае поиск SDK не дал нам приемлемых результатов(https://github.com/search?q=paypal+golang). В итоге найдено пять проектов на github, два из которых выглядят достойно, но имеют ограниченный функционал:
- leebenson/paypal (неполное покрытие API)
- crowdmob/paypal (реализовывает только Express Checkout)
Нам был необходим клиент с возможностью расширения и улучшения функционала, поэтому было принято решение написать свой велосипед.
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.
ссылка на оригинал статьи http://habrahabr.ru/post/274555/
Добавить комментарий