NAV Navbar
Logo

Overview

Production Request

curl https://api.payload.co \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
import payload as pl
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'
require 'payload'
Payload.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'
<?php
require_once('vendor/autoload.php');
use Payload\API as pl;
pl::$api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE';
?>
var pl = require('payload-api');
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'
using Payload;
pl.api_key = "secret_key_3bW9JMZtPVDOfFNzwRdfE";

Test Request

curl https://api.payload.co \
    -u test_secret_key_dwjVajaCQKO4RJuV:
import payload as pl
pl.api_key = 'test_secret_key_dwjVajaCQKO4RJuV'
require 'payload'
Payload.api_key = 'test_secret_key_dwjVajaCQKO4RJuV'
<?php
require_once('vendor/autoload.php');
use Payload\API as pl;
pl::$api_url = 'test_secret_key_dwjVajaCQKO4RJuV';
?>
var pl = require('payload-api');
pl.api_key = 'test_secret_key_dwjVajaCQKO4RJuV'
using Payload;
pl.api_key = "test_secret_key_dwjVajaCQKO4RJuV";

Payload’s APIs use a RESTful API design with enhanced features like hierarchical object design and API Relational Model wrappers. You can explore the architecture in the API Design section.

Tutorials

API Architecture

Payment Stack

Install Packages

To use one of our language-specific API packages, select one of the following for instructions:

pip install payload-api

gem 'payload-api', '~> 0.5.6'

gem install payload-api

composer require payload/payload-api

npm install payload-api

nuget install payload-api

Test Environment

Test API Keys

You’ll find a set of production and test API keys in your dashboard. The test API keys give you access to a private test API environment where you can run simulated API requests to test and integrate Payload into your application. The test API keys start with test_secret_key and test_client_key.

Test Dashboard Access

To switch your dashboard to the test environment and access, manage, and review your test data, there is a toggle on the sidebar to switch between environments.


API Design

Payload’s API design incorporates a variety of concepts to create a simple, robust, and intuitive platform. The following sections will take you through the general API design concepts inherited in all of our API components.

Authentication

# Curl supports HTTP basic authentication with the `-u` command
# To use the `-u` command, add a `:` onto the API key.
curl "https://api.payload.co/transactions" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
import payload as pl
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'
require "payload"
Payload.api_key = "secret_key_3bW9JMZtPVDOfFNzwRdfE"
<?php
require_once('vendor/autoload.php');
use Payload\API as pl;
pl::$api_key = "secret_key_3bW9JMZtPVDOfFNzwRdfE";
?>
var pl = require('payload-api')
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'
using Payload;
pl.api_key = "secret_key_3bW9JMZtPVDOfFNzwRdfE";

Authentication with Payload uses a simple API secret key. The secret key is passed as the username field in an HTTP Basic Auth header. Your secret key is available from your dashboard in the API Keys section under settings.

Operations

Below are the HTTP methods for performing operations on the API objects. Operations can be executed on a single or list of known objects by their object id, or a group of objects based on query parameters by using Conditional Queries.

Method Description Example
GET Select one or many objects GET /transactions
POST Create one or many objects POST /transactions
PUT Update one or many objects PUT /transactions
DELETE Delete one or many objects DELETE /transactions

Selecting Objects

Select individual or multiple objects using the HTTP method GET.

Select an Individual Object

curl "https://api.payload.co/transactions/3bW9JN4CCJFkc9ukQ0umW" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
transaction = pl.Transaction.get('3bW9JN4CCJFkc9ukQ0umW')
transaction = Payload::Transaction.get('3bW9JN4CCJFkc9ukQ0umW')
<?php
$transaction = Payload\Transaction::get('3bW9JN4CCJFkc9ukQ0umW');
?>
pl.Transaction.get('3bW9JN4CCJFkc9ukQ0umW')
    .then(function(transaction){})
var transaction = pl.Transaction.get("3bW9JN4CCJFkc9ukQ0umW");

Select a List of Objects

curl "https://api.payload.co/transactions/?type=payment&status=processed" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
payments = pl.Payment.filter_by( status='processed' ).all()
payments = Payload::Payment.select( status: "processed" )
<?php
$payments = Payload\Transaction::filter_by(
    array('status'=>'processed', 'type'=>'payment'))->all();
?>
pl.Payment.filter_by({ status: 'processed' })
    .then(function(payments){})
var payments = pl.Payment.filter_by(new { status="processed" }).all();

Request

GET https://api.payload.co/transactions/{id}

Request Multiple Objects

GET https://api.payload.co/transactions/?attr=value

Filter results using conditional queries.


Creating Objects

Create individual or multiple objects using the HTTP method POST.

Create an Individual Object

curl "https://api.payload.co/accounts/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d email="matt.perez@example.com" \
    -d name="Matt Perez" \
    -d type="customer"
account = pl.Customer.create(
    email='matt.perez@example.com',
    name='Matt Perez'
)
account = Payload::Customer.create(
    email: 'matt.perez@example.com',
    name: 'Matt Perez'
)
<?php
$account = Payload\Account::create(array(
    'email'=>'matt.perez@example.com',
    'name'=>'Matt Perez',
    'type'=>'customer'
));
?>
pl.Customer.create({
    email: 'matt.perez@example.com',
    name: 'Matt Perez'
}).then(function(customer) {});
var customer = pl.Customer.create(new {
    email="matt.perez@example.com",
    name="Matt Perez"
});

Create Multiple Objects

curl "https://api.payload.co/accounts/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d [0]email="matt.perez@example.com" \
    -d [0]name="Matt Perez" \
    -d [0]type="customer" \
    -d [1]email="andrea.kearney@example.com" \
    -d [1]name="Andrea Kearney" \
    -d [1]type="customer"
accounts = pl.create([
    pl.Customer(
        email='matt.perez@example.com',
        name='Matt Perez'
    ),
    pl.Customer(
        email='andrea.kearney@example.com',
        name='Andrea Kearney'
    )
])
accounts = Payload::create([
    Payload::Customer.new(
        email: 'matt.perez@example.com',
        name: 'Matt Perez'
    ),
    Payload::Customer.new(
        email: 'andrea.kearney@example.com',
        name: 'Andrea Kearney'
    )
])
<?php
$accounts = Payload\Account::create(array(
    new Payload\Account(array(
        'email'=>'matt.perez@example.com',
        'name'=>'Matt Perez'
        'type'=>'customer'
    )),
    new Payload\Account(array(
        'email'=>'andrea.kearney@example.com',
        'name'=>'Andrea Kearney'
        'type'=>'customer'
    ))
));
?>
pl.create([
    new pl.Customer({
        email: 'matt.perez@example.com',
        name: 'Matt Perez'
    }),
    new pl.Customer({
        email: 'andrea.kearney@example.com',
        name: 'Andrea Kearney'
    })
]).then(function(customers) {});
var customers = pl.create(new[]{
    new pl.Account(new{
        email="matt.perez@example.com",
        name="Matt Perez"
    }),
    new pl.Account(new{
        email="andrea.kearney@example.com",
        name="Andrea Kearney"
    })
});

Create Request

POST https://api.payload.co/accounts

Request Body

{
    "email": "matt.perez@example.com",
    "name": "Matt Perez"
}

Create Multiple Objects

POST https://api.payload.co/accounts

Request Body

{
    "object": "list",
    "values": [
        {
            "email": "matt.perez@example.com",
            "name": "Matt Perez"
        },
        {
            "email": "matt.perez@example.com",
            "name": "Matt Perez"
        }
    ]
}


Updating Objects

Update individual or multiple objects using the HTTP method PUT.

Update an Individual Object

curl "https://api.payload.co/accounts/3bW9JMoHcb0u4ZmM7FZ32" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X PUT \
    -d email="matt.perez@newwork.com"
account.update( email="matt.perez@newwork.com" )
account.update( email: 'matt.perez@example.com' )
<?php
$account->update(array( 'email'=>'matt.perez@example.com' ));
?>
account.update({ email: "matt.perez@newwork.com" })
account.update(new { email="matt.perez@newwork.com" });

Update Objects by Query

curl "https://api.payload.co/accounts/?email=matt.perez@example.com&type=customer&mode=query" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X PUT \
    -d email="matt.perez@newwork.com"
accounts = pl.Customer\
    .filter_by(email='matt.perez@example.com')\
    .update(email='matt.perez@newwork.com')
accounts = Payload::Customer.select(
    email: 'matt.perez@example.com',
    update_all: {
        'email' => 'matt.perez@newwork.com'
    }
)
<?php
$accounts = Payload\Account::filter_by(array(
        'email'=>'matt.perez@example.com'
    ))->update(array(
        'email'=>'matt.perez@newwork.com'
    ));
?>
pl.Customer
    .filter_by({ email: 'matt.perez@example.com' })
    .update({ email: 'matt.perez@newwork.com' })
    .then(function(customers){})
var customers = pl.Customer.filter_by(new {
    email="matt.perez@example.com"
}).update(new {
    email="matt.perez@newwork.com"
});

Update Multiple Objects

curl "https://api.payload.co/accounts/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X PUT \
    -d [0][id]="3bW9JMoHcb0u4ZmM7FZ32" \
    -d [0][email]="matt.perez@newwork.com" \
    -d [1][id]="3bW9JMojd17Dm4LSuOxIu" \
    -d [1][email]="andrea.kearney@newwork.com"
accounts = pl.update([
    [ account1, { 'email': 'matt.perez@newwork.com' } ],
    [ account2, { 'email': 'andrea.kearney@newwork.com' } ]
])
accounts = Payload::update([
    [ account1, { 'email': 'matt.perez@newwork.com' } ],
    [ account2, { 'email': 'andrea.kearney@newwork.com' } ]
])
pl.update([
    [ account1, { email: 'matt.perez@newwork.com' } ],
    [ account2, { email: 'andrea.kearney@newwork.com' } ]
])
var customers = pl.update(new object[]{
    new object[]{ account1, new { email="matt.perez@newwork.com" } },
    new object[]{ account2, new { email="andrea.kearney@newwork.com" } }
});

Update Request

PUT https://api.payload.co/accounts/{id}

Request Body

{
    "email": "matt.perez@newwork.com"
}

Update Query

PUT https://api.payload.co/accounts/?email=matt.perez@example.com

Request Body

{
    "email": "matt.perez@newwork.com"
}

Update Multiple Objects

PUT https://api.payload.co/accounts

Request Body

{
    "object": "list",
    "values": [
        {
            "id": "3bW9JN4OEmUpEezSq6TJY",
            "email": "matt.perez@newwork.com"
        },
        {
            "id": "3bW9JN4OmbDd2Kg8bdK4W",
            "email": "andrea.kearney@newwork.com"
        }
    ]
}


Deleting Objects

Create individual or multiple objects using the HTTP method DELETE.

Delete an Individual Object

curl "https://api.payload.co/accounts/3bW9JMojd17Dm4LSuOxIu" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X DELETE
account.delete()
account.delete()
<?php
$account->delete();
?>
account.delete()
account.delete();

Delete Objects by Query

curl "https://api.payload.co/accounts/?email=matt.perez@example.com&mode=query" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X DELETE
deleted_accnts = pl.Customer\
    .filter_by(email='matt.perez@example.com')\
    .delete()
deleted_accnts = Payload::Customer.select(
    email: 'matt.perez@example.com',
    delete_all: true
)
<?php
$deleted_accnts = Payload\Account::filter_by(array(
        'email'=>'matt.perez@example.com'
    )).delete();
?>
pl.Customer
    .filter_by({email: 'matt.perez@example.com'})
    .delete()
    .then(function(deleted_accnts){})
var deleted_accnts = pl.Customer
    .filter_by(new { email="matt.perez@example.com" })
    .delete();

Delete Multiple Objects

curl "https://api.payload.co/accounts/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X DELETE \
    -d [0][id]="3bW9JN4OEmUpEezSq6TJY" \
    -d [1][id]="3bW9JN4OmbDd2Kg8bdK4W"
deleted_accnts = pl.delete([
    account1,
    account2
])
deleted_accnts = Payload::delete_all([
    account1,
    account2
])
pl.delete_all([
    account1,
    account2
])
pl.delete(new[]{
    account1,
    account2
});

Delete Request

DELETE https://api.payload.co/accounts/{id}

Delete Query

DELETE https://api.payload.co/accounts/?email=matt.perez@example.com

Delete Multiple Objects

DELETE https://api.payload.co/accounts

Request Body

{
    "object": "list",
    "values": [
        {
            "id": "3bW9JN4OEmUpEezSq6TJY"
        },
        {
            "id": "3bW9JN4OmbDd2Kg8bdK4W"
        }
    ]
}


Errors

Individual Object

{
    "object": "error",
    "error_type": "InvalidAttributes",
    "details": {
        "secret_key": "Must be valid number"
    }
}

Multiple Objects

{
    "object": "error",
    "error_type": "InvalidAttributes",
    "details": {
        "object": "list",
        "values": [
            {
                "amount": "Must be a valid number"
            },
            {
                "payment_method_id": "Required"
            }
        ]
    }
}

try {
    var cust = pl.Customer.get("your_customer_id");
    cust.charge(new{ amount=100 });
} catch ( pl.NotFound e ) {
    Console.WriteLine("Customer doesn't exist");
} catch ( pl.InvalidAttributes e ) {
    // See full error
    Console.WriteLine(e.json());

    // Loop over invalid attributes
    foreach (JProperty property in e.details) {
        Console.WriteLine(property.Name+": "+property.Value);
    }
}

Errors are raised under the related HTTP 4xx Client Error or 5xx Server Error HTTP status codes. The body of the response includes more information.

Standard Error Codes

Object Operation Validation Issue

Code Name Meaning
400 Bad Request A bad request is most commonly a validation issue due to missing or malformed attributes. More information on the issue can be found in the body of the response.

Issues With Access & Permissions

Code Name Meaning
401 Unauthorized An unauthorized response is caused by an invalid or expired API secret key.
403 Forbidden A forbidden response is caused when a request is made to a resource that is not in scope of the API secret key that was used.

Requesting Nonexistent Resources

Code Name Meaning
404 Not Found A not found response is used when requesting a URL path that does not exist. If the URL path is the full object resource path, the object has likely been deleted.

