PaypalAPI
Installation
bundle add paypal-rest-api
Features
- Supported Ruby Versions - (2.6 .. 3.3), head, jruby-9.4, truffleruby-24
- No dependencies;
- Automatic authorization & re-authorization;
- Auto-retries (configured);
- Automatically added Paypal-Request-Id header for idempotent requests if not provided;
- Webhooks Offline verification (needs to download certificate once)
- Custom callbacks before/after request
- Automatic pagination methods
Usage
# in config/initializers/paypal_rest_api.rb
PaypalAPI.client = PaypalAPI::Client.new(
client_id: ENV['PAYPAL_CLIENT_ID'],
client_secret: ENV['PAYPAL_CLIENT_SECRET'],
live: ENV['PAYPAL_LIVE'] == 'true'
)
# Use this API to create order
PaypalAPI::Orders.create(body: body, headers: {prefer: 'return=representation'})
# After customer approves order, we can tell PayPal to authorize it
PaypalAPI::Orders.authorize(order_id)
# After PayPal authorizes order, we can capture it
PaypalAPI::AuthorizedPayments.capture(authorization_id)
# After payment was captured, we can refund it
PaypalAPI::CapturedPayments.refund(capture_id, body: payload, headers: headers)
# Use this APIs to register/actualize PayPal Webhooks
PaypalAPI::Webhooks.list
PaypalAPI::Webhooks.create(body: body)
PaypalAPI::Webhooks.update(webhook_id, body: body)
PaypalAPI::Webhooks.delete(webhook_id)
Per-request Configuration
# Anywhere in your business logic
client = PaypalAPI::Client.new(
client_id: ENV['PAYPAL_CLIENT_ID'],
client_secret: ENV['PAYPAL_CLIENT_SECRET'],
live: ENV['PAYPAL_LIVE'] == 'true'
)
# Show order
client.orders.show(order_id)
# Create order
client.orders.create(body: body)
Custom requests
If you want to request some undocumented APIs (or some forgotten API):
response = PaypalAPI.post(path, query: query, body: body, headers: headers)
response = PaypalAPI.get(path, query: query, body: body, headers: headers)
response = PaypalAPI.patch(path, query: query, body: body, headers: headers)
response = PaypalAPI.put(path, query: query, body: body, headers: headers)
response = PaypalAPI.delete(path, query: query, body: body, headers: headers)
# Or, using per-request client:
response = client.post(path, query: query, body: body, headers: headers)
response = client.get(path, query: query, body: body, headers: headers)
response = client.patch(path, query: query, body: body, headers: headers)
response = client.put(path, query: query, body: body, headers: headers)
response = client.delete(path, query: query, body: body, headers: headers)
Environment helpers
PaypalAPI.client = PaypalAPI::Client.new(
live: ENV['PAYPAL_LIVE'] == 'true',
client_id: ENV['PAYPAL_CLIENT_ID'],
client_secret: ENV['PAYPAL_CLIENT_SECRET']
)
PaypalAPI.live? # => false
PaypalAPI.sandbox? # => true
PaypalAPI.api_url # => "https://api-m.sandbox.paypal.com"
PaypalAPI.web_url # => "https://sandbox.paypal.com"
Response
Response
object is returned after each API
request.
Original HTTP response data
-
response.http_status
- response HTTP status as Integer -
response.http_body
- response body as String -
response.http_headers
- response headers as Hash with String keys -
response.http_response
- original Net::HTTP::Response object -
response.request
- Request object that was used to get this response
Parsed JSON body methods
-
response.body
- parsed JSON body, keys are Symbols -
response[:field]
- gets:field
attribute from parsed body, returns nil if response have no such key -
response.fetch(:field)
- gets:field
attribute from parsed body, raises KeyError if response has no such key
Error check methods
-
response.success?
- checks HTTP status code is 2xx -
response.failed?
- checks HTTP status code is not 2xx
Using HATEOAS links
-
response.follow_up_link('approve', query: nil, body: nil, headers: nil)
- Finds HATEOAS link is response withrel=approve
and requests it. Returnsnil
if no such link were found.
Pagination (see Automatic Pagination for examples)
-
response.each_page { |response| ... }
- iterates over each page in response -
response.each_page_item(items_field) { |item| ... }
- iterates over each page item
Configuration options
PaypalAPI client accepts this additional options:
:live
:retries
:http_opts
:cache
Option :live
PaypalAPI client can be defined with live
option which is false
by default.
When live
is false
all requests will be send to the sandbox endpoints.
client = PaypalAPI::Client.new(
live: ENV['PAYPAL_LIVE'] == 'true'
# ...
)
Option :retries
This is a Hash with retries configuration.
By default retries are enabled, 4 retries with 0, 0.25, 0.75, 1.5 seconds delay.
Default config: {enabled: true, count: 4, sleep: [0, 0.25, 0.75, 1.5]}
.
New options are merged with defaults.
Please keep sleep
array same size as count
.
Retries happen on any network error, on 409, 429, 5xx response status code.
client = PaypalAPI::Client.new(
retries: {enabled: !Rails.env.test?, count: 5, sleep: [0, 0.25, 0.75, 1.5, 2]}
# ...
)
Option :http_opts
This are the options that are provided to the Net::HTTP.start
method,
like :read_timeout
, :write_timeout
, etc.
You can find full list of available options here https://docs.ruby-lang.org/en/master/Net/HTTP.html#method-c-start (Please choose you version of ruby).
By default it is an empty hash.
client = PaypalAPI::Client.new(
http_opts: {read_timeout: 30, write_timeout: 30, open_timeout: 30}
# ...
)
Option :cache
This option can be added to save webhook-validating certificates between
redeploys to validate webhooks offline. By default this gem has only in-memory
caching. The cache
object must respond to standard for caching #fetch
method.
By default it is nil
, so downloaded certificates will be downloaded again after
redeploys.
client = PaypalAPI::Client.new(
cache: Rails.cache
# ...
)
Automatic pagination
PayPal provides HATEOAS links in responses. This links can contain items with
rel=next
attribute. We request next pages using this links.
We have two specific methods:
-
Response#each_page
- iterates over each pageResponse
object -
Response#each_page_item(items_field_name)
- iterates over items on each page
Example:
PaypalAPI::WebhookEvents.list(page_size: 25).each_page do |response|
# ...
end
PaypalAPI::WebhookEvents.list(page_size: 25).each_page_item(:events) do |hash|
# ...
end
Webhoooks verification
Webhooks can be verified offline
or online.
Method PaypalAPI.verify_webhook(webhook_id:, headers:, raw_body:)
verifies webhook. It verifies webhook OFFLINE and fallbacks
to ONLINE if initial verification returns false to be sure you don't miss a
valid webhook.
When some required header is missing the
PaypalAPI::WebhooksVerifier::MissingHeader
error will be raised.
Example of a Rails controller with a webhook verification:
class Webhooks::PaypalController < ApplicationController
def create
# PayPal registered webhook ID for current URL
webhook_id = ENV['PAYPAL_WEBHOOK_ID']
headers = request.headers # must be a Hash
raw_body = request.raw_post # must be a raw String body
webhook_is_valid = PaypalAPI.verify_webhook(
webhook_id: webhook_id,
headers: headers,
raw_body: raw_body
)
if webhook_is_valid
handle_valid_webhook_event(body)
else
handle_invalid_webhook_event(webhook_id, headers, body)
end
head :no_content
end
end
Callbacks
Paypal::API client allows to subscribe to this callbacks:
-
:before
- Runs before request -
:after_success
- Runs after getting successful response -
:after_fail
- Runs after getting failed response (non-2xx) status code -
:after_network_error
- Runs after getting network error
Each callback receive request
and context
variables.
context
can be modified manually to save state between callbacks.
Arguments:
-
:before
- (request, context) -
:after_success
- (request, context, response) -
:after_fail
- (request, context, response) -
:after_network_error
- (request, context, error)
Context
argument contains retries_enabled
, retries_count
and
retry_number
options by default.
On :after_fail
and :after_network_error
there are also the
:will_retry
boolean option.
Examples:
PaypalAPI.client.add_callback(:before) do |request, context|
context[:request_id] = SecureRandom.hex(3)
context[:starts_at] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
PaypalAPI.client.add_callback(:after_success) do |request, context, response|
ends_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
duration = ends_at - context[:starts_at]
SomeLogger.debug(
'PaypalAPI success request',
method: request.method,
uri: request.uri.to_s,
duration: duration
)
end
PaypalAPI.client.add_callback(:after_fail) do |request, context, response|
SomeLogger.error(
'PaypalAPI request failed',
method: request.method,
uri: request.uri.to_s,
response_status: response.http_status,
response_body: response.http_body,
will_retry: context[:will_retry],
retry_number: context[:retry_number],
retry_count: context[:retry_count]
)
end
PaypalAPI.client.add_callback(:after_network_error) do |request, context, error|
SomeLogger.error(
'PaypalAPI network connection error',
method: request.method,
uri: request.uri.to_s,
error: error.message,
paypal_request_id: request.headers['paypal-request-id'],
will_retry: context[:will_retry],
retry_number: context[:retry_number],
retry_count: context[:retry_count]
)
end
Errors
All APIs can raise error in case of network error or non-2xx response status code.
Errors structure:
-
PaypalAPI::Error
-
PaypalAPI::Errors::NetworkError
- any network error -
PaypalAPI::Errors::FailedRequest
- any non-2xx code error- 400 -
PaypalAPI::Errors::BadRequest
- 401 -
PaypalAPI::Errors::Unauthorized
- 403 -
PaypalAPI::Errors::Forbidden
- 404 -
PaypalAPI::Errors::NotFound
- 405 -
PaypalAPI::Errors::MethodNotAllowed
- 406 -
PaypalAPI::Errors::NotAcceptable
- 409 -
PaypalAPI::Errors::Conflict
- 415 -
PaypalAPI::Errors::UnsupportedMediaType
- 422 -
PaypalAPI::Errors::UnprocessableEntity
- 429 -
PaypalAPI::Errors::TooManyRequests
- 5xx -
PaypalAPI::Errors::FatalError
- 500 -
PaypalAPI::Errors::InternalServerError
- 503 -
PaypalAPI::Errors::ServiceUnavailable
- 500 -
- 400 -
-
All errors have additional methods:
-
#paypal_request_id
- PayPal-Request-Id header sent with request -
#response
- Original response object, can be nil in case of NetworkError -
#request
- Original request object -
#error_name
- Original error name -
#error_message
- Original PayPal error:message
or error:description
-
#error_debug_id
- Paypal debug_id found in response -
#error_details
- Parsed PayPal error details found in parsed response (with symbolized keys)
begin
response = PaypalAPI.authorized_payments.capture(authorization_id, body: body)
rescue PaypalAPI::Error => error
YourLogger.error(
error,
context: {
paypal_request_id: error.paypal_request_id,
error_name: error.error_name,
error_message: error.error_message,
error_debug_id: error.error_debug_id,
error_details: error.error_details
}
)
# `error.request` and `error.response` methods can be used also
end
APIs
All API endpoints accept query:
, body:
and headers:
optional keyword
Hash parameters. So this parameters can be omitted in next docs.
-
query
- Hash with request query params -
body
- Hash with request body params -
headers
- Hash with request headers
Add tracking / Shipment Tracking docs
PaypalAPI::ShipmentTracking.add(body: body)
PaypalAPI::ShipmentTracking.update(id, body: body)
PaypalAPI::ShipmentTracking.show(id)
Catalog Products docs
PaypalAPI::CatalogProducts.create(body: body)
PaypalAPI::CatalogProducts.list
PaypalAPI::CatalogProducts.show(product_id)
PaypalAPI::CatalogProducts.update(product_id, body: body)
Disputes docs
PaypalAPI::Disputes.settle(id)
PaypalAPI::Disputes.update_status(id)
PaypalAPI::Disputes.escalate(id)
PaypalAPI::Disputes.accept_offer(id)
PaypalAPI::Disputes.list
PaypalAPI::Disputes.provide_supporting_info(id)
PaypalAPI::Disputes.show(id)
PaypalAPI::Disputes.update(id)
PaypalAPI::Disputes.deny_offer(id)
PaypalAPI::Disputes.make_offer(id)
PaypalAPI::Disputes.appeal(id)
PaypalAPI::Disputes.provide_evidence(id)
PaypalAPI::Disputes.acknowledge_return_item
PaypalAPI::Disputes.send_message(id)
-
PaypalAPI::Disputes.accept_claim
(id)
Identity docs
User Info
PaypalAPI::UserInfo.show
User Management
PaypalAPI::Users.create
PaypalAPI::Users.list
PaypalAPI::Users.update(id)
PaypalAPI::Users.show(id)
PaypalAPI::Users.delete(id)
Invoicing (Invoices & Invoice Templates) docs
PaypalAPI::Invoices.create
PaypalAPI::Invoices.list
PaypalAPI::Invoices.send_invoice(invoice_id)
PaypalAPI::Invoices.remind(invoice_id)
PaypalAPI::Invoices.cancel(invoice_id)
PaypalAPI::Invoices.record_payment(invoice_id)
PaypalAPI::Invoices.delete_payment(invoice_id)
PaypalAPI::Invoices.record_refund(invoice_id)
PaypalAPI::Invoices.delete_refund(invoice_id)
PaypalAPI::Invoices.generate_qr_code(invoice_id)
PaypalAPI::Invoices.generate_invoice_number
PaypalAPI::Invoices.show(invoice_id)
PaypalAPI::Invoices.update(invoice_id)
PaypalAPI::Invoices.delete(invoice_id)
PaypalAPI::Invoices.search
PaypalAPI::InvoiceTemplates.create
PaypalAPI::InvoiceTemplates.list
PaypalAPI::InvoiceTemplates.show(template_id)
PaypalAPI::InvoiceTemplates.update(template_id)
PaypalAPI::InvoiceTemplates.delete(template_id)
Orders docs
PaypalAPI::Orders.create
PaypalAPI::Orders.show(order_id)
PaypalAPI::Orders.update(order_id)
PaypalAPI::Orders.confirm(order_id)
PaypalAPI::Orders.authorize(order_id)
PaypalAPI::Orders.capture(order_id)
PaypalAPI::Orders.track(order_id)
PaypalAPI::Orders.update_tracker(order_id, tracker_id)
Orders V1 (Deprecated on PayPal) docs
PaypalAPI::OrdersV1.create
PaypalAPI::OrdersV1.show(order_id)
PaypalAPI::OrdersV1.cancel(order_id)
PaypalAPI::OrdersV1.pay(order_id)
PartnerReferrals docs
PaypalAPI::PartnerReferrals.create
PaypalAPI::PartnerReferrals.show(partner_referral_id)
Payment Experience Web Profiles docs
PaypalAPI::PaymentExperienceWebProfiles.create
PaypalAPI::PaymentExperienceWebProfiles.list
PaypalAPI::PaymentExperienceWebProfiles.show
PaypalAPI::PaymentExperienceWebProfiles.replace
PaypalAPI::PaymentExperienceWebProfiles.update
PaypalAPI::PaymentExperienceWebProfiles.delete
Payment Method Tokens / Setup Tokens docs
PaypalAPI::PaymentTokens.create
PaypalAPI::PaymentTokens.list
PaypalAPI::PaymentTokens.show(id)
PaypalAPI::PaymentTokens.delete(id)
PaypalAPI::SetupTokens.create
PaypalAPI::SetupTokens.show(setup_token_id)
Payments (Authorized Payments, Captured Payments, Refunds) docs
PaypalAPI::AuthorizedPayments.show(authorization_id)
PaypalAPI::AuthorizedPayments.capture(authorization_id)
PaypalAPI::AuthorizedPayments.reauthorize(authorization_id)
PaypalAPI::AuthorizedPayments.void(authorization_id)
PaypalAPI::CapturedPayments.show(capture_id)
PaypalAPI::CapturedPayments.refund(capture_id)
PaypalAPI::Refunds.show(refund_id)
Payouts / Payout Items docs
PaypalAPI::Payouts.create
PaypalAPI::Payouts.show(payout_id)
PaypalAPI::PayoutItems.show(payout_item_id)
PaypalAPI::PayoutItems.cancel(payout_item_id)
Referenced Payouts / Referenced Payout Items docs
PaypalAPI::ReferencedPayouts.create
PaypalAPI::ReferencedPayouts.show(payouts_batch_id)
PaypalAPI::ReferencedPayoutItems.create
PaypalAPI::ReferencedPayoutItems.show(payouts_item_id)
Subscriptions / Subscription Plans docs
PaypalAPI::Subscriptions.create
PaypalAPI::Subscriptions.show(id)
PaypalAPI::Subscriptions.update(id)
PaypalAPI::Subscriptions.revise(id)
PaypalAPI::Subscriptions.suspend(id)
PaypalAPI::Subscriptions.cancel(id)
PaypalAPI::Subscriptions.activate(id)
PaypalAPI::Subscriptions.capture(id)
PaypalAPI::Subscriptions.transactions(id)
PaypalAPI::SubscriptionPlans.create
PaypalAPI::SubscriptionPlans.list
PaypalAPI::SubscriptionPlans.show(plan_id)
PaypalAPI::SubscriptionPlans.update(plan_id)
PaypalAPI::SubscriptionPlans.activate(plan_id)
PaypalAPI::SubscriptionPlans.deactivate(plan_id)
PaypalAPI::SubscriptionPlans.update_pricing(plan_id)
Transaction Search docs
PaypalAPI::TransactionSearch.list_transactions
PaypalAPI::TransactionSearch.list_all_balances
Webhooks Management docs
PaypalAPI::Webhooks.create
PaypalAPI::Webhooks.list
PaypalAPI::Webhooks.show(webhook_id)
PaypalAPI::Webhooks.update(webhook_id)
PaypalAPI::Webhooks.delete(webhook_id)
PaypalAPI::Webhooks.event_types(webhook_id)
PaypalAPI::Webhooks.verify
PaypalAPI::WebhookEvents.available
PaypalAPI::WebhookEvents.list
PaypalAPI::WebhookEvents.show(event_id)
PaypalAPI::WebhookEvents.resend(event_id)
PaypalAPI::WebhookEvents.simulate
PaypalAPI::WebhookLookups.create
PaypalAPI::WebhookLookups.list
PaypalAPI::WebhookLookups.show(webhook_lookup_id)
PaypalAPI::WebhookLookups.delete(webhook_lookup_id)
Development
rubocop
rspec
mdl README.md CHANGELOG.md RELEASE.md
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/aglushkov/paypal-rest-api.
License
The gem is available as open source under the terms of the MIT License.