paypal is a Go client for PayPal's REST API, with a mock server for the parts you cannot call in CI
Golang client for PayPal REST API
At a glance
- What is it?
- The plutov/paypal module wraps PayPal's REST API in typed Go calls for orders, authorizations, payouts, webhooks, invoicing and the credit card vault. Its most useful feature for a backend team is the generated mock server, and its main risk is that the client can fall behind a fast moving API.
- Who is it for?
- paypal fits Go services that need PayPal orders, captures and payouts with typed structs instead of hand written HTTP calls, and it fits teams that want to test payment code without touching a real account, since the mock server is generated from PayPal's own OpenAPI specification.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 88 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Typed calls for the endpoints a Go service actually uses
The plutov/paypal module is a Go client for the PayPal REST API, and the module path carries its major version, github.com/plutov/paypal/v4. The surface is organised around the objects a payments backend handles: authorizations, orders, sales, payouts, disputes, invoices, subscription plans, products, the credit card vault, webhooks and web experience profiles.
Each area has its own file in the repository root, so the structure is easy to read: order.go, payout.go, authorization.go, vault.go, webhooks.go, invoicing.go, dispute.go, subscription.go, transaction_search.go, filter.go, identity.go and stc.go, with types.go holding the shared request and response structures.
The library does not hide the API behind its own abstraction. You construct PayPal's own request types, pass them to a method, and get PayPal's own response types back. The value over rolling your own is that the payloads are typed and the endpoint paths, authentication and error handling are handled for you.
The audience is therefore a Go backend that integrates with PayPal directly. It is not a storefront framework, and it does not draw a checkout page. A shop that wants PayPal to host the whole payment flow is looking at a different product, while a service that captures, refunds, splits payouts and reconciles webhooks is exactly the case this module covers.
NewClient takes credentials and a base URL, and SetLog turns on request logging
The entry point is a constructor that takes a client id, a secret and one of two base URLs. The README shows the sandbox constant and notes the live alternative in a comment on the same line:
import "github.com/plutov/paypal/v4"
c, err := paypal.NewClient("clientID", "secretID", paypal.APIBaseSandBox) // or paypal.APIBaseLive
c.SetLog(os.Stdout)SetLog writes request and response logging to a writer you supply, here os.Stdout. In a service, that writer should be your logger rather than standard output, and the switch should stay off in production unless you have a plan for what a full log of every PayPal response contains.
The two base URL constants are the switch that matters most for safety. APIBaseSandBox points at the sandbox, APIBaseLive at production, and the choice is made at construction time rather than per call, which means a misconfigured environment variable sends sandbox traffic to production or the other way round with no per-request safeguard. The constructor returns an error, so a bad credential surfaces immediately rather than on the first API call.
Because the choice is a constant in code, the usual pattern is to select it from configuration at startup and pass the result in, keeping the live and sandbox paths in different builds or different environment values.
Authorizations: fetch, capture, void, reauthorize
The authorization flow is the older PayPal payment model and the client covers it end to end. Four operations appear in the README:
auth, err := c.GetAuthorization("2DC87612EK520411B")capture, err := c.CaptureAuthorization(authID, &paypal.Amount{Total: "7.00", Currency: "USD"}, true)The capture call takes a paypal.Amount with a total and a currency, plus a boolean that the README does not describe in prose, so its meaning has to be read from the method signature or the source.
auth, err := c.VoidAuthorization(authID)auth, err := c.ReauthorizeAuthorization(authID, &paypal.Amount{Total: "7.00", Currency: "USD"})Void releases an authorization without capturing it, and reauthorize extends one that has expired. Together with capture and get, these cover the states a payment can be in, which is exactly the set a service needs when a checkout is abandoned at some point in the flow.
The last argument to CaptureAuthorization is worth checking against PayPal's own documentation before you rely on it, because the README does not name it and a wrong reading of a boolean is the kind of mistake that only shows up in production traffic.
Orders, and the two ways to finish one
Orders are the newer model and the client's richest area, since creating an order requires more assembly than the other calls. The create call takes a context, an intent constant such as paypal.OrderIntentCapture, a slice of purchase units, a payment source and an application context.
The rest of the lifecycle is four short calls:
order, err := c.GetOrder("O-4J082351X3132253H")order, err := c.UpdateOrder("O-4J082351X3132253H", []paypal.PurchaseUnitRequest{})auth, err := c.AuthorizeOrder(orderID, paypal.AuthorizeOrderRequest{})capture, err := c.CaptureOrder(orderID, paypal.CaptureOrderRequest{})The split between authorize and capture mirrors the authorization flow, and choosing between them is a business decision rather than a technical one. Authorizing and capturing separately gives you a window in which the payment is approved but not taken, which is what you want when stock or fraud checks happen after the customer approves. Capturing directly is the simpler path when you trust the order as it stands.
A practical note on the create call: it takes a context, so it is the one method in this group that participates in cancellation and deadlines. The other calls in the README do not take a context, which means they use a default one and cannot be cancelled by a request that is going away.
Payouts, webhooks and the vault are the other three pillars
Three feature groups matter for a marketplace or platform business, and the client covers all three.
Payouts send money out, in batches, with an optional note and sender item id per item so you can reconcile later:
payoutResp, err := c.CreatePayout(payout)payout, err := c.GetPayout("PayoutBatchID")Payout items can be fetched individually, and an unclaimed item can be cancelled. The vault API stores payment instruments on the platform side, with store, delete, patch and list operations on credit cards, and the patch operation takes a field list with an operation, a JSON pointer style path and a value. Webhooks are managed the same way, with create, update, get, delete and list, where the list call takes a type such as paypal.AncorTypeApplication.
Invoicing is the smaller area, with a method to generate the next invoice number and one to fetch invoice details by identifier.
One thing to know about the vault is that it stores card data at PayPal, not locally, but your service still handles the numbers on the way in, so whatever you do with card data in transit, the tokenisation flow, and the decision about whether to pass through or use a hosted field, stays your responsibility. The README does not document a hosted form flow, which is the part most integrations get wrong.
A missing endpoint has a documented escape hatch
The client does not wrap every PayPal endpoint, and the README is straightforward about that. It says it is possible that some endpoints are missing, and that you can use built-in paypal functions to perform a request: NewClient, then NewRequest, then SendWithAuth.
That escape hatch is the important design detail here. It means the client is a convenience layer over an authenticated request path rather than a closed API surface, so an endpoint that was added to PayPal's API last month is still reachable without waiting for a release of the module. The cost is that these calls do not get the typed request and response structs, so the JSON handling is on you.
The same openness applies to the resource files. dispute.go, filter.go and transaction_search.go are present in the repository, and the module requires github.com/oapi-codegen/runtime, which is the runtime support library for code generated from OpenAPI documents. That is consistent with the project's testing approach: the project uses the paypal-rest-api-specifications repository to generate a mock server for testing, and the Makefile has a target that pulls the specification files and runs the generator over each OpenAPI document.
So when you hit a gap, you have two honest options: wait for a typed method, or call the endpoint through NewRequest and SendWithAuth and accept the manual JSON. What you should not do is assume the wrapper will appear on a schedule.
The mock server, the Makefile targets, and the release cadence
Testing is the part of this repository that shows real engineering discipline, and for a payments library it is the part that matters most. The README says the project uses paypal-rest-api-specifications to generate a mock server for testing purposes, and that the tests are run with:
make testThe underlying Makefile target runs the Go test suite with the race detector, so the tests are checking for data races as well as behaviour. To refresh the mock server you first need the latest OpenAPI specification files and then generate the mock server code, and because PayPal has multiple files that have to be combined into one OpenAPI document, the README points at a dedicated target:
make oapiAfter regenerating, the README tells you to add any missing mock implementations in mockserver/impl.go. That is the honest cost of the approach: a new PayPal endpoint means a new mock implementation, and until somebody writes it the test suite cannot exercise it.
Linting has its own target, run before a pull request:
make lintwhich runs golangci-lint through the Go tool mechanism. The list of linters pinned in go.mod is long, dozens of indirect dependencies from the golangci-lint ecosystem, which tells you the project treats lint as part of the contract rather than an afterthought.
On releases, the tags show v4.17.0 on 2025-10-05, v4.16.0 on 2025-09-22 and v4.15.0 on 2025-07-11, and the last push to the repository was on 2026-07-28. There is a gap between the last tagged release and the last push, so the main branch may contain fixes you are not getting from the newest tag. The license is MIT, with the licence text in LICENSE.md at the repository root.
Editorial conclusion
paypal fits Go services that need PayPal orders, captures and payouts with typed structs instead of hand written HTTP calls, and it fits teams that want to test payment code without touching a real account, since the mock server is generated from PayPal's own OpenAPI specification. It is the wrong choice for a service that must support API versions the client has not caught up with, for a shop that only needs a one-off checkout link, or for a shop that wants the full hosted checkout experience rather than a server-side integration. Verify first which endpoints your flow needs against the client, since the README warns that some endpoints may be missing and points you at the NewClient, NewRequest and SendWithAuth path, then confirm the module version you pin is new enough for the PayPal API version your account is on.
Frequently asked questions
How do I create a PayPal client in Go with the plutov/paypal package?
Call paypal.NewClient with a client id, a secret and a base URL constant, either paypal.APIBaseSandBox or paypal.APIBaseLive, and enable logging with c.SetLog(os.Stdout). The module path is github.com/plutov/paypal/v4.
What happens if the plutov/paypal client does not support an endpoint I need?
The README says some endpoints may be missing, and that you can perform a request with the built-in paypal functions NewClient, then NewRequest, then SendWithAuth. That path gives you access to the endpoint without a typed wrapper.
How do I test PayPal code without calling the real API?
The project uses the paypal-rest-api-specifications repository to generate a mock server for testing, and the tests are run with make test. To refresh the mock server you regenerate it with make oapi and then add any missing mock implementations in mockserver/impl.go.
Does plutov/paypal support orders, payouts and webhooks?
Yes, the client covers order creation, update, authorize and capture, batch payouts with per item notes, webhook create, update, get, delete and list, plus the credit card vault, invoicing, disputes and identity calls.
What license does the plutov/paypal Go client use?
The project is released under the MIT license, with the licence text in the LICENSE.md file at the repository root, and the module path is github.com/plutov/paypal/v4.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/plutov-paypal)Was this useful?