API Rate Limit Reached

Code Name Meaning
429 Too Many Requests A too many requests response is raised when the API secret key, IP, or other limiting factor has reached the preset usage limit.

Internal Issues & Downtime

Code Name Meaning
500 Internal Server Error An internal server error is raised if there was an unexpected issue while processing your request.
503 Service Unavailable A service unavailable error is raised if the Payload service you’re requesting has been temporarily brought offline for maintenance.

Hierarchical Object Design

curl "https://api.payload.co/invoices/3bW9JNCxdMPBUV510Gf9E" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
invoice = pl.Invoice.get('3bW9JNCxdMPBUV510Gf9E')
print(invoice.json())
invoice = Payload::Invoice.get('3bW9JNCxdMPBUV510Gf9E')
puts invoice.json()
<?php
$invoice = Payload\Invoice::get('3bW9JNCxdMPBUV510Gf9E');
echo($invoice->json());
?>
pl.Invoice.get('3bW9JNCxdMPBUV510Gf9E')
    .then(function( invoice ){
        console.log(invoice.json())
    })
var invoice = pl.Invoice.get("3bW9JNCxdMPBUV510Gf9E");
Console.WriteLine( invoice.json() );
{
  "id": "3bW9JNCxdMPBUV510Gf9E",
  "object": "invoice",
  "amount": 1000,
  "description": "INV - 401",
  "due_date": "2019-02-01",
  "type": "membership",
  "items": [{
    "id": "3bW9JND5cyoQVPVcCwOkC",
    "object": "line_item",
    "amount": 100,
    "date_incurred": "2019-01-10 20:41:40",
    "description": "Item 1",
    "invoice_id": "3bW9JNCxdMPBUV510Gf9E",
    "type": "item",
    "entry_type": "charge"
  },{
    "id": "3bW9JND5l6QlItvoXYUXA",
    "object": "line_item",
    "amount": -100,
    "description": "Payment",
    "invoice_id": "3bW9JNCxdMPBUV510Gf9E",
    "entry_type": "payment",
    "payment": {
      "id": "3bW9JMonPXbEWWcNYrIm0",
      "object": "transaction",
      "account_id": "3bW9JMoMCmrCjZByd1Wt6",
      "amount": 100,
      "processing_id": "3bW9JMoN07ApUDouuPdB2",
      "description": "INV - 401 Payment",
      "fee": 1.95,
      "created_at": "2019-02-20 17:21:15",
      "payment_method": {},
      "payment_method_id": "3bW9JMoQUQiZaEV8TgPUO",
      "status": "processed",
      "type": "payment"
    }
  }]
}

Objects are often associated to other objects in the Payload API. When requesting an object like an invoice, any related objects, like line items, wil be included as nested objects within the parent invoice object’s response. This works recursively to create a hierarchical representation of an object’s relationships.

Access Nested Objects Directly

Nested objects can be accessed and manipulated directly either through an embedded object resource path, or, if the object id is known, you can access and modify it directly through the object’s full resource path.

Embedded Object Resource Path

https://api.payload.co/invoices/3bW9JNCxdMPBUV510Gf9E/items

Full Object Resource Path

https://api.payload.co/line_items/3bW9JND5cyoQVPVcCwOkC

Changes to a Nested Object Tree

You can make changes to nested objects by editing the nested object tree in an update operation.

Creating a New Nested Object

New objects added into a nested object tree without an id attribute will get created on either a create or update request.

Updating an Existing Nested Object

To update attributes of a nested object, pass the updated fields along with the object’s id in the correct nested position within the parent object in an update operation.

Deleting an Existing Nested Object

Remove the nested object from the parent’s object tree in an update operation and the object will be deleted.


API Relational Model

# An ARM api request representation
# requesting monthly aging receivables
results = pl.Invoice.select(
        attr.due_date.month(),
        attr.due_date.year(),
        attr.amount_due.sum()
    ).filter_by(
        attr.status == 'overdue',
        attr.due_date <= datetime.now() - timedelta(days=30)
    ).group_by(
        attr.due_date.month(),
        attr.due_date.year()
    ).all()
// An ARM api request representation
// requesting monthly aging receivables
results = pl.Invoice.select(
        pl.attr.due_date.month(),
        pl.attr.due_date.year(),
        pl.attr.amount_due.sum()
    ).filter_by(
        pl.attr.status.eq('overdue'),
        pl.attr.due_date.ge(new Date(new Date()-30*3600))
    ).group_by(
        pl.attr.due_date.month(),
        pl.attr.due_date.year()
    ).then(function(results){})
// An ARM api request representation
// requesting monthly aging receivables
var results = pl.Invoice.select(
        pl.attr.due_date.month(),
        pl.attr.due_date.year(),
        pl.attr.amount_due.sum()
    ).filter_by(
        pl.attr.status.eq("overdue"),
        pl.attr.due_date.le(DateTime.Now.AddDays(30))
    ).group_by(
        pl.attr.due_date.month(),
        pl.attr.due_date.year()
    ).all();

API Relational Model, or ARM, is a software library abstraction of the underlying API resources in much the same way ORMs abstract database requests. ARM combines functionality like the query language, attribute functions, and object attribute selection. The ARM representations are then compiled into API requests and executed.

The attr object

The attr object is a dot notation interface for any object attributes. Attribute functions can be called as methods of the attributes.

Using select to select attributes

The select class method can be used to specify what attributes you want in your API response. Below are the common ways to use select:

Get Additional Attributes

select can be used to request additional attributes that are not included by default for the object (more details on these additional attributes can be found in the Object Reference). To request all default attributes for an object, use *pl.Transaction wildcard operator.

Select Specific Attributes

Return a specific set of attributes by passing the desired attributes as parameters to select.

Calling Functions

Use a function to modify the value of an attribute passed to select by calling the method on the attr representation being passed to select.

Querying with filter_by

To filter the results of an API request, you can construct conditional statements with attr attributes and pass those to filter_by. filter_by uses lazy-loading and does not execute API requests immediately letting you chain filter_by calls or other non-terminal methods.

Executing the Request

Requests aren’t immediately executed when using select, filter_by, and group_by. Requests wait for an execution trigger method or are iterated. The ways in which a request execution is triggered are by using method all, first, update, or delete. Request execution is also triggered by iterating over the request or slicing the request results.


Conditional Queries

Queries can be made using query string parameters on any object or nested object attribute. You can specify advanced conditional operators like greater-than or boolean on any parameter as described below.

Select, Update, and Delete operations can be performed on query results.

Less-Than Conditional

curl "https://api.payload.co/transactions/?type=payment&amount=<200" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
payments = pl.Payment.filter_by(
    pl.attr.amount < 200
).all()
transactions = Payload::Payment.select(
    amount: '<200'
)
<?php
$transactions = Payload\Transactions::filter_by(
    pl->attr()->type->eq('payment'),
    pl->attr()->amount->lt(200)
);
?>
pl.Payment.filter_by(
        pl.attr.amount.lt(200)
    ).then(function( transactions ){
    })
var payments = pl.Payment.select(
    pl.attr.amount.lt(200)
);

Combining Less-Than and Greater-Than

curl "https://api.payload.co/transactions/?type=payment&amount=>100&amount=<200" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
payments = pl.Payment.filter_by(
    pl.attr.amount > 100,
    pl.attr.amount < 200
).all()
payments = Payload::Payment.select(
    amount: [ '>100', '<200' ]
)
<?php
$payments = Payload\Transaction::filter_by(
    pl->attr()->amount->gt(100),
    pl->attr()->amount->lt(200)
));
?>
pl.Payment.filter_by(
        pl.attr.amount.gt(100),
        pl.attr.amount.lt(200)
    ).then(function( transactions ){
    })
var payments = pl.Payment.select(
    pl.attr.amount.gt(100),
    pl.attr.amount.lt(200)
);

OR Conditional

curl "https://api.payload.co/transactions/?type=payment&risk_status=denied|in_review" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
payments = pl.Payment.filter_by(
    (pl.attr.risk_flag == 'denied') | (pl.attr.risk_flag == 'in_review')
).all()
payments = Payload::Payment.filter_by(
    risk_flag: 'denied|in_review'
)
<?php
$payments = Payload\Transaction::filter_by(array(
    'risk_flag' => 'denied|in_review'
))->all();
?>
pl.Payment.filter_by(
        pl.attr.risk_flag.eq('denied','in_review')
    ).then(function( payments ){
    })
var payments = pl.Payment.filter_by(
    pl.attr.risk_flag.eq("denied","in_review")
).all();

Like Conditional

payments = pl.Payment.filter_by(
    pl.attr.description.contains('INV -')
).all()
pl.Payment.filter_by(
    pl.attr.description.contains('INV -')
).then(function(payments) {
})
var payments = pl.Payment.filter_by(
    pl.attr.description.contains("INV -")
).all();

Combined Conditional Example

payments = pl.Payments.filter_by(
    pl.attr.amount > 100,
    pl.attr.amount < 200,
    (pl.attr.risk_status == 'denied') | (self.status == 'in_review'),
    pl.attr.description.contains('INV -'),
    pl.attr.created_at.date() != datetime.date(2019,2,1)
).all()
payments = pl.Payments.filter_by(
    pl.attr.amount.gt(100),
    pl.attr.amount.lt(200),
    pl.attr.risk_status.eq('denied', 'in_review'),
    pl.attr.description.contains('INV -'),
    pl.attr.created_at.date().ne(new Date(2019,1,1))
).all()
var payments = pl.Payments.filter_by(
    pl.attr.amount.gt(100),
    pl.attr.amount.lt(200),
    pl.attr.risk_status.eq("denied", "in_review"),
    pl.attr.description.contains("INV -"),
    pl.attr.created_at.date().ne(new DateTime(2019,2,1))
).all();

Nested Object Parameters

Nested objects or lists can be referenced as a query string parameter using the follow syntax:

Nested Object Parameters

?object[attr]=value

2nd Degree Nested Object Parameters

?object[subobject][attr]=value

Nested List

?list[0]=value

Nested List of Objects

?list[0][attr]=value

Query Operators

Conditional Description
attr=value Equal
attr=!value Not Equal
attr=<value Less-Than
attr=<=value Less-Than or Equal
attr=>value Greater-Than
attr=>=value Greater-Than or Equal
attr=<value1|>value2 Or
attr=!value&attr=>value And
attr=?*value* Like

Paging & Ordering Results

Example of Paging & Ordering Results

curl "https://api.payload.co/accounts/?type=customer&limit=5&offset=15&order_by=created_at" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
customers = pl.Customer.order_by( pl.attr.created_at )[5:15]
customers = Payload::Customer.select(
    order_by: 'created_at',
    limit: 10,
    offset: 5
)
<?php
$customers = Payload\Account::filter_by(array(
    'order_by' => 'created_at',
    'limit' => 10,
    'offset' => 5
    ))->all();
?>
pl.Customer.select().range(1,15)
    .then(function(customers){})
var customers = pl.Customer
    .order_by( pl.attr.created_at )
    .range( 5, 15 );

Results of a query can be paged using the limit and offset query string parameters.

Paging

Attribute Description
limit=10 Limit the number of results
offset=5 Offset the results from the start of the set

Ordering

Attribute Description
order_by=created_at Results Ordering

Attributes

Every object has a predefined set of default attributes, which you can find in the object reference. Some objects also have non-default attributes which you can explicitly request to include in a response.

You also have the ability to add any custom attributes you want to any object.

Request Specific Attributes

Filter Attributes

curl "https://api.payload.co/invoices/3axWdCgzecx9HR7PSOqP2" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "fields[]=due_date" \
    -d "fields[]=amount_due"
results = pl.Invoice.select(
    attr.due_date, attr.amount_due ).all()
results = pl.Invoice.select(
    pl.attr.due_date, pl.attr.amount_due
).then(function(results){})
var results = pl.Invoice.select(
    pl.attr.due_date, pl.attr.amount_due ).all();

If only certain attributes are needed, you can explicitly requested which attributes you want returned.


Included Extra Attributes

curl "https://api.payload.co/invoices/3axWdCgzecx9HR7PSOqP2" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "fields[]=*" \
    -d "fields[]=processing_account"
results = pl.Invoice.select(
    attr.processing_account, *pl.Invoice ).all()
results = pl.Invoice.select(
    '*', pl.attr.processing_account
).then(function(results){})
var results = pl.Invoice.select(
    "*", pl.attr.processing_account ).all();

If there’s an additional attribute you want included in the response that is not a default attributes, you can specify that attribute and all default fields using the default attributes symbol, *.


Set Global Preferences

pl.Payment.default_params = { 'fields':
    [ pl.attr.conv_fee, *pl.Transaction ] }
Payload::Payment.default_params = { fields: ['*'. 'conv_fee' ] }
<?php
Payload\Transaction::default_params = array( 'fields' => ['*', 'conv_fee'] );
?>
pl.Payment.default_params = { 'fields': ['*', 'conv_fee'] }
pl.Payment.default_params = new { attrs=["*", "conv_fee"] };

The software packages allow you to set global query string parameters by object resource using the default_params class attribute. You can use this to set the fields attribute to apply your preferences globally.


Set Custom Attributes

curl "https://api.payload.co/invoices/3axWdCgzecx9HR7PSOqP2" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X PUT \
    -d "attrs[custom]=data"
inv = pl.Invoice.get('3axWdCgzecx9HR7PSOqP2')

inv.update(attrs={'custom': 'data'})
inv = Payload::Invoice.get('3axWdCgzecx9HR7PSOqP2')

inv.update(attrs: {'custom': 'data'})
<?php
$inv = Payload\Invoice.get('3axWdCgzecx9HR7PSOqP2')

$inv.update(array("attrs"=>{"custom": "data"}))
?>
pl.Invoice
    .get('3axWdCgzecx9HR7PSOqP2')
    .then(function(inv) {
        return inv.update({
            attrs: {'custom': 'data'},
        })
    })
var inv = pl.Invoice.get("3axWdCgzecx9HR7PSOqP2");

inv.update(new { attrs=new { custom="data" } });

You can add any key/value custom attributes to any object by updating the attrs nested object.


Functions

curl "https://api.payload.co/accounts/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "type=customer" \
    -d "year(created_at)=2019" \
    -d "fields=*,lower(email)"
