Swiftpack.co - Package - skelpo/PayPal


A Vapor integration for PayPal's REST API. Mostly incomplete, but good for basic payments


Add the package to your manifest's dependencies and to any targets you want access to the package in:

.package(url: "https://github.com/skelpo/PayPal.git", from: "0.1.0")

Run vapor update or swift package update and regenerate your Xcode project if you are developing with Xcode.


The PayPal package uses two environment variables to authenticate your app with PayPal, PAYAPL_CLIENT_ID and PAYPAL_CLIENT_SECRET. You can access these keys by going to your developer dashboard and selecting the app you want to use the package for:

API keys in PayPal developer dashboard

You can then set these keys in Xcode's environment variables or in the command-line with export <KEY-NAME>=<KEY-VALUE>.

Note: If you set the keys in Xcode, they will need to be re-added every time you regenerate the project. If you set the keys in the command-line, you will need to close Xcode and regenerate your project.

You can log request/response pairs that result in an API error if you are in debug mode and set the env var PAYPAL_LOG_API_ERROR to TRUE.

In your configure.swift file, you will need to import the PayPal module and register the PayPalProvider with your app's services:

try services.register(PayPalProvider())

Using the Package


To create and execute a payment, start by creating a Payment object to send to PayPal:

let address = try Address(
    recipientName: "Ira Harding",
    defaultAddress: true,
    line1: "578 Wild Wood",
    line2: nil,
    city: "New Haven",
    state: "CN",
    countryCode: "US",
    postalCode: "79812",
    phone: nil,
    type: nil
let item = try Payment.Item(
    quantity: "3",
    price: "39.00",
    currency: .usd,
    sku: "8EFAFEF3-72D2-4E5C-85EC-C14BA2F9D997",
    name: "Plum Pudding",
    description: "With sugar an inch thick",
    tax: "8.00"
let details = try DetailedAmount.Detail(
    subtotal: "117.00",
    shipping: "15.00",
    tax: "8.00",
    handlingFee: "10.00",
    shippingDiscount: nil,
    insurance: nil,
    giftWrap: nil
let amount = try DetailedAmount(currency: .usd, total: "150.00", details: details)
let items = try Payment.ItemList(
    items: [item],
    address: nil,
    phoneNumber: nil
let transaction = try Payment.Transaction(
    amount: amount,
    payee: Payee(email: "payee@example.com", merchant: nil, metadata: nil),
    description: nil,
    payeeNote: "Thanks for paying for the order!",
    custom: nil,
    invoice: nil,
    softDescriptor: nil,
    payment: .unrestricted,
    itemList: items,
    notify: "https://example.com/notify"
let payment = try Payment(
    intent: .sale,
    payer: PaymentPayer(method: .paypal, funding: nil, info: nil),
    context: nil,
    transactions: [transaction],
    experience: nil,
    payerNote: "Thanks for ordering!",
    redirects: Redirects(return: "https://example.com/approved", cancel: "https://example.com/canceled")

Then get the registered Payments service from the container you have access to. This will be a Request object if you are in a route handler:

let payments = try request.make(Payments.self)

You can then create the payment with PayPal:

let payment = try payments.create(payment: payment)

This method returns a future with the payment that was created. In the payment is an array of URIs that are related to the payment you just created. One of them is for letting the buyer authorize the payment, called approval_url. You send this as a redirect response back to the client:

return payment.map { payment -> Response in
    guard let uri = result.links?.filter({ $0.rel == "approval_url" }).first?.href else {
        throw Abort(.failedDependency, reason: "Cannot get payment approval URL")
    return req.redirect(to: uri)

When this response is sent to the client, they will be redirected to the payment approval page on PayPal. Depending on what they do then, they will be redirected to one of the links that was registered with the payment (https://example.com/approved or https://example.com/canceled in our case). These should be URIs for routes in your app.

If the payment is approved by the buyer, they will be redirected back to your 'approved' route. Here you can get the payment ID and the payer ID from the URI's query-strings and execute the payment:

func approved(_ request: Request)throws -> Future<SOME-TYPE> {
    let paymentID = try request.query.get(String.self, at: "paymentId")
    let payerID = try request.query.get(String.self, at: "PayerID")
    let payments = try request.make(Payments.self)
    let executor = try Payment.Executor(payer: payerID, amounts: [ DetailedAmount(currency: .usd, total: "150.00", details: nil) ])
    payments.execute(payment: paymentID, with: executor)

Make sure the amount in the executor object is the proper currency and equals the amount of the payment you created.


Stars: 9
Help us keep the lights on


0.3.2 - Jan 23, 2019

Padded Currency Values

Format money decimal values with trailing 0 characters, so there are as many fraction digits as the currency exponent number:

85.8 USD => 85.80
10 USD => 10.00
10 XXX => 10

This is because some endpoint returned validation errors if this padding wasn't there.

0.3.1 - Jan 23, 2019

Decoding Int and String for quantity Properties

The Order.Item.quantity and Payment.Item.quantity properties are documented as strings in the PayPal API documentation, but you actually get ints back when you create a payment or order. Because of this, we attempt to decode an int, and fall back to a string if we get a type mismatch decoding error.

0.3.0 - Jan 23, 2019

Failing Properties

The biggest change in this release is that properties themselves handle validations, instead of the parent type. You will notice that quite a few initializers don't accept the primitive types like String or Int, but instead something like Failable<Int, ...>. You can wrap the those value in .init(value) and everything should work again:

try Name(
    prefix: .init("Mr."), 
    given: .init("Caleb"), 
    surname: .init("Kleveter"), 
    middle: .init("J"), 
    suffix: .init(nil), 
    full: .init("Caleb J Kleveter")

Instead of setting the properties with the .set method like you used to:

try name.set(\.prefix <~ nil)`

You can use plain old property setting:

try name.prefix <~ nil

The Environment.domain property is now public, so you can easily get the API domain for the endpoints you are using.

0.2.0 - Dec 3, 2018

No Environmental Variation

  • Pass client ID and secret into PayPalProvider initializer for flexibility during app configuration, instead of requiring environment variables.
  • Remove all ID assignment from initializers.
  • Made some types into sub-types, i.e. AgreementState => Agreement.State.
  • Replaced Money and Amount structs with generic AmountType struct.
  • Created Country struct which encodes/decodes using a single value container to handle country code validation.
  • Created a Province enum that represents the states or provinces a country can have.
  • Extended HTTPHeaderName with custom PayPal headers.

0.1.0 - Oct 3, 2018

You'll Pay. No, the Buyer Will.

A mostly incomplete and partially broken interface for PayPal's REST API, but hey, that's what pre-releases are for, right?

The only thing that we know works are basic payments. You can read about how to implement that in the README.

Be sure to leave feed back and suggestions for the next releases!