Go-client for PayPal API

Hello! We are developing a service for collecting, delivering and analyzing logs, the server part of which is written in Go . In this article we will talk about the problem that we encountered when connecting our project to the PayPal payment system and the solution that we developed and successfully implemented.
So, many people have experience with the PayPal API, using OAuth 2.0 is quite simple: we connect the client library to our project and begin implementation.
There are official SDK libraries for PHP, Java and Python, but our service is written in GO, and in this case, the SDK search did not give us acceptable results ( https://github.com/search?q=paypal+golang ). As a result, five projects on github were found, two of which look decent, but have limited functionality:
- leebenson / paypal (incomplete API coverage)
- crowdmob / paypal (implements only Express Checkout)
We needed a client with the ability to expand and improve functionality, so it was decided to write your
OAuth 2.0
At the development stage, we used PayPal sandbox , where we tested all kinds of API requests.
The first stage is work with the PayPal protocol and authorization. PayPal uses OAuth version 2.0. First we need to get the private keys (client_id and secret_key).
Authorization is carried out as follows: after receiving client_id and secret_key, you need to make a request to PayPal to get access_token, which is valid for a given time. Further, all requests in the PayPal API must be accompanied by this token in the request header (-u ":").

Implementation using our client:
import "github.com/logpacker/PayPal-Go-SDK"
// ...
// Create a client instance
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
accessToken, err := c.GetAccessToken()
Further, the client object will have all available methods for working with the API. For example, to create a payment, we need to do the following:
paymentResponse, err := client.CreatePayment(p)
We are working to provide and describe all available API operations , while it is possible to call any final method using basic functions:
req, err := c.NewRequest(method, url, payload)
c.SendWithAuth(req, &resp)
All requests in PayPal can be logged to a file, a complete dump of the request is saved along with the headers:
c.SetLogFile("/tpm/paypal-debug.log")
Available API Features
A full list of PayPal API functions is presented in the specification , all of them are divided into groups, Payments, Orders, Vault. In the client, we implemented built-in functions for the basic API operations:
POST / v1 / oauth2 / token - receiving a temporary access_token
accessToken, err := c.GetAccessToken()
The application is responsible for saving the key, so instead of receiving a new key, you can install the saved one.
token := "abcdef"
c.SetAccessToken(token)
POST / v1 / payments / payment - create a payment in PayPal. We have provided two functions for creating a payment.
Internal PayPal payment:
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. Payment of any type:
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 - receiving payment information
payment, err := c.GetPayment(paymentID)
GET / v1 / payments / payment - a list of all payments
payments, err := c.GetPayments()
GET / v1 / payments / authorization / ID - get authorization information
authID := "2DC87612EK520411B"
auth, err := c.GetAuthorization(authID)
POST / v1 / payments / authorization / ID / capture - authorization block
capture, err := c.CaptureAuthorization(authID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"}, true)
POST / v1 / payments / authorization / ID / void - cancel authorization
auth, err := c.VoidAuthorization(authID)
POST / v1 / payments / authorization / ID / reauthorize - reauthorization
auth, err := c.ReauthorizeAuthorization(authID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})
GET / v1 / payments / sale / ID - receiving a sale
saleID := "36C38912MN9658832"
sale, err := c.GetSale(saleID)
POST / v1 / payments / sale / ID / refund - refund for the sale. You can make a full refund or a partial one.
// 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 - receiving information about the return
orderID := "O-4J082351X3132253H"
refund, err := c.GetRefund(orderID)
GET / v1 / payments / orders / ID - receiving order information
order, err := c.GetOrder(orderID)
POST / v1 / payments / orders / ID / authorize - order authorization
auth, err := c.AuthorizeOrder(orderID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"})
POST / v1 / payments / orders / ID / capture - blocking the order (can be partial or full, depending on the transferred Amount and IsFinalTransaction)
capture, err := c.CaptureOrder(orderID, &paypalsdk.Amount{Total: "7.00", Currency: "USD"}, true, nil)
POST / v1 / payments / orders / ID / do-void - order cancellation
order, err := c.VoidOrder(orderID)
You can also use godoc documentation to familiarize yourself with all the client features: https://godoc.org/github.com/logpacker/PayPal-Go-SDK
Testing and CI
The project implements two types of tests: Unit and Integration. Unit tests allow you to check the performance of internal conditions and validation.
An example of checking input parameters in the NewClient function:
_, err := NewClient("", "", "")
if err == nil {
t.Errorf("All arguments are required in NewClient()")
} else {
fmt.Println(err.Error())
}
Integration tests work directly with test data on PayPal Sandbox, check server responses and their conversion to go-structures.
This process is presented in the diagram below:

Example of checking the response of the CreateDirectPaypalPayment function:
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")
}
We have created a test account in the PayPal sandbox and use test IDs for each type of request. For example, on a payment with ID PAY-5YK922393D847794YKER7MUI, you can test the receipt of information about it. In order to tell the client that you are working with Sandbox, you need to set the base API URL (and after testing change it to the Live URL):
c, err := paypalsdk.NewClient("clientID", "secretID", paypalsdk.APIBaseSandBox)
Tests can be run locally with the go test command, but you cannot always be sure that the code in the repository will always be stable. Therefore, we use Continuous Integration (CI) to automatically run the test with each push to the repository. We use TravisCI , it easily integrates with the GitHub repository, the configuration of .travis.yml lies at the root of our project:
language: go
go:
- 1.5
install:
- export PATH=$PATH:$HOME/gopath/bin
script:
- go test -v
Open Source and Upcoming Plans
You can find all our achievements on GitHub, they are published under MIT license. The plans are to create some standard library for Go, provide full coverage of the API (+ webapps, etc.).
Current documentation can be found on the project page on GitHub.
We are waiting for your commits and pull-requests to logpacker / PayPal-Go-SDK .