customers = pl.Customer.select(
        pl.attr.email.lower(),
        *pl.attr
    )\
    .filter_by( pl.attr.created_at.year() == 2019 )\
    .all()
customers = Payload::Customer.select( 'year(created_at)': '2019',
    fields: '*,lower(email)' )
<?php
$customers = Payload\Account::select(
        '*', 'lower(email)'
    )->filter_by(array(
        "year(created_at)"=>"2019",
        "fields"=>"*,lower(email)"
    ));
?>
pl.Customer.select(
        '*', pl.attr.email.lower(),
    ).filter_by(
        pl.attr.created_at.year().eq(2019)
    ).then(function(results) {
    })
var customers = pl.Customer.select(
        "*", pl.attr.email.lower()
    )
    .filter_by( pl.attr.created_at.year().eq( 2019 ) )
    .all();

Functions can be applied to attributes in either a query string conditional statement or to modify the attribute value in the response.

There are a few special aggregate functions which can only be used to modify the response, not in a conditional statement.

String Functions

lower | upper | length

Numeric Functions

abs | ceil | floor | round

Date Functions

year | month | monthname | day | dayname| dayofweek | dayofyear | weekofyear | last_day | hour | minute | second | unix_timestamp

Aggregate Functions

curl "https://api.payload.co/invoices/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "due_date=>2019-01-01" \
    -d "fields=sum(amount_due),status" \
    -d "group_by=status"
invoice_stats = pl.Invoice.select(
        pl.attr.amount_due.sum(),
        pl.attr.status
    )\
    .filter_by( pl.attr.due_date >= datetime.date(2019,1,1) )\
    .group_by( pl.attr.status )\
    .all()
invoice_stats = Payload::Invoice.select( due_date: '>2019-01-01',
    fields: 'sum(amount_due),status', group_by: 'status' )
<?php
$invoice_stats = Payload\Invoice::select(
        'sum(amount_due)', 'status'
    )->filter_by(
        pl->attr()->due_date->gt("2019-01-01")
    )->group_by('status')
?>
pl.Invoice.select(
        pl.attr.amount_due.sum(),
        pl.attr.status
    ).filter_by(
        pl.attr.due_date.ge(new Date(2019,1,1))
    ).group_by(
        pl.attr.status
    ).then(function(results) {
    })
var results = pl.Invoice.select(
        pl.attr.amount_due.sum(),
        pl.attr.status
    ).filter_by(
        pl.attr.due_date.ge(new DateTime(2019,1,1))
    ).group_by(
        pl.attr.status
    );
[
  {
    "status": "overdue",
    "sum(amount_due)": 2389.50
  },
  {
    "status": "paid",
    "sum(amount)": 52310.00
  }
]

Aggregate functions can be used to produce an aggregated response. You can combine aggregate functions with a group_by attribute to produce grouped results.

sum | count | count_distinct | avg | min | max | variance | stddev

group_by | ?group_by=status


Timezone

By default, dates are stored and returned in UTC. You can adjust your default timezone under settings from your dashboard.


Event Model

var socket = io.connect('/client',{
    transports: ['websocket'],
    query: {
        access_token: 'secret_key_3bW9JMZtPVDOfFNzwRdfE'
    }
})

socket.on('change', function(data) {
    console.log('[' + data.evt + '] ' + data.object + ': ' + data.id)
})

From our back-end architecture to our dashboard and API, our system is built on an event-first design.

We’ve exposed this throughout our API using a socket.io protocol so you can take advantage of real-time object changes for your object scope.

Events: insert | update | delete


Payments

Charging Payments

Simple Card Payments

payment = pl.Payment.create(
    amount=100.0,
    payment_method=pl.Card(
        card_number='4242 4242 4242 4242'
    )
)
<?php
$payment = Payload\Transaction::create(array(
    'amount'=> 100.0,
    'type'=> 'payment',
    'payment_method' =>new Payload\PaymentMethod(array(
        'type'=> 'card',
        'card'=> array('card_number'=>'4242 4242 4242 4242')
    )),
));
?>
pl.Payment.create({
    amount: 100.0,
    payment_method: new pl.Card({
        card_number: '4242 4242 4242 4242'
    })
}).then(function(payment) {
})
var payment = pl.Payment.create(new {
    amount=100.0,
    payment_method=new pl.Card(new{
        card_number="4242 4242 4242 4242"
    })
});

Processing card payments starts just simply with a card number and amount. This will attempt to process the payment for a card not on file.

Please Note: Handling card numbers can expose you to PCI compliance. To avoid any PCI exposure, you can use our Checkout and Secure Input toolkits and take advantage of our end-to-end encryption and tokenization.


Simple Bank Payments

payment = pl.Payment.create(
    amount=100.0,
    payment_method=pl.BankAccount(
        account_number='1234567890',
        routing_number='021000121',
        account_type='checking'
    )
)
<?php
    $payment = Payload\Transaction::create(array(
        'amount'=>100.0,
        'type' => 'payment',
        'payment_method'=>new Payload\PaymentMethod(array(
            'type'=> 'bank_account',
            'bank_account'=>array(
                'account_number'=>'1234567890',
                'routing_number'=> '021000121',
                'account_type' => 'checking'
                )
        )),
    ));
?>
pl.Payment.create({
    amount: 100.0,
    payment_method: new pl.BankAccount({
        account_number: '1234567890',
        routing_number: '021000121',
        account_type: 'checking'
    })
}).then(function(payment) {
})
var payment = pl.Payment.create(new {
    amount=100.0,
    payment_method=new pl.BankAccount(new{
        account_number="1234567890",
        routing_number="021000121",
        account_type="checking"
    })
});

Processing bank account payments require both the customer’s account and routing number. This will attempt to process the payment with the customer’s bank.

Please Note: To avoid handling banking information directly, you can use our Checkout and Secure Input toolkits and take advantage of our end-to-end encryption and tokenization.


Charging a Customer

customer = pl.Customer.get('u7NDGPfjBc4uwChD')
customer.charge( amount=100 )
pl.Customer.get('u7NDGPfjBc4uwChD')
    .then(function(customer) {
        return customer.charge( amount=100 )
    }).then(function(payment) {
    })

var customer = pl.Customer.get("u7NDGPfjBc4uwChD");
customer.charge(new { amount=100 });

Charge an existing customer with payment method information on file is simple. Provide the account and amount, and Payload will attempt to run the payment with the customer’s securely stored payment details.

If the customer has multiple payment details on file, the payment attempts can cascade until a successful payment is processed.


Test Payments

To create test transactions in your test environment using the test API key you can submit dummy card numbers as long as they fit a valid format. For some samples:

Card Type Samples
Visa 4242 4242 4242 4242
MasterCard 5103 2088 7967 2792
American Express 3672 005140 64164
Discover 6011 9102 7504 5718

Handle Declines

payment = pl.Payment.create(
    amount=100.0,
    payment_method=pl.Card(
        card_number='4111 1111 1111 9903'
    )
)

if payment.status == 'decline':
    print(f'Declined: {payment.status_code}')
pl.Payment.create({
    amount: 100.0,
    payment_method: new pl.Card({
        card_number: '4111 1111 1111 9903'
    })
}).then(function(payment) {
    if ( payment.status == 'decline' )
        console.log('Declined: '+ payment.status_code)
})
var payment = pl.Payment.create(new {
    amount=100.0,
    payment_method=new pl.Card(new{
        card_number="4111 1111 1111 9903"
    })
});

if ( payment.status.Equals('decline') )
    Console.WriteLine('Declined: '+ payment.status_code);

If a payment has been declined, the payments status will return declined and the transaction’s status_code will contain one of the decline codes described below.

To trigger one of the decline code use the test number from the below table in the test environment.

Decline Code Description Test Number
card_expired The card has expired. 4111 1111 1111 9900
duplicate_attempt This transaction appears be a duplicate attempt and has been prevented. 4111 1111 1111 9901
exceeded_limit The amount of the transaction exceeds the allowed limit for this account. 4111 1111 1111 9902
general_decline The card has been declined, contact card issuer for more information. 4111 1111 1111 9903
insufficient_bal The card does not have a sufficient balance to complete the payment. 4111 1111 1111 9904
invalid_card_code The security code is invalid, please check the number. 4111 1111 1111 9905
invalid_card_number The card number is invalid, please check the number. 4111 1111 1111 9906
invalid_zip The ZIP Code does not match the card. 4111 1111 1111 9907
suspicious_activity This transaction has been identified as suspicious. 4111 1111 1111 9908
too_many_attempts Too many payment attempts have been made, please try again later. 4111 1111 1111 9909

Voids and Refunds

Void a Payment

curl "https://api.payload.co/transactions/3bW9JN4BVk3wU0ZZQs2Ay" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X PUT \
    -d "status=voided"
payment = pl.Transaction.get('3bW9JN4BVk3wU0ZZQs2Ay')
payment.update(status='voided')
payment = Payload::Transaction.get('3bW9JN4BVk3wU0ZZQs2Ay')
payment.update(status: 'voided')
<?php
$payment = Payload\Transaction::get('3bW9JN4BVk3wU0ZZQs2Ay');
$payment->update(array( 'status' => 'voided' ))
?>
pl.Transaction.get('3bW9JN4BVk3wU0ZZQs2Ay')
    .then(function(payment){
        return payment.update({ status: 'voided' })
    })
var payment = pl.Payment.get("3bW9JN4BVk3wU0ZZQs2Ay");
payment.update(new { status="voided" });

To cancel a recent payment before it settles, simply update it’s status to voided. If a payment has already settled, you can initiate a refund instead.


Refund a Payment

curl "https://api.payload.co/transactions/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "type=refund" \
    -d "amount=100" \
    -d "ledger[0][assoc_transaction_id]=3bW9JN4BVk3wU0ZZQs2Ay"
payment = pl.Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')

refund = pl.Refund.create(
    amount=payment.amount,
    ledger=[{
        'assoc_transaction_id': payment.id
    }]
)
payment = Payload::Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')

refund = Payload::Refund.create(
    amount: payment.amount,
    ledger: [{
        'assoc_transaction_id' => payment.id
    }]
)
<?php
$payment = Payload\Transaction::get('3bW9JN4BVk3wU0ZZQs2Ay');

$refund = Payload\Transaction::create(array(
    'type' => 'refund',
    'amount' => $payment->amount,
    'ledger' => array(array(
        'assoc_transaction_id' => $payment->id
    ))
));
?>
pl.Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')
    .then(function(payment){
        return pl.Refund.create({
            amount: payment.amount,
            ledger: [{
                assoc_transaction_id: payment.id
            }]
        })
    })
var payment = pl.Payment.get("3bW9JN4BVk3wU0ZZQs2Ay");

var refund = pl.Refund.create(new {
    amount=payment.amount,
    ledger=new[]{
        new pl.Ledger(new{ assoc_transaction_id=payment.id })
    }
});

// Or the short form
var refund = payment.refund();

To refund a payment, create a new refund transaction with a ledger entry setting the assoc_transaction_id to the original payment.


Partially Refund a Payment

curl "https://api.payload.co/transactions/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "type=refund" \
    -d "amount=10" \
    -d "ledger[0][assoc_transaction_id]=3bW9JN4BVk3wU0ZZQs2Ay"
payment = pl.Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')

refund = pl.Refund.create(
    amount=10,
    ledger=[{
        'assoc_transaction_id': payment.id
    }]
)
payment = Payload::Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')

refund = Payload::Refund.create(
    amount: 10,
    ledger: [{
        'assoc_transaction_id' => payment.id
    }]
)
<?php
$payment = Payload\Transaction::get('3bW9JN4BVk3wU0ZZQs2Ay');

$refund = Payload\Transaction::create(array(
    'type' => 'refund',
    'amount' => 10,
    'ledger' => array(array(
        'assoc_transaction_id' => $payment->id
    ))
));
?>
pl.Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')
    .then(function(payment){
        return pl.Refund.create({
            amount: 10,
            ledger: [{
                assoc_transaction_id: payment.id
            }]
        })
    })
var payment = pl.Payment.get("3bW9JN4BVk3wU0ZZQs2Ay");

var refund = pl.Refund.create(new {
    amount=10,
    ledger=new[]{
        new pl.Ledger(new{ assoc_transaction_id=payment.id })
    }
});

// Or the short form
var refund = payment.refund();

To refund only part of a payment, create a new refund transaction with a ledger entry setting the assoc_transaction_id to the original payment but set the amount attribute of the refund to be the desired partial amount.


Make a Blind Refund

curl "https://api.payload.co/transactions/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "type=refund" \
    -d "amount=10" \
    -d "payment_method[type]=card" \
    -d "payment_method[card_number]=4242 4242 4242 4242" \
payment = pl.Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')

refund = pl.Refund.create(
    amount=10,
    payment_method=pl.Card(
        card_number='4242 4242 4242 4242'
    )
)
payment = Payload::Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')

refund = Payload::Refund.create(
    amount: 10,
    payment_method=Payload::Card(
        card_number: '4242 4242 4242 4242'
    )
)
<?php
$refund = Payload\Transaction::create(array(
    'type' => 'refund',
    'amount' => 10,
    'processing_id' => '3c0EVDZYsO4lVTVGwoX89'
    'payment_method' => new Payload\PaymentMethod(array(
        'type' => 'card',
        'card'=> array('card_number'=>'4242 4242 4242 4242')
    ))
));
?>
pl.Payment.get('3bW9JN4BVk3wU0ZZQs2Ay')
    .then(function(payment){
        return pl.Refund.create({
            amount: 10,
            payment_method: pl.Card(
                card_number: '4242 4242 4242 4242'
            }
        })
    })
var payment = pl.Payment.get("3bW9JN4BVk3wU0ZZQs2Ay");

var refund = pl.Refund.create(new {
    amount=10,
    payment_method=pl.Card(new{
        card_number="4242 4242 4242 4242"
    })
});

To initiate a blind refund, create a new refund transaction but instead of proving a ledger entry linking to the original payment provide a payment method object or id of the account receiving the blind refund.


Chargebacks and Rejects

Chargebacks

curl "https://api.payload.co/transactions/?type=chargeback&created_at=>2019-01-01&fields=*,ledger" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
chargebacks = pl.Chargeback.select(
        pl.attr.ledger,
        *pl.Transaction
    )\
    .filter_by(
        created_at=datetime.date(2019,1,1)
    )


