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



    Всем привет! Мы разрабатываем сервис для сбора, доставки и анализа логов, серверная часть которого написана на 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.
    • +15
    • 7,3k
    • 1
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 1
    • +1
      Выглядит круто, букмарк.

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