for chargeback in chargebacks:
    print(chargeback.ledger[0].assoc_transaction_id)
    # The id of the associated payment
chargebacks = Payload::Chargeback.select(
    created_at: '>2019-01-01',
    fields: '*,ledger'
)

for chargeback in chargebacks
    puts chargeback.ledger[0].assoc_transaction_id
    # The id of the associated payment
end
<?php
$chargebacks = Payload\Chargeback::select(
        '*', 'ledger'
    )->filter_by(
        pl->attr()->created_at->gt('2019-01-01')
    );

foreach( $chargebacks as $chargeback ) {
    echo $chargeback->ledger[0]->assoc_transaction_id;
    # The id of the associated payment
}
?>
pl.Chargeback.select(
    '*', pl.attr.ledger
).filter_by(
    pl.attr.created_at.gt(new Date(2019,1,1))
).then(function(chargebacks){
    for ( var i = 0; i < chargebacks.length; i++ ) {
        var chargeback = chargebacks[i]
        console.log( chargeback.ledger[0].assoc_transaction_id )
        // The id of the associated payment
    }
})

var chargebacks = pl.Chargeback.select(
        "*", pl.attr.ledger,
    )
    .filter_by(new {
        created_at=new DateTime(2019,01,01)
    });


foreach ( var chargeback in chargebacks ) {
    Console.WriteLine( chargeback.ledger[0].assoc_transaction_id );
    // The id of the associated payment
}

A chargeback is the result of a customer disputing a payment. This can be caused by suspected fraud, a claim of services not rendered, or unauthorized payment. Chargebacks result in the bank reversing the original payment. If a dispute is overturned, the funds will be returned as a chargeback_reversal transaction type.

Chargebacks have type chargeback and are associated to the original payment through the ledger.

Your disputes can be managed through disputes dashboard.


Bank Account Rejects

curl "https://api.payload.co/transactions/?status=rejected&rejected_on=>2019-02-01" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
rejected_payments = pl.Payment.filter_by(
    pl.attr.status=='rejected',
    pl.attr.rejected_on>=datetime.date(2019,1,1)
)

for payment in rejected_payments:
    print(payment.status_code)
rejected_payments = Payload::Payment.select(
    status: 'rejected',
    rejected_on: '>2019-01-01'
)

for payment in rejected_payments
    puts payment.status_code
end
<?php
$rejected_payments = Payload\Transaction::filter_by(
    pl->attr()->status->eq('rejected'),
    pl->attr()->rejected_on->gt('2019-01-01')
);

foreach( $rejected_payments as $payment ) {
    echo $payment->status_code;
}
?>
pl.Payment.select(
    pl.attr.status.eq('rejected'),
    pl.attr.rejected_on.gt(new Date(2019,1,1))
).then(function(rejected_payments){
    for ( var i = 0; i < rejected_payments.length; i++ ) {
        var payment = rejected_payments[i]
        console.log( payment.status_code )
    }
})
var rejected_payments = pl.Payment.filter_by(
    pl.attr.status.eq("rejected"),
    pl.attr.rejected_on.ge(new DateTime(2019,1,1))
);

foreach( var payment in rejected_payments )
    Console.WriteLine( payment.status_code );

Bank account payments don’t decline instantly like card payments and can be rejected by the bank for reasons like insufficient funds or incorrect account number a few days after they were originally processed.


Types of Transactions

Type Description
payment A customer’s payment.
deposit A settlement of processed payments sent to a processing account funding method.
refund A return of a payment (full or partial).
reversal Debit the processing account for refunds or chargebacks.
chargeback A return of the original payment from a disputed payment.
chargeback_reversal A transaction recovering the returned funds from an overturned chargeback and .
Status Description
authorized An authorization for a payment. The payment can be set to process at a later date.
processed A transaction that has processed successfully.
voided A canceled transaction before any transfer was initiated.
declined A payment attempt that was unsuccessful.
rejected A bank account payment that was rejected by the bank.

Processing Accounts

Select all active processing accounts

curl "https://api.payload.co/accounts/?processing[status]=active" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
processing_account = pl.ProcessingAccount.filter_by(
    processing={ 'status': 'active' }
).all()
processing_account = Payload::ProcessingAccount.filter_by(
    processing: { 'status': 'active' }
)
<?php
$processing_account = new Payload\ProcessingAccount::filter_by(array(
    'processing' => array( 'status' => 'active )
));
?>
processing_accounts = pl.ProcessingAccount.filter_by({
    processing: { 'status': 'active' }
}).all()
var processing_account = pl.ProcessingAccount.filter_by(new {
    processing=new { status="active" }
});

Every payment needs to be associated with an active Processing Account to process. Processing Accounts specify the pricing and settings for how payments should be processed, including what funding account to transmit the payment to.

Processing Accounts can be added through the dashboard or the full setup process can be integrated into your platform using the Integrated Processing Account Setup toolkit.

Using Processing Accounts

Using your processing account is simple. Once your account is activated, all it comes down to is selecting the right account for your transactions.

payment = pl.Payment.create(
    amount=100.0,
    processing_account_id=processing_account.id,
    payment_method=pl.Card(
        card_number='4242 4242 4242 4242'
    )
)
payment = pl.Payment.create({
    amount: 100.0,
    processing_account_id: processing_account.id,
    payment_method: new pl.Card({
        card_number: '4242 4242 4242 4242'
    })
})
var payment = pl.Payment.create(new {
    amount=100.0,
    processing_account_id=processing_account.id,
    payment_method=new pl.Card(new{
        card_number="4242 4242 4242 4242"
    })
});

Paying a Processing Account

When making a payment, pass the id of the desired processing account with the payment as the processing_id parameter.

# If no processing account is specified
# the default account will be used automatically
payment = pl.Payment.create(
    amount=100.0,
    payment_method=pl.Card(
        card_number='4242 4242 4242 4242'
    )
)
// If no processing account is specified
// the default account will be used automatically
payment = pl.Payment.create({
    amount: 100.0,
    payment_method: new pl.Card({
        card_number: '4242 4242 4242 4242'
    })
})
// If no processing account is specified
// the default account will be used automatically
var payment = pl.Payment.create(new {
    amount=100.0,
    payment_method=new pl.Card(new{
        card_number="4242 4242 4242 4242"
    })
});

Setting a Default Account

You can set a default processing account on the dashboard, or by flipping the default attribute to true.

Any payment that is submitted without a processing_id specified will automatically select the default processing account.

Segmenting Customers

If you are using Customers to manage your customer accounts, you can specify a primary processing account for each customer by setting the primary_processing_id.

Any payment made for this customer without a processing_id specified will default to the primary_processing_id.


Processing Activation

Check the activation status

curl "https://api.payload.co/accounts/3bW9JMapnT7sw7neax7ui" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
processing_account = pl.ProcessingAccount.get('3bW9JMapnT7sw7neax7ui')

print(processing_account.processing.status)
processing_account = Payload::ProcessingAccount.get('3bW9JMapnT7sw7neax7ui')

puts processing_account.processing.status
<?php
$processing_account = Payload\Account::get('3c0EVDZYsO4lVTVGwoX89');

echo($processing_account->processing['status']);
?>
payload.ProcessingAccount.get('3bW9JMapnT7sw7neax7ui')
    .then(function(processing_account){
        console.log(processing_account.processing.status)
    })
var processing_account = pl.ProcessingAccount.get('3bW9JMapnT7sw7neax7ui');

Console.WriteLine(processing_account.processing.status);

A processing account generally takes 24 business hours for full payment processing activation. Sometimes the information submitted for the processing account is incorrect and will require you to update the information to proceed.

Below is a breakdown of the different statuses for a Processing Account.

Status Description
pending The new processing account activation is underway.
active The processing account has been activated.
in_review The processing account has been put into a review process which can take 3-5 days.
update_required Some of information submitted for the processing account needs to be updated.
inactive The processing account has set to inactive.

Changes to the status can be monitored using the processing_status webhook.


Integrated Processing Setup

To integrate the processing account setup into your app or platform, you can use the Processing Account ui toolkit to capture the details for a new processing account.

Example

Below is an example of the Processing Account form. Click the button to view.

<script src="https://payload.co/Payload.js"
  pl-client-key="client_key_AWcpDnNBB7oLfNqfQ6g66262"
  pl-btn-open="processing-account">
</script>
<script>
Payload('client_key_AWcpDnNBB7oLfNqfQ6g66262');

new Payload.ProcessingAccount()
    .on('account_created', function(evt) {
        console.log(evt.account.id)
    })
</script>

Open the Processing Form

You can open the Processing Account form using the Payload inline script tag with the pl-btn-open parameter set to processing-account.

The Processing Account plugin can be opened using the javascript interface by creating a new instance of Payload.ProcessingAccount, as seen in the example.


JavaScript Events

<script>
Payload.on( '.processing-button', 'account_created', function(evt) {
    console.log('handle event')
})
</script>

The processing form has different JavaScript events that you can watch for. To watch for an event on a Payload Button, you can pass either the element or a css selector as the first parameter.

Event Description
account_created Event triggered after the form has been successfully completed.
loaded Event triggered after the form has loaded
closed Event triggered after the form has been closed

Configuration

<script> Tag JavaScript Description
pl-client-key
required
client_key
required
Your client key
pl-btn-open
required
N/A Value must be processing-account
pl-btn-class N/A CSS class to apply to the button.
pl-btn-text N/A Display text for the button.
pl-inline inline Draw the form in-line instead of in a modal.
N/A container Specify a containing element embed the inline form.
N/A form Specify a form element to auto-submit with the account id once an application is processed.

Fee & Pricing Control

Net or Gross Deposits

Net Deposits

Processing fees by default are configured to be netted out of the deposit to the processing account. The total fee amount netted from a deposit can be found in the fee attribute. If your account is configured for gross deposits, the fee attribute will be zero.

Gross Deposits

If your processing account is set to gross deposits, your processing fees will be deducted monthly. By default, the fees will be debited directly from the processing account funding method but you can override this to use a separate account.

Convenience Fees

curl "https://api.payload.co/transactions/3bW9JN4BVk3wU0ZZQs2Ay?field=*,conv_fee" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
payment = pl.Payment.select(
        pl.attr.conv_fee,
        *pl.Payment
    )\
    .get('3bW9JN4BVk3wU0ZZQs2Ay')

print(payment.fee) # Fee charged to the processing account
print(payment.conv_fee) # Fee charged to the customer
payment = Payload::Payment.get('3bW9JN4BVk3wU0ZZQs2Ay',
    fields: '*,conv_fee')

puts payment.fee # Fee charged to the processing account
puts payment.conv_fee # Fee charged to the customer
<?php
$payment = Payload\Transaction::select('*', 'conv_fee')->get('3bW9JN4BVk3wU0ZZQs2Ay');

echo $payment->fee; # Fee charged to the processing account
echo "\n";
echo $payment->conv_fee; # Fee charged to the customer
echo "\n";
?>
pl.Payment.select( '*', pl.attr.conv_fee )
    .get('3bW9JN4BVk3wU0ZZQs2Ay')
    .then(function(payment){
        console.log(payment.fee) // Fee charged to the processing account
        console.log(payment.conv_fee) // Fee charged to the customer
    })
var payment = pl.Payment
    .select( '*', pl.attr.conv_fee )
    .get("3bW9JN4BVk3wU0ZZQs2Ay");

Console.WriteLine( payment.fee ); // Fee charged to the processing account
Console.WriteLine( payment.conv_fee ); // Fee charged to the customer

By using Paylaod Checkout, you can configure certain fees to be charged as a convenience for different types of payment methods. You can configure how much of the processing fee you want to cover, e.g.: all, none, or part of the fee. This can be set differently by payment method, i.e.: debit card, credit card, or bank account and can also be different for different parts of your application. The convenience fee amount is stored in the conv_fee attribute which must be explicitly requested using the select() function.

Adjustable Rates

Rates can be adjustable based on industry, volume, and type, i.e.: debit card, credit card, and bank account payments.

For more information, please reach out at sales@payload.co.

Platform Upcharging

For platforms offering Payload payment services to their customers, if applicable for our rev-share program, platforms can add an upcharge to their processing rates.

For more information, please reach out at sales@payload.co.


Checkout

Payload Checkout is a UI toolkit for integrating simple payment acceptance into your portal or platform. Below are a few of built-in the benefits of accepting payments using the checkout toolkit.

If your platform has recurring customers, you can create Customer accounts and store your customers payment information and settings for fast checkout and simple recurring billing.

Example

Below is a simple example of Payload Checkout. Click Pay Now to view.

Pay Now Button

<form action="/submit_payment" method="POST">
  <script src="https://payload.co/Payload.js"
    pl-client-key="client_key_AWcpDnNBB7oLfNqfQ6g66262"
    pl-amount="100.00"
    pl-description="Membership">
  </script>
</form>

Step 1) Script Tag

Use the script tag to embed a Pay Now button into your form. This will open an instance of Payload Checkout to accept a new payment.

Step 2) Form Submit

Once a payment is confirmed, a hidden input, <input type="hidden" name="pl_transaction_id" value="{id}">, is added to the form element and then Payload.js automatically submits the form.

Step 3) Server Confirmation

pl_transaction_id = '19QsDurdSxJ0gVNzOcQSlew3D'
curl "https://api.payload.co/transactions/$pl_transaction_id" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X PUT \
    -d status=processed
import payload as pl
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'

@server.route('/submit_payment', method='post')
def post_transaction(pl_transaction_id):
    pl.Transaction(id=pl_transaction_id)\
        .update(status='processed')
require 'payload'
Payload.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'

post '/submit_payment/' do
    pl_transaction_id = params[:pl_transaction_id]

    transaction = Payload::Transaction.get(
        pl_transaction_id,
        update: { 'status' => 'processed' }
    )
end
<?php
pl::$api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE';

$pl_transaction_id = $_GET['pl_transaction_id'];

$transaction = Payload\Transaction::filter_by(
        pl->attr()->id->eq(pl_transaction_id)
    )->update(array( 'status' => 'processed' ));
?>
var pl = require('payload-api')
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'

app.post('/submit_payment', (req, res) => {
    var pl_transaction_id = req.body.pl_transaction_id
    pl.Transaction({ id: pl_transaction_id })
        .update({ status: 'processed' })
        .then(function() {
            res.send('Payment Successful!')
        })
})
pl.api_key = "secret_key_3bW9JMZtPVDOfFNzwRdfE";

string pl_transaction_id = "19QsDurdSxJ0gVNzOcQSlew3D";

new pl.Payment(new{
    id=pl_transaction_id
}).update(new { status="processed" });

On the server side, you must confirm the pl_transaction_id.

The transaction has only been authorized at this point. To confirm the transaction you need to update the status from authorized to processed as seen in the example.


JavaScript Interface

Step 1) Open via JavaScript

<script src="https://payload.co/Payload.js"></script>

<script>
Payload('client_key_AWcpDnNBB7oLfNqfQ6g66262');

var checkout = new Payload.Checkout({
    amount: 1000.0,
    description: "Example Payment"
})
</script>

Payload Checkout can be opened using the javascript interface by creating a new instance of Payload.Checkout, as seen in the example.

You can have Payload Checkout automatically submit a form after a successful payment by passing the form element as the form parameter when creating the checkout instance.


Step 2) Watch for Callback

<script>
checkout.on('processed', function(evt) {
    $.post('/submit_payment',
        { pl_transaction_id: evt.transaction_id })
})
</script>

When using the JavaScript interface and Checkout is not embedded within a form, you can watch for the processed callback to receive the transaction id.

Once you receive the transaction id you will need to send that to the server using an asynchronous request or trigger your own form submit.

Step 3) Server Confirmation

pl_transaction_id = '19QsDurdSxJ0gVNzOcQSlew3D'
curl "https://api.payload.co/transactions/$pl_transaction_id" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -X PUT \
    -d status=processed
import payload as pl
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'

@server.route('/submit_payment', method='post')
def post_transaction(pl_transaction_id):
    pl.Transaction(id=pl_transaction_id)\
        .update(status='processed')
require 'payload'
Payload.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'

post '/submit_payment/' do
    pl_transaction_id = params[:pl_transaction_id]

    transaction = Payload::Payment.get(
        pl_transaction_id,
        update: { 'status' => 'processed' }
    )
end
<?php
Payload\Payload::$api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE';

$pl_transaction_id = $_GET['pl_transaction_id'];


$transaction = Payload\Transaction::filter_by(
        pl->attr()->id->eq(pl_transaction_id)
    )->update(array( 'status' => 'processed' ));
);
?>
var pl = require('payload-api')
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'

app.post('/submit_payment', (req, res) => {
    var pl_transaction_id = req.body.pl_transaction_id
    pl.Payment({ id: pl_transaction_id })
        .update({ status: 'processed' })
        .then(function() {
            res.send('Payment Successful!')
        })
})
pl.api_key = "secret_key_3bW9JMZtPVDOfFNzwRdfE";

string pl_transaction_id = "19QsDurdSxJ0gVNzOcQSlew3D";

new pl.Payment(new{
    id=pl_transaction_id
}).update(new { status="processed" });

On the server side, you must confirm the pl_transaction_id using the transactions api.

The transaction has only been authorized at this point. To confirm the transaction you need to update the status from authorized to processed as seen in the example.


JavaScript Callbacks

<script>
Payload.on( '.checkout-btn', 'processed', function(evt) {
    console.log('handle event')
})
</script>

Payload Checkout has different JavaScript events that you can watch for. To watch for an event on a Payload Button, you can pass either the element or a css selector as the first parameter.

Event Description
loaded Event triggered after the form has been successfully completed
processed Event triggered after a payment has been processed successfully
declined Event triggered if a payment attempt was declined
closed Event triggered after the form has been closed

Checkout Configuration

Script Tag Attributes

Attributes Description
pl-client-key
required
Your client key.
pl-amount
required without invoice-id
The payment amount.
pl-amount-editable
optional
Allow custom amounts.
pl-description
required without invoice-id
A description of the payment.
pl-order-number If you have a specific order number, include it to reference the transaction later.
pl-processing-id The processing account id for the payment.
pl-customer-id Load an existing customer’s account settings. See the Customers section for more information.
pl-card-payments
default: true
Specifies if payments via card are accepted.
pl-bank-account-payments
default: false
Specifies if payments via bank account are accepted.
pl-billing-address Use true or false to specify whether billing address fields are displayed. Default is false.
pl-email The customer’s email address, if available.
pl-name The customer’s name, if available.
pl-invoice-id To tie the resulting payment to an invoice, pass the invoice id value.
pl-auto-billing-toggle Adds an option to allow customer to set payment method as billing default.
pl-conv-fee Use true or false to specify whether fee should be charged as a convenience.
pl-attrs Transaction custom attributes JSON object. Example: data-attrs='{"example":"data"}'
pl-btn-class CSS class to apply to the button.
pl-btn-text Display text for the button.
pl-type
default: popup-btn
Choose between popup-btn and inline.

JavaScript Attributes

Attributes Description
key
required
Your client key.
amount
required without invoice id
The payment amount.
description
required
A description of the payment.
amount_editable
optional
Allow custom amounts.
processing_id
optional
The processing account id for the payment.
customer_id
optional
The customer’s account id.
card_payments
default: true
Specifies if payments via card are accepted.
bank_account_payments
default: false
Specifies if payments via bank account are accepted.
billing_address Use true or false to specify whether billing address fields are displayed. Default is false.
email The customer’s email address, if available.
full_name The customer’s full name, if available.
invoice_id To tie the resulting payment to an invoice, pass the invoice id value.
auto_billing_toggle Adds an option to allow customer to set payment method as billing default.
attrs Transaction custom attributes JSON object. Example: {"example":"data"}
conv_fee Use true or false to specify whether fee should be charged as a convenience.
inline An html element to embed the interface instead of a popup.
form If present, following a successful payment the form will be submitted with the pl_transaction_id parameter.

Secure Input

Use Secure Input for capturing card or bank account details securely from within any html form.

How Does it Work?

<!-- Secure Input required Payload.js -->
<script src="https://payload.co/Payload.js"></script>

Payload’s Secure Input library will capture the sensitive card or bank account details from the customer, pass the details directly to Payload’s secure PCI environment, and return either a transaction id or a payment method id.

Secure Inputs offer a lot of flexibility for customizing the user experience of capturing payment details without introducing security concerns to your app by handling sensitive card and bank account details.

Creating a Form

Bootstrap 4 Example

<form id="checkout-form" class="container" pl-form="payment">
    <input type="hidden" pl-input="amount" value="10.00">
    <div class="row pt-2">
        <div class="form-group col-7 px-1">
            <label>Card Number</label>
            <div class="form-control" pl-input="card_number"></div>
        </div>
        <div class="form-group col-3 px-1">
            <label>Expiration</label>
            <div class="form-control" pl-input="expiry"></div>
        </div>
        <div class="form-group col-2 px-1">
            <label>CVC</label>
            <div class="form-control" pl-input="cvc"></div>
        </div>
    </div>
    <div class="row pt-2">
        <button class="btn btn-primary" type="submit">Pay Now</button>
    </div>
</form>
<script>
Payload('UPDATE WITH YOUR test_client_key_....')

var checkout_form = new Payload.Form({
    form: $('#checkout-form').get(0)
})
</script>

Creating a form with Payload Secure Inputs requires just a few html attributes and a JavaScript call to initialize. Follow the below steps to setup a form:

1) Tag the Form

Create an html <form> element and tag the form with the pl-form attribute.

<form pl-form="payment"></form>


The pl-form attribute accepts a value of either payment or payment_method.

Payment Form

The payment form type should be used if the form is intended to process a payment, or checkout, immediately once the form is completed.

Payment Method Form

The payment_method form type should be used if the form is intended to capture payment and billing details to be stored for future payments.

2) Create the Inputs

There are two types of inputs you can add to your form. Secured inputs for the card number and other sensitive data fields that need to be <div> elements or data inputs for things like the payment amount or other data fields that don’t contain sensitive data and can be a standard html <input>s.

Both input types require the pl-input html attribute.

Secured Inputs

Secured inputs need to be a <div> element type with the pl-input attribute.

<div pl-input="card_number"></div>


Secure input types include:


Data Inputs

Data inputs are standard <input> elements with the pl-input attribute for any data field that should be included in the request to Payload.

<input type="hidden" pl-input="amount" value="10.00">


Data inputs can be any attribute in the object tree, some common fields include:

3) Initialize with JavaScript

After the form and the inputs have been created, initialize the form by passing the form element to a new Payload.Form object using Payload’s JavaScript interface.

<script>
Payload('test_client_key_....')
new Payload.Form(document.getElementById('checkout-form'))
</script>


Initialize with static data fields

You can initialize the form with static data fields to be included with the other input fields when creating the object.

<script>
Payload('test_client_key_....')
new Payload.Form({
    form: document.getElementById('checkout-form')),
    payment: {
        processing_id: '<processing id here>',
        description: 'Payment description'
    }
})
</script>


Handling Results

Once a form submit is triggered, a request is sent to Payload with only the data from the secure inputs within the form. If the request to payload is successful, the form submit proceeds as normal. If the request is unsuccessful, the invalid inputs are shown as invalid.

Successful Result

If the request to Payload was successful, a hidden element containing the new object’s id will be embedded into the form. If the pl-form attribute was set to payment then an input with the name pl_transaction_id will be added to the form. If the pl-form attribute was set to payment_method then an input with the name pl_payment_method_id will be added to the form.

<input type="hidden" name="pl_transaction_id" value="<id of new payment>">


Once this input has been added to the form, a form submit is triggered.

Override Form Submit

<script>
var checkout = new Payload.Form({
    form: document.getElementById('checkout-form'),
    autosubmit: false
}).on('processed',function(evt) {
    /*handle response*/
})
</script>

If you want to handle the form in a different way and don’t want it to submit, you can use the autosubmit: false option when initializing the form. You can watch for the JavaScript events and handle the results in another manner.


Invalid Input

<script>
var checkout = new Payload.Form(document.getElementById('checkout-form'))
    .on('invalid',function(evt) {
        /*handle invalid data*/
        $(evt.target).css({background:'red'})
        alert(evt.message)
    })
    .on('valid',function(evt) {
        /*revert to a valid input*/
        $(evt.target).css({background:'white'})
    })
    .on('error',function(evt) {
        /*handle bad request*/
    })
</script>

If the request to Payload is invalid, the pl-invalid css class will be applied to any invalid inputs. Note: the default invalid css classname can be changed

There is also an invalid and an error event that can be used for custom error handling. The invalid event will only fire if any of the input fields contained invalid values. The error event will trigger for any error response from the API request. Please see the errors section for more information on possible error responses.


Declined Payment

<script>
var checkout = new Payload.Form(document.getElementById('checkout-form'))
    .on('declined',function(evt) {
        /*handle declined payment*/
        console.log(evt.status_code)
        alert(evt.message)
    })
</script>

If the payment request to Payload results in a decline, the declined event will fire with a message attribute describing the reason and the decline status code.


Styling Inputs

You can style any input by adding your own classes or css rules, or by extending the payload classes. It’s also possible to override the default classnames using the Payload.Form styles parameter.

Standard CSS Styling

Style inputs using Bootstrap 4 classes

<input class="form-control" pl-input="amount" value="10.00">
<div class="form-control" pl-input="card_number"></div>

To style a secure input you can apply any custom style directly to the <div> or <input> element as shown in the corresponding Bootstrap example.


Extend Payload Classes

.pl-input {
    color: #495057;
    background-color: #fff;
    border: 1px solid #ced4da;
    padding: .375rem .75rem;
    outline: none;
}

Another approach to styling inputs is to add a CSS rule for the CSS classes below.

Payload’s default CSS classes:


Override Default Classes

Using the Bootstrap 4 invalid class

<script>
new Payload.Form({
    form: document.getElementById('checkout-form'),
    styles: { invalid: 'is-invalid' } /* override default style class */
})
</script>

Instead of extending the default Payload classes, you can change the defualt class for state changes like invalid to use a custom class.


Trigger Changes on JS Events

Setting custom classes via events

<script>
new Payload.Form({
    form: document.getElementById('checkout-form'),
}).on('focus', function(evt) {
    /* handle focus event */
    $(evt.target).addClass('my-focus')
}).on('blur', function(evt) {
    /* handle blur event */
    $(evt.target).removeClass('my-focus')
}).on('invalid', function(evt) {
    /* handle invalid event */
    $(evt.target).addClass('my-invalid')
}).on('valid', function(evt) {
    /* handle valid event */
    $(evt.target).removeClass('my-invalid')
})
</script>

For more advanced control over styling and UX of the secure input, Payload.js provides event triggers for state changes of the inputs.

You can listen for events like blur and invalid, and handle the style and UX changes required for that event.


Input Options

Payment

Name Description
amount The amount of the payment

Cards

Name Description
card_number secure Card number
expiry secure Expiration of the card
cvc secure Code on the back of the card
account_holder Name of the cardholder

Bank Accounts

Name Description
account_number secure The bank account number
routing_number secure The ACH routing number for the account
account_type Either checking or savings
account_class optional Either personal or business
account_holder Signer for the account

Billing Details

Name Description
billing_address[street_address] optional
billing_address[unit_number] optional
billing_address[city] optional
billing_address[state_province] optional
billing_address[postal_code] optional

JavaScript Interface

Form Options

Name Description
form The html form element
autosubmit optional Specify if the form submit should trigger. Default: true
styles optional Change the default classnames
payment optional Add extra fields to include with the payment
payment_method optional Add extra fields to include with the payment_method

Events

Name Description
processing Triggered once a request is sent to Payload
processed Triggered if the payment was successfully processed
declined Triggered if the payment was declined
created Triggered if the payment method was successfully saved
invalid Triggered if the any of the input field values were invalid
error Triggered for any API error response
focus Triggered if a secure input receives focus
blur Triggered if a secure input looses focus

React Component Library

Use the payload-react component library when integrating Secure Input with React.

Installation

npm install payload-react

View on Github

Examples


Payment Links

Payload Payment Links are a simple and useful tool for accepting payments via secure links. A payment link will generate a stand-alone payment page that can be sent via email or linked to from any webpage as either a one-time or reusable link.

Basic One-time Link

curl "https://api.payload.co/payment_links/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d type="one_time" \
    -d description="Payment Request" \
    -d amount="10" \
    -d processing_id="3brhxEXpz2qEJ8vnIXbvW"
payment_link = pl.PaymentLink.create(
    type='one_time',
    description='Payment Request',
    amount=10.00, # Optional
    processing_id='3brhxEXpz2qEJ8vnIXbvW'
)
payment_link = Payload::PaymentLink.create(
    type: 'one_time',
    description: 'Payment Request',
    amount: 10.00, # Optional
    processing_id: '3brhxEXpz2qEJ8vnIXbvW'
)
<?php
$payment_link = Payload\PaymentLink::create(array(
    'type' => 'one_time',
    'description' => 'Payment Request',
    'amount' => 10.00, # Optional
    'processing_id' => '3brhxEXpz2qEJ8vnIXbvW'
));
?>
pl.PaymentLink.create({
    type: 'one_time',
    description: 'Payment Request',
    amount: 10.00,  /* Optional */
    processing_id: '3brhxEXpz2qEJ8vnIXbvW'
}).then(function(payment_link){})
var payment_link = pl.PaymentLink.create(new {
    type="one_time",
    description="Payment Request",
    amount=10.00, /* Optional */
    processing_id="3brhxEXpz2qEJ8vnIXbvW"
});

Creating a basic link just requires a short description and the processing_id of the payment. You can either specify an amount or allow the client to enter the amount themselves. Payment links also accept a customer_id key instead of a processing_id, which will enable an automatic email to be sent directly to the client.


Include Additional Data Fields

Additional Data Fields

curl "https://api.payload.co/payment_links/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d type="one_time" \
    -d description="Payment Request" \
    -d amount="10" \
    -d processing_id="3brhxEXpz2qEJ8vnIXbvW"
    -d additional_datafields='[{"section":"Extra Fields","fields":[{"title:"Field Name"}]}]'
payment_link = pl.PaymentLink.create(
    type='one_time',
    description='Payment Request',
    amount=10.00,
    processing_id='3brhxEXpz2qEJ8vnIXbvW',
    additional_datafields=[{
        'section': 'Extra Fields',
        'fields': [{ 'title': 'Field Name' }]
    }]
)
payment_link = Payload::PaymentLink.create(
    type: 'one_time',
    description: 'Payment Request',
    amount: 10.00,
    processing_id: '3brhxEXpz2qEJ8vnIXbvW',
    additional_datafields: [{
        section: 'Extra Fields',
        fields: [{ title: 'Field Name' }]
    }]
)
<?php
$payment_link = Payload\PaymentLink::create(array(
    'type' => 'one_time',
    'description' => 'Payment Request',
    'amount' => 10.00,
    'processing_id' => '3brhxEXpz2qEJ8vnIXbvW',
    'additional_datafields' => array(array(
        'section' => 'Extra Fields',
        'fields' => array(array( 'title' => 'Field Name' ))
    ))
));
?>
pl.PaymentLink.create({
    type: 'one_time',
    description: 'Payment Request',
    amount: 10.00,
    processing_id: '3brhxEXpz2qEJ8vnIXbvW',
    additional_datafields: [{
        section: 'Extra Fields',
        fields: [{ title: 'Field Name' }]
    }]
}).then(function(payment_link){})
var payment_link = pl.PaymentLink.create(new {
    type="one_time",
    description="Payment Request",
    amount=10.00,
    processing_id="3brhxEXpz2qEJ8vnIXbvW",
    additional_datafields: new []{new{
        section='Extra Fields',
        fields=new []{new{ title='Field Name' }}
    }}
});

If there are additional data or form fields that should be collected with each payment, additional_datafields can accept a list of additional fields grouped by sections. The resulting data field values will be stored in the resulting payment attrs object.


Reusable-time Link

curl "https://api.payload.co/payment_links/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d type="reusable" \
    -d description="Payment Request" \
    -d processing_id="3brhxEXpz2qEJ8vnIXbvW"
payment_link = pl.PaymentLink.create(
    type='reusable',
    description='Payment Request',
    processing_id='3brhxEXpz2qEJ8vnIXbvW'
)
payment_link = Payload::PaymentLink.create(
    type: 'reusable',
    description: 'Payment Request',
    processing_id: '3brhxEXpz2qEJ8vnIXbvW'
)
<?php
$payment_link = Payload\PaymentLink::create(array(
    'type' => 'reusable',
    'description' => 'Payment Request',
    'processing_id' => '3brhxEXpz2qEJ8vnIXbvW'
));
?>
pl.PaymentLink.create({
    type: 'reusable',
    description: 'Payment Request',
    processing_id: '3brhxEXpz2qEJ8vnIXbvW'
}).then(function(payment_link){})
var payment_link = pl.PaymentLink.create(new {
    type="reusable",
    description="Payment Request",
    processing_id="3brhxEXpz2qEJ8vnIXbvW"
});

A reusable secure payment link can be used as a static page to accept as many payments as needed. Reusable links are great for adding a static link directly from any website or page that may be used to collect a payment from visitors.


Attach a File

Attach a File

payment_link = pl.PaymentLink.create(
    type='one_time',
    description='Payment Request',
    attachments = [{'file':file1}, {'file':file_2}],
    amount=10.00,
    processing_id='3bz0zU9dAX06SJwfMmfn0'
)

Files with extensions: 'svg','pdf','png' can be attached to a payment link within the attachments field.


Customer Link

curl "https://api.payload.co/payment_links/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d type="one_time" \
    -d description="Payment Request" \
    -d customer[name]="Test Customer" \
    -d customer[email]="testcustomer@gmail.com" \
    -d processing_id="3brhxEXpz2qEJ8vnIXbvW"
payment_link = pl.PaymentLink.create(
    type='one_time',
    description='Payment Request',
    processing_id='3brhxEXpz2qEJ8vnIXbvW',
    customer=pl.Customer(
        name='Test Customer',
        email='testcustomer@gmail.com'
    )
)
payment_link = Payload::PaymentLink.create(
    type: 'one_time',
    description: 'Payment Request',
    processing_id: '3brhxEXpz2qEJ8vnIXbvW',
    customer: Payload::Customer(
        name: 'Test Customer',
        email: 'testcustomer@gmail.com'
    )
)
<?php
$payment_link = Payload\PaymentLink::create(array(
    'type' => 'one_time',
    'description' => 'Payment Request',
    'processing_id' => '3brhxEXpz2qEJ8vnIXbvW',
    'customer'=Payload\Customer(array(
        'name' => 'Test Customer',
        'email' => 'testcustomer@gmail.com'
    ))
));
?>
pl.PaymentLink.create({
    type: 'one_time',
    description: 'Payment Request',
    processing_id: '3brhxEXpz2qEJ8vnIXbvW',
    customer: pl.Customer(
        name: 'Test Customer',
        email: 'testcustomer@gmail.com'
    )
}).then(function(payment_link){})
var payment_link = pl.PaymentLink.create(new {
    type="one_time",
    description="Payment Request",
    processing_id="3brhxEXpz2qEJ8vnIXbvW",
    customer=pl.Customer(new{
        name="Test Customer",
        email="testcustomer@gmail.com"
    })
});

An email can be automatically triggered by providing a customer’s name and email address or an existing customer_id when instantiating a secure payment link. The customer will receive an email with details on the requested payment and a secure link to the payment page where they’ll be able to complete the payment.


Reusable-time Link

print(payment_link.url)
puts(payment_link.url)
<?php
echo($payment_link->url);
?>
console.log(payment_link.url)
Console.WriteLine(payment_link.url);

Every payment link object includes a url that can be sent to a client for payment or can be added to any website or page.


Handling Successful Payment

The customer and admin will receive email receipts after a successful payment. A redirect url can be provided as well which will redirect the page after a payment has been completed.


Customers

Customer accounts provide a simple way to organize and simplify your recurring customer’s purchases and payment settings.

With customer accounts you can:

Creating Customers

Create a New Customer

curl "https://api.payload.co/accounts/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d email="matt.perez@example.com" \
    -d name="Matt Perez" \
    -d type="customer"
account = pl.Customer.create(
    email='matt.perez@example.com',
    name='Matt Perez',
    primary_processing_id=processing.id
)
account = Payload::Customer.create(
    email: 'matt.perez@example.com',
    name: 'Matt Perez'
)
<?php
$account = Payload\Account::create(array(
    'type' => 'customer',
    'email' => 'matt.perez@example.com',
    'name' => 'Matt Perez'
));
?>
pl.Customer.create({
    email: 'matt.perez@example.com',
    name: 'Matt Perez'
}).then(function(account){
})
var account = pl.Customer.create(new {
    email="matt.perez@example.com",
    name="Matt Perez"
});

Create a customer account is simple. Start creating an account of type customer and provide the customer’s email address and name.

If you have more than one processing account, you can specify the primary processing account for the customer by passing the primary_processing_id.

Once an account has been created, you can store the customer['id'] of the resulting Payload account.


Customer Checkout

<form action="/submit_payment" method="POST">
  <script src="https://payload.co/Payload.js"
    pl-client-key="client_key_AWcpDnNBB7oLfNqfQ6g66262"
    pl-amount="100.00"
    pl-description="Membership"
    pl-customer-id="{customer_id}">
  </script>
</form>

Once you’ve created a customer account, you can pass the customer_id to Checkout using the data-customer-id parameter.

This will load any stored payment methods and settings for that customer and allow them to update their payment options.


Automating Billing

Create a customer invoice

curl "https://api.payload.co/invoices/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d items[0][amount]=100 \
    -d items[0][description]=membership \
    -d customer_id="3bW9JMoqAIGhB3J4axyam"
invoice = pl.Invoice.create(
    items=[ pl.ChargeItem(entry_type='charge', amount=100, description='membership')],
    due_date='2019-01-01',
    customer_id='3bsVUMvAUV8AdvbFWVSWD',
    processing_id='3bz0zU99AX06SJwfMmfn0',
)
invoice = Payload::Invoice.create(
    items: [ Payload::ChargeItem( amount: 100, description: 'membership' ) ],
    customer_id: customer.id
)
<?php
$invoice = Payload\Invoice::create(array(
    'type' => 'bill',
    'processing_id' => '3c0EVDZYsO4lVTVGwoX89',
    'due_date' => '2019-01-01',
    'customer_id' => '3bzs0vKOQllhhooaG0omz'
    'items' => array(
        new Payload\LineItem(array(
            'entry_type' => 'charge',
            'amount' => 29.99
        ))
    )
));
?>
// Create a customer invoice
pl.Invoice.create({
        items: [
            new pl.ChargeItem({
                amount: 100,
                description: "membership"
            })
        ],
        customer_id: customer.id
    }).then(function(invoice) {
    })

var invoice = pl.Invoice.create(new {
        due_date=new Date(2019,5,1),
        items=new []{
            new pl.ChargeItem(new{amount=100, description="membership"})
        },
        customer_id=customer.id
    });

A key feature of customer accounts is being able to setup automated billing. With a customer account you can:

More information can be found in the Billing & Invoicing section.


Billing & Invoicing

Invoicing

Create an Invoice


curl "https://api.payload.co/invoices/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d type="bill" \
    -d due_date="2019-01-01" \
    -d processing_id="3bW9JMapnT7sw7neax7ui" \
    -d items[0][type]="item1" \
    -d items[0][amount]=29.99 \
    -d customer_id=3bW9JMoGYQul5fCIa9f8q
invoice = pl.Invoice.create(
    items=[pl.ChargeItem(entry_type='charge', amount=100)],
    due_date='2019-01-01',
    customer_id='3bsVUMvAUV8AdvbFWVSWD',
    processing_id='3bz0zU99AX06SJwfMmfn0',
)
invoice = Payload::Invoice.create(
    type: 'bill',
    processing_id: 'AcOnw5PigJ4Ww43Fo',
    due_date: '2019-01-01',
    items: [
        Payload::ChargeItem.new(
            type: 'item1',
            amount: 29.99
        )
    ],
    customer_id='3bW9JMoGYQul5fCIa9f8q'
)
<?php
$invoice = Payload\Invoice::create(array(
    'type' => 'bill',
    'processing_id' => '3c0EVDZYsO4lVTVGwoX89',
    'due_date' => '2019-01-01',
    'items' => array(
        new Payload\LineItem(array(
            'entry_type' => 'charge',
            'amount' => 29.99
        ))
    ),
    'customer_id' => '3bzs0vKOQllhhooaG0omz'
));
?>
pl.Invoice.create({
    type: 'bill',
    processing_id: '3bW9JMapnT7sw7neax7ui',
    due_date: '2019-01-01',
    items: [
        new pl.ChargeItem({
            type: 'item1',
            amount: 29.99
        })
    ],
    customer_id: '3bW9JMoGYQul5fCIa9f8q'
})
var invoice = pl.Invoice.create(new {
    type="bill",
    processing_id="3bW9JMapnT7sw7neax7ui",
    due_date="2019-01-01",
    items=new[]{
        new pl.ChargeItem(new{
            type="item1",
            amount=29.99
        })
    },
    customer_id="3bW9JMoGYQul5fCIa9f8q"
});

Invoicing is a simple way to create and track orders, and send payment requests. At the core, an invoice has a due date, an associated processing account for routing funds, and line item charges. You can also assign a customer to an invoice.

Line items can be positive or negative values and can be either a charge or a payment type. A charge type refers to an incurred amount by the biller. A payment type should be reserved for payments from the customer only.


Pay an Invoice

curl -s "https://api.payload.co/transactions" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:\
    -X POST \
    -d account_id=3bW9JND5iKnAlOYn9u82q \
    -d amount=100 \
    -d type=payment \
    -d allocations[0][invoice_id]=3bW9JND5iKnAlOYn9u82q
invoice = pl.Invoice.get('3bW9JND5iKnAlOYn9u82q')

if invoice.status != 'paid':
    payment = pl.Payment.create(
        amount=invoice.amount_due,
        customer_id=invoice.customer_id,
        allocations=[
            pl.PaymentItem( invoice_id=invoice.id )
        ]
    ))
invoice = Payload::Invoice.get('3bW9JND5iKnAlOYn9u82q')

if invoice.status != 'paid'
    payment = Payload::Payment.create(
        amount: invoice.amount_due,
        customer_id: invoice.customer_id,
        allocations: [
            Payload::PaymentItem.new(
                invoice_id: invoice.id
            )
        ]
    )
end
<?php
$invoice = Payload\Invoice::get('3bW9JND5iKnAlOYn9u82q');
$transaction = Payload\Transaction::get('2bW9JND5an6AlOYn9ufx3');

if ( invoice.status != 'paid' ) {
    $payment = Payload\Transaction::create(array(
        'amount' => $invoice->amount_due,
        'customer_id' => $invoice->customer_id,
        'type' => 'payment',
        'payment_method_id' => $transaction->payment_method_id,
        'allocations' => array(
            Payload\LineItem::new(array(
                'invoice_id'=>$invoice->id,
                'entry_type' => 'payment'
            ))
        )
    ));
}
?>
pl.Invoice.get('3bW9JND5iKnAlOYn9u82q')
    .then(function( invoice ){
        if ( invoice.status != 'paid' ) {
            pl.Payment.create({
                    amount: invoice.amount_due,
                    customer_id: invoice.customer_id,
                    allocations: [
                        new pl.PaymentItem({
                            invoice_id: invoice.id,
                        })
                    ]
                })
        }
    })
var invoice = pl.Invoice.get("3bW9JND5iKnAlOYn9u82q");

if ( invoice.status != "paid" ) {
    var payment = pl.Payment.create(new {
        amount=invoice.amount_due,
        customer_id=invoice.customer_id,
        allocations=new[]{
            new pl.PaymentItem(new{
                invoice_id=invoice.id
            })
        }
    });
}

To pay invoice, you can create a payment object with a nested payment line item within the allocations nested array. This will allocate the transaction to the invoice, subtracting from any balance due on that invoice.

If you’re using Checkout, you can pass the invoice id to the checkout instance and it will automatically allocate any successful payments.


Billing Schedules

Create a Billing Schedule

Setup a Billing Schedule

curl "https://api.payload.co/billing_schedules/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d start_date="2019-01-01" \
    -d end_date="2019-12-31" \
    -d recurring_frequency="monthly" \
    -d type="subscription" \
    -d processing_id="3bW9JMapnT7sw7neax7ui" \
    -d charges[0][type]=option_1 \
    -d charges[0][amount]=39.99 \
    -d customer_id=3bW9JMorTV1q6mXkkXUTg
billing_schedule = pl.BillingSchedule.create(
    start_date=datetime.date(2019, 1, 1),
    end_date=datetime.date(2019, 12, 31),
    recurring_frequency='monthly',
    type='subscription',
    processing_id='3bW9JMapnT7sw7neax7ui',
    charges=[
        pl.BillingCharge(type='option_1', amount=39.99)
    ],
    customer_id='3bW9JMorTV1q6mXkkXUTg'
)
billing_schedule = Payload::BillingSchedule.create(
    start_date: '2019-01-01',
    end_date: '2019-12-31',
    recurring_frequency: 'monthly',
    type: 'subscription',
    processing_id: '3bW9JMapnT7sw7neax7ui',
    charges: [
        Payload::BillingCharge.new(
            type: 'option_1',
            amount: 39.99
        )
    ],
    customer_id: '3bW9JMorTV1q6mXkkXUTg'
)
<?php
$billing_schedule = Payload\BillingSchedule::create(array(
    'start_date' => '2019-01-01',
    'end_date' => '2019-12-31',
    'recurring_frequency' => 'monthly',
    'type' => 'subscription',
    'processing_id' => '3bW9JMapnT7sw7neax7ui',
    'charges' => array(
        new Payload\BillingCharge(array(
            'type' => 'option_1',
            'amount' => 39.99
        ))
    ),
    'customer_id' => '3bW9JMorTV1q6mXkkXUTg'
));
?>
pl.BillingSchedule.create({
    start_date: '2019-01-01',
    end_date: '2019-12-31',
    recurring_frequency: 'monthly',
    type: 'subscription',
    processing_id :  '3bW9JMapnT7sw7neax7ui',
    charges: [
        new pl.BillingCharge({
            type: 'option_1',
            amount: 39.99
        })
    ],
    customer_id: '3bW9JMorTV1q6mXkkXUTg'
}).then(function(billing_schedule) {
})
var billing_schedule = pl.BillingSchedule.create(new {
    start_date="2019-01-01",
    end_date="2019-12-31",
    recurring_frequency="monthly",
    type="subscription",
    processing_id="3bW9JMapnT7sw7neax7ui",
    charges=new[]{
        new pl.BillingCharge(new{
            type="option_1",
            amount=39.99
        })
    },
    customer_id="3bW9JMorTV1q6mXkkXUTg"
});

If you have a preset or recurring billing schedule, you can use the billing schedule object to create and manage advanced billing settings. You can define charges, start dates, frequencies, and more, as well as enable automatic billing of invoices to a customer.

Invoices will be automatically generated based on your billing schedule’s recurring_frequency, with Line Items that correspond with the Billing Charges in your charges array.

For more information on integrating Billing Schedules, check out the Subscription Tutorial.


Specify Billing Frequency

Updating a Billing Frequency

curl "https://api.payload.co/billing_schedules/{id}" \
  -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
  -X PUT \
  -d recurring_frequency="bimonthly" \
  -d bimonthly[first_billing_day]=1 \
  -d bimonthly[second_billing_day]=15
billing_schedule.update(
  recurring_frequency='annually'
  annually={
    'billing_month': 12,
    'billing_day': 31
  }
)
billing_schedule.update(
    recurring_frequency: 'monthly'
)
<?php
$billing_schedule->update(array( 'recurring_frequency'=>'quarterly' ));
?>
billing_schedule.update({
  recurring_frequency: 'quarterly',
  quarterly: {
    q1: {
      billing_month: 2,
      billing_day:28
    },
    q2: {
      billing_month: 5,
      billing_day: 31
    },
    q3: {
      billing_month: 8,
      billing_day: 31
    },
    q4: {
      billing_month: 11,
      billing_day: 30
    }
  }
}).then(function(update) {
  // Handle Billing Schedule update
})
billing_schedule.update(new { recurring_frequency="bimonthly" });

Billing schedules can run daily, weekly, biweekly (every two weeks), bimonthly (twice a month), monthly, quarterly, or annually. If the recurring_frequency is set to bimonthly, monthly, quarterly, or annually, the billing day(s) and/or month(s) can be adjusted by updating the corresponding nested object.

Within the bimonthly, monthly, quarterly, and annually objects, the fields are integers that represent the day(s) and/or month(s) on which billing will occur. In the example below, billing will occur on the 12th of every month, starting with the first 12th of the month after the start_date, and ending with the last 12th of the month before the end_date.

'monthly': {
  'billing_day': 12
}

Default Behavior

Recurring billing will occur on the following days if values are not provided:

daily: Every day beginning on the start_date

weekly: Every seven days, beginning on the start_date

biweekly: Every fourteen days, beginning on the start_date

Note that automatic invoice generation does not occur for partial billing periods.

bimonthly default value
first_billing_day The day the Billing Schedule was created, or 14 days before
second_billing_day The day the Billing Schedule was created, or 14 days after

The default values for the bimonthly billing days are set dynamically based on the day of the month on which the Billing Schedule was created. By default they will always be 14 days apart, the first_billing_day will always be lower, and the second_billing_day will never be 29, 30, or 31.

For example, if the Billing Schedule was created on the 22nd of the month, the first_billing_day will be 8, and the second_billing_day will be 22. If the Billing Schedule was created on the 3rd of the month, the first_billing_day will be 3, and the second_billing_day will be 17.

monthly default value
billing_day The day the Billing Schedule was created
quarterly default value
q1[billing_day] and q1[billing_month] 1 and 1 (the 1st of January)
q2[billing_day] and q2[billing_month] 1 and 4 (the 1st of April)
q3[billing_day] and q3[billing_month] 1 and 7 (the 1st of July)
q4[billing_day] and q4[billing_month] 1 and 10 (the 1st of October)

By default, quarterly billing will occur on the first day of each quarter.

annually default value
billing_day The day the Billing Schedule was created
billing_month The month the Billing Schedule was created

Edge Cases

For the bimonthly, monthly, quarterly, and annually recurring frequencies, billing can be set to occur on any day of a given month, including the 29th - 31st.

For months with fewer than 31 days, billing will occur on the last available day of that month.

This means that in April, all billing for the 30th and the 31st of the month will occur on April 30th. In February, all billing for the 28th-31st of the month will occur on the 28th.

In leap years, all billing for the 29th-31st of February will occur on February 29th.


Enable Automatic Payments

If a customer has a default_payment_method flag set to true on one of their payment methods, that payment method will be automatically charged on the due date of any invoice they are assigned to.


Mobile Card Reader

curl -s "https://api.payload.co/readers" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE:
card_reader = pl.CardReader.select().all().first()
print(card_reader.json())
card_reader = Payload::CardReader.select(
    wifi_details: {'status': 'connected'}).first
puts card_reader.json()
<?php
$card_reader = Payload\CardReader::select()[0];
echo($card_reader.json());
?>
pl.CardReader.select()
    .then(function( card_readers ){
        console.log(card_readers[0].json())
    })
var card_reader = pl.CardReader.select().all()[0];

card_reader.json();
{
    "id": "7IpDlK8yqRnJm7rQhHW892",
    "object": "reader",
    "created_at": "Mon, 27 May 2019 00:11:13 GMT",
    "modified_at": "Mon, 27 May 2019 00:11:13 GMT",
    "name": "eDynamo-B49BC7E",
    "processing_id": null,
    "serial_num": "B49BC7E042319AA",
    "type": "mgtk_edynamo"
}

Our integrated card reader has a simple interface to help seamlessly facilitate your in-person payments through the /readers API resource.

The mobile reader can be used out of the box with our mobile Android and iOS SDKs.

Features

Supported Platforms


Android
>=21

iOS
>=10

View the Card Reader SDK Docs


Webhook Triggers

Registering a Webhook

curl "https://api.payload.co/webhooks/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d trigger="payment" \
    -d url="https://yourdomain.com/path/to/webhook/handler"
webhook = pl.Webhook.create(
    trigger='payment',
    url='https://yourdomain.com/path/to/webhook/handler'
)
webhook = Payload::Webhook.create(
    trigger: 'payment',
    url: 'https://yourdomain.com/path/to/webhook/handler'
)
<?php
$webhook = Payload\Webhook::create(array(
    'trigger' => 'payment',
    'url' => 'https://yourdomain.com/path/to/webhook/handler'
));
?>
pl.Webhook.create({
    trigger: 'payment',
    url: 'https://yourdomain.com/path/to/webhook/handler'
}).then(function(webhook){})
var webhook = pl.Webhook.create(new {
    trigger="payment",
    url="https://yourdomain.com/path/to/webhook/handler"
});

Example Trigger Request

POST https://yourdomain.com/path/to/webhook/handler

Example Trigger Payload

{
    "object": "webhook_trigger",
    "trigger": "payment",
    "triggered_on": {
        "id": "3bW9JN4BVk3wU0ZZQs2Ay",
        "type": "payment",
        "object": "transaction",
    }
}

Webhooks can be used to monitor events in real-time by initiating a http request to any specified url providing an instant notification of that event.

When a trigger occurs, a POST request will be made to the webhook URL with details of the object that triggered the webhook. The http response code from the request is stored in the Webhook logs for simple auditing of the triggered requests.

The available triggers are shown below.

Triggers

Name Description
payment Any payment that gets initiated
refund Any refund that gets initiated
void Any payment that gets voided
automatic_payment Any payment that is initiated by automatic billing
decline Any payment that gets declined
chargeback Any chargebacks that get issued against the account
chargeback_reversal Any chargebacks that get overturned
bank_account_reject Any bank payment that get rejected by the bank
processing_status Any change to the activation status of a processing account

Webhook OAuth

Registering a Webhook with OAuth Parameters

curl "https://api.payload.co/webhooks/" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d trigger="payment" \
    -d url="https://yourdomain.com/path/to/webhook/handler"
    -d oauth_params= '{
        "grant_type": "client_credentials",
        "client_id": "58d5c805-bf35-4c3d-a722-4e09a2af7a11",
        "client_secret": "UdfghhY.IZB=F-kuxehjyAl:ty9m[k/",
        "resource": " ",
        "auth_url": "https://login.microsoftonline.com/gh572c30-debb-41a6-9c78-05dfgghhfa0d/oauth2/token"
        }'
webhook = pl.Webhook.create(
    trigger='payment',
    url='https://yourdomain.com/path/to/webhook/handler',
    oauth_params={
        'grant_type': 'client_credentials',
        'client_id': '58d5c805-bf35-4c3d-a722-4e09a2af7a11',
        'client_secret': 'UdfghhY.IZB=F-kuxehjyAl:ty9m[k/',
        'resource': ' ', # optional
        'auth_url': 'https://login.microsoftonline.com/gh572c30-debb-41a6-9c78-05dfgghhfa0d/oauth2/token'
        })
<?php
$webhook = Payload\Webhook::create(array(
    'trigger' => 'payment',
    'url' => 'https://yourdomain.com/path/to/webhook/handler',
    'oauth_params' => array(
        'client_id' => '58d5c805-bf35-4c3d-a722-4e09a2af7a11',
        'client_secret'=> 'UdfghhY.IZB=F-kuxehjyAl:ty9m[k/' ,
        'grant_type' => 'client_credentials',
        'resource' => '' , // optional
        'auth_url' => 'https://login.microsoftonline.com/gh572c30-debb-41a6-9c78-05dfgghhfa0d/oauth2/token',
    )
));
?>
pl.Webhook.create({
    trigger: 'payment',
    url: 'https://yourdomain.com/path/to/webhook/handler',
    oauth_params: {
        'client_id' : '58d5c805-bf35-4c3d-a722-4e09a2af7a11',
        'client_secret': 'UdfghhY.IZB=F-kuxehjyAl:ty9m[k/',
        'grant_type' : 'client_credentials',
        'resource' : ' ', // optional
        'auth_url' : 'https://login.microsoftonline.com/gh572c30-debb-41a6-9c78-05dfgghhfa0d/oauth2/token',
    }
}).then(function(webhook){})
var webhook = pl.Webhook.create(new {
    trigger="payment",
    url="https://yourdomain.com/path/to/webhook/handler",
    oauth_params= new {
        client_id = "58d5c805-bf35-4c3d-a722-4e09a2af7a11" ,
        client_secret=  "UdfghhY.IZB=F-kuxehjyAl:ty9m[k/",
        grant_type = "client_credentials",
        resource = " " , // optional
        auth_url = "https://login.microsoftonline.com/gh572c30-debb-41a6-9c78-05dfgghhfa0d/oauth2/token",
    }
});

To add OAuth parameters to the Webhook, pass the client_id, client_secret, grant_type, resource, and auth_url in the oauth_params field when creating the Webhook. The retrieved token will be passed in the request headers to the webhook url.


Reporting & Analytics

import payload as pl
from payload import attr

Custom reporting & analytics is simple by utilizing our query language combined with the aggregate response value modifier functions and group by capabilities.

Using our software libraries ARM (API Relational Model) design, constructing complex reporting queries is simple and intuitive.

Example Queries

Monthly Payments by Card Type

results = pl.Transaction.select(
        attr.completed_date.month(),
        attr.completed_date.year(),
        attr.payment_method.card_type.countbyval()
    ).filter_by(
        attr.status == 'processed',
        attr.completed_date.year() == 2019,
        attr.completed_date.month() <= 6
    ).group_by(
        attr.completed_date.month(),
        attr.completed_date.year()
    ).all()
pl.Transaction.select(
        attr.completed_date.month(),
        attr.completed_date.year(),
        attr.payment_method.card_type.countbyval()
    ).filter_by(
        attr.status.eq('processed'),
        attr.completed_date.year().eq(2019),
        attr.completed_date.month().eq(6)
    ).group_by(
        attr.completed_date.month(),
        attr.completed_date.year()
    ).then(function(results) {
    })
var results = pl.Transaction.select(
        attr.completed_date.month(),
        attr.completed_date.year(),
        attr.payment_method.card_type.countbyval()
    ).filter_by(
        attr.status.eq("processed"),
        attr.completed_date.year().eq(2019),
        attr.completed_date.month().eq(6)
    ).group_by(
        attr.completed_date.month(),
        attr.completed_date.year()
    ).all();
year(completed_date) month(completed_date) count(visa) count(mc) count(amex)
2019 1 627 170 68
2019 2 890 286 154
2019 3 1590 276 243
2019 4 1326 325 304
2019 5 1654 365 241
2019 6 1891 479 302

Average Volume by Day of Week

results = pl.Transaction.select(
        pl.attr.completed_date.dayofweek(),
        pl.attr.amount.avgsum()
    ).filter_by(
        pl.attr.status == 'complete',
        pl.attr.completed_date.year() == 2019
    ).group_by(
        pl.attr.completed_date.dayofweek()
    ).all()
pl.Transaction.select(
        pl.attr.completed_date.dayofweek(),
        pl.attr.amount.avgsum()
    ).filter_by(
        pl.attr.status.eq('complete'),
        pl.attr.completed_date.year().eq(2019)
    ).group_by(
        pl.attr.completed_date.dayofweek()
    ).then(function(results) {
    })
var results = pl.Transaction.select(
        pl.attr.completed_date.dayofweek(),
        pl.attr.amount.avgsum()
    ).filter_by(
        pl.attr.status.eq("complete"),
        pl.attr.completed_date.year().eq(2019)
    ).group_by(
        pl.attr.completed_date.dayofweek()
    ).all();
dayofweek(completed_date) avgsum(amount)
sunday 24960.22
monday 8060.74
tuesday 15732.97
wednesday 15732.97
thursday 26703.59
friday 29253.64
saturday 25357.85

Unified Payout & Ledger

Transaction Ledger

The transaction ledger holds the records of all associated payment activity and how they relate, like refunds of a payment. Each transaction has a nested list of ledger entries relative to them.


Reading the Ledger Records

curl "https://api.payload.co/transactions" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "fields[]=*" \
    -d "fields[]=ledger"
payment = pl.Payment.select( pl.attr.ledger, *pl.Transaction )
    .get('3bW9JN8jkM9mP3qQCO5mC')
payment = Payload::Payment.select( '*', 'ledger' )
    .get('3bW9JN8jkM9mP3qQCO5mC')
    $payment = Payload\Transaction::select('*', 'ledger')
    ->get('3bW9JN8jkM9mP3qQCO5mC);
pl.Payment.select('*', pl.attr.ledger)
    .get('3bW9JN8jkM9mP3qQCO5mC')
    .then(function(trans) {
    })
var trans = pl.Payment.filter_by( "*", pl.attr.ledger )
    .get("3bW9JN8jkM9mP3qQCO5mC");

The nested ledger list can be included in the response by requesting the ledger attribute be part of the response.


{
  "id": "3bW9JN4OPJXdUyrK5VZQG",
  "object": "transaction",
  "amount": 29.99,
  "created_at": "2019-01-01 21:41:04",
  "payment_method_id": "3bW9JMoT2CKw8DQ1yFyG8",
  "status": "processed",
  "type": "payment",
  "ledger": [{
    "amount": -29.99,
    "assoc_transaction_id": "3bW9JN8cF50sSYDtI5X0a",
    "timestamp": "2019-01-04 18:30:09",
    "entry_type": "deposit"
  },{
    "amount": -29.99,
    "assoc_transaction_id": "3bW9JN8cutWRm2iYnhjaS",
    "timestamp": "2019-01-10 09:52:29",
    "entry_type": "refund"
  },{
    "amount": 29.99,
    "assoc_transaction_id": "3bW9JN8dAmVVEkM5guwca",
    "timestamp": "2019-01-10 18:30:23",
    "entry_type": "reversal"
  }]
}

Ledger entries can be either positive or negative depending on whether the associated transaction is a debit or a credit relative to the parent transaction.

The ledger entry_type refers to the associated transaction type.

In the example you’ll see a payment that was deposited but was later refunded. Since the payment had already been deposited, a reversal transaction was initiated to rebalance the ledger.


Unified Payout

Payout Batching

All transaction, regardless of whether the payment was made with a bank account, Visa, MasterCard, Discover, or American Express, are batched into a single unified deposit, daily.


List of Deposited Payments

curl "https://api.payload.co/transactions" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d "fields=*,ledger" \
    -d "type=deposit" \
    -d "processing_id=$processing_id"
trans = pl.Transaction.select(
        pl.attr.ledger,
        *pl.Transaction
    ).filter_by(
        type='deposit',
        processing_id=processing_account.id
    )
trans = Payload::Transaction.select( type: 'deposit',
    processing_id: processing_account.id, fields: '*,ledger' )
trans = Payload\Transaction::select(
        '*', 'ledger'
    )->filter_by(array(
        'type'=>'deposit',
        'processing_id'=>$processing_account->id
    ))->all();
payload.Transaction.select({
    'type': 'deposit',
    'processing_id': processing_account.id,
    'fields': '*,ledger'
)).then(function(trans) {
})
var trans = pl.Transaction.select(
        "*", pl.attr.ledger
    ).filter_by(new{
        processing_id=processing_account.id,
        type="deposit"
    });

To nested ledger list of a deposit will include the list of associated payments with a deposit.


3rd Party App Connect

To create a 3rd party app or plugin that can access Payload accounts, Payload.js has a Connect function to trigger an OAuth authentication flow.

Connect Flow

Step 1) Request OAuth Code on Client

<script src="https://payload.co/Payload.js"></script>

<script>
Payload('client_key_AWcpDnNBB7oLfNqfQ6g66262');

new Payload.Connect({
    org_id: 'f77fqR3fH4XUP8vEmqueOGOY'
}).on('connected', function(e) {
    $.post('/get_payload_oauth_token', {code: e.code}, function(){
        alert('Connected!')
    })
})
</script>

The first step is to initiate an oauth authentication flow to get an oauth code that you can use to gain access and retrieve the processing_id of an existing Payload user.

The simpliest way to initiate a request to authenticate is to use Payload.js Payload.Connect interface. Payload.Connect accepts a single parameter, org_id, which is the id of your primary payload organization that was setup when you created your Payload platform account.


Step 2) Get Token on Server

curl "https://api.payload.co/oauth/token" \
    -u secret_key_3bW9JMZtPVDOfFNzwRdfE: \
    -d code='<code retrieved from the client>' \
    -d grant_type=authorization_code \
    -d client_id='f77fqR3fH4XUP8vEmqueOGOY' \
    -d client_secret='secret_key_3bW9JMZtPVDOfFNzwRdfEe'
import payload as pl
pl.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'

@server.route('/get_payload_oauth_token', method='post')
def get_payload_oauth_token(code):
    resp = request.post('https://api.payload.co/oauth/token', data=dict(
        code=code,
        grant_type='authorization_code',
        client_id='f77fqR3fH4XUP8vEmqueOGOY',
        client_secret='secret_key_3bW9JMZtPVDOfFNzwRdfEe'
    ))

    # The resulting access token is the account's processing_id
    do_something(resp.json()['access_token'])
    return jsonify(1)
require 'payload'
Payload.api_key = 'secret_key_3bW9JMZtPVDOfFNzwRdfE'

post '/get_payload_oauth_token/' do
    code = params[:code]

    resp = HTTParty.post("https://api.payload.co/oauth/token", body: {
        code: code,
        grant_type: 'authorization_code',
        client_id: 'f77fqR3fH4XUP8vEmqueOGOY',
        client_secret: 'secret_key_3bW9JMZtPVDOfFNzwRdfEe'
    })

    do_something(resp['data']['access_token'])
end
<?php
$code = $_POST['code'];
$ch = curl_init();

$fields = array(
    "code" => $code,
    "grant_type" => "authorization_code",
    "client_id" => "f77fqR3fH4XUP8vEmqueOGOY",
    "client_secret" => "secret_key_3bW9JMZtPVDOfFNzwRdfEe"
);

curl_setopt($ch,CURLOPT_URL, "https://api.payload.co/oauth/token");
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_POSTFIELDS, $fields_string);

$result = curl_exec($ch);

curl_close($ch);
?>
app.post('/get_payload_oauth_token', (req, res) => {
    var code = req.body.code
    axios.post('https://api.payload.co/oauth/token', {
        code: code,
        grant_type: 'authorization_code',
        client_id: 'f77fqR3fH4XUP8vEmqueOGOY',
        client_secret: 'secret_key_3bW9JMZtPVDOfFNzwRdfEe'
    })
    .then((res) => {
      do_something(res.data.access_token)
    })
})

On the server side, you can use the oauth code returned from the client side to get the access token.

The access_token returned is the user’s processing_id that they selected to give access to your app. Once you receive the access token (processing_id), your account and api keys can now access reports and initiate payments on behalf of the user.


Security & Compliance

Data Security & PCI

Our platform is certified to the highest level of PCI for data security, and we go beyond the industry standards at every level. From our above grade zero-access secure vault to our rotating encryption mechanism utilizing cryptographic splitting, this is not your every-day tokenization.

Tokenization

# Example of passing card details to be stored securely
payment_method = pl.Card.create(
    account_holder='John Smith',
    card_number='4242424242424242',
    expiry='05/22',
    card_code='123'
)
print(payment_method.id)
# Returns the id of the tokenized payment details
// Example of passing card details to be stored securely
pl.Card.create(
    account_holder='John Smith',
    card_number='4242424242424242',
    expiry='05/22',
    card_code='123'
).then(function(payment_method) {
    console.log(payment_method.id)
})
// Returns the id of the tokenized payment details
// Example of passing card details to be stored securely
var payment_method = pl.Card.create(new {
    account_holder="John Smith",
    card_number="4242 4242 4242 4242",
    expiry="05/22",
    card_code="123"
});

Console.WriteLine(payment_method.id);
// Returns the id of the tokenized payment details

Take advantage of our secure, unidirectional, financial data security vault. Often referred to as tokenization, our platform handles the security and storage of your sensitive financial data taking the burden off your hands.


Client-Side Tools

<!-- Checkout Modal -->
<form action="/submit_payment" method="POST">
  <script src="https://payload.co/Payload.js"
    pl-client-key="client_key_AWcpDnNBB7oLfNqfQ6g66262"
    pl-amount="1000.00">
  </script>
</form>
<!-- Secure Input -->
<script src="https://payload.co/Payload.js"></script>

<form action="/submit_card_data" method="POST">
  <input pl-secure-input="card_number"/>
</form>

To keep your servers out of the scope of any PCI and data security requirements, our client-side toolkits make it easy to secure all sensitive financial data encrypted on the client’s machine and routed directly to us keeping you out of any data security liability.

UI Toolkit Options:


Payment & Risk Monitoring

We recognize that your business has its own cash flow requirements, and we designed our dynamic risk management technology to support your processing needs instead of industry-standard date or time-based limits. We are here to help you run your business the way you want to - with the simplest and most flexible payment platform, so you won’t need to design around typical payment processing limitations.

Payment Analysis

Our payment-by-payment real-time monitoring utilizes data-driven behavioral machine learning that looks at data points in over 150 categories across billions of dollars in processing volume. These industry-specific risk models categorize behavior by what’s standard for your operating space, not just generalized to our platform.

Flagged Transaction

results = pl.Transaction.select(
        attr.id,
        attr.risk_score
    ).filter_by(risk_flag='in_review')\
    .all()
pl.Transaction.select(
        attr.id,
        attr.risk_score
    ).filter_by(risk_flag='in_review')
    .then(function(results){
    })
var results = pl.Transaction.select(
        attr.id,
        attr.risk_score
    ).filter_by(new { risk_flag="in_review" })
    .all();

You can retrieve and review the results through the API or dashboard. Each transaction has a risk score between -1 and 1, along with a flagged status.

You can query your flagged transactions through the API or review them on our dashboard. Using the API or dashboard you can update the status of flagged transactions to either accept or reject.

Settings & Controls

Controlling your risk settings is simple. Through our dashboard you can configure your risk threshold for automatically flagging transactions as well as enable and configure other data validation options.

Data Validation Settings:


Object Reference