Before demonstrating an Adaptive Payments integration with the
Tweet Relevance sample code from Appendix A, let’s take a closer look at the
Adaptive Payments Pay
[2] and PaymentDetails
[3] APIs, which are integral to our implementation details. A
complete and more comprehensive list of all of the Adaptive Payments API
operations can be found at Adaptive
Payments API documentation on X.com. This section focuses on two
of the most essential APIs and relevant options for Tweet Relevance
payment integration along with some sample code to quickly get you up
and running.
If you come from an Express Checkout background or have been following along in the book chapter by chapter, an important distinction to make up front between Adaptive Payments and Express Checkout is the nature of the request parameters. Whereas Express Checkout involves sending in 3-Token credentials along with Name-Value pairs through the request body, Adaptive Payments require 3-Token credentials along with a mandatory application identifier and additional configuration information to be passed in as headers; the POST request payload identifies the API operation and its parameters. Let’s now turn to the Pay API and execute some sample API calls to see how it all works.
All payments made via the Pay
API have the same
essential fields and are outlined in Table 4-1. If thinking about
Adaptive Payments from an Express Checkout mindset, you might consider
the Pay
API to be similar to the
SetExpressCheckout
API in that it sets up a transaction
and returns a value called a “pay key” that can be used to redirect a
sender to PayPal for approval.
Table 4-1. Common fields for the Pay API encoded in NVP format
Field | Description |
---|---|
actionType | Will be one of three possible values:
|
receiverList
.receiver(
n ).email | One or more receivers’ email addresses, where n can take on values between 0 and 5. For parallel payments, up to 6 receivers may be identified, and for chained payments, 1 primary receiver and 5 secondary receivers may be identified. |
receiverList
.receiver(
n ) .amount | The amount to be credited to each receiver’s account. |
receiverList.receiver(
n ).primary | (Optional) Set this value to true to indicate that this is a
chained payment. Only one receiver can be the primary
receiver. |
currencyCode | The code for the currency in which the payment is made. You can specify only one currency, regardless of the number of receivers. A complete list of supported currency codes is available online. |
cancelUrl | The URL for sender redirection if the sender cancels the payment approval. This value is required, but used only for explicit payments. |
returnUrl | The URL for sender redirection after completion of the payment. This value is required, but used only for explicit payments. |
requestEnvelope.errorLanguage | The requestEnvelope is required
information common to each API operation and includes members
such as errorLanguage , the language in which
error messages are displayed, and the level of detail that
should be returned for error messages. At the current time,
the only supported error language is US English
(en_US ). |
feesPayer | (Optional) The payer of PayPal fees. Allowable values
are:
|
It’s a fine detail, but do note that part of the request
includes a mandatory “request envelope.” It’s a subtle but important
point that the existence of a dot separator in field names for the
Adaptive Payments APIs indicates a notion of hierarchy. For example,
requestEnvelope.errorLanguage
connotes that there’s a
requestEnvelope
field with a sub-field
errorLanguage
. As you’ll see later in this chapter, a
JSON object expressing this same field would be
{'requestEnvelope' : {'errorLanguage' : 'en_US'}}
.
Additional parameters are possible to include as part of the request
envelope and are specific to particular Adaptive Payments API
operations and indicated in the more comprehensive online
documentation.
For readers familiar with a Linux or Unix shell, a trivial Bash
script that uses the curl
command to execute a request
might look like Example 4-1. Readers
unfamiliar with Bash or command-line utilities should simply focus on
the structure of the curl
command
that is being executed. A brief explanation follows, and subsequent
examples for this chapter are written in Python as GAE web
applications, so there’s no need to fret if learning Bash syntax
wasn’t part of your expectations for this chapter.
Example 4-1. Bash script demonstrating execution of the Pay API
#!/bin/bash USERID="XXX" PASSWORD="XXX" SIGNATURE="XXX" APPID="APP-80W284485P519543T" RECEIVER="XXX" AMOUNT="1.00" CANCELURL="http://example.com/cancel" RETURNURL="http://example.com/return" RESULT=$(curl -s --insecure \ -H "X-PAYPAL-SECURITY-USERID: $USERID" \ -H "X-PAYPAL-SECURITY-PASSWORD: $PASSWORD" \ -H "X-PAYPAL-SECURITY-SIGNATURE: $SIGNATURE" \ -H "X-PAYPAL-REQUEST-DATA-FORMAT: NV" \ -H "X-PAYPAL-RESPONSE-DATA-FORMAT: JSON" \ -H "X-PAYPAL-APPLICATION-ID: $APPID" \ https://svcs.sandbox.paypal.com/AdaptivePayments/Pay -d "requestEnvelope.errorLanguage=en_US\ &actionType=PAY\ &receiverList.receiver(0).email=$RECEIVER\ &receiverList.receiver(0).amount=$AMOUNT\ ¤cyCode=USD\ &feesPayer=EACHRECEIVER\ &memo=Simple payment example.\ &cancelUrl=$CANCELURL\ &returnUrl=$RETURNURL\ ;) echo $RESULT
In short, the script sets up a few variables, executes a
curl
command using those variables along with some other
parameters, and displays the results. Although it’s just a trivial
script, there’s a lot that can be gleaned. The following observations
may be helpful in solidifying your understanding of how an Adaptive
Payments Pay
API operation takes place:
The USERID
, PASSWORD
, and
SIGNATURE
are the 3-Token credentials associated with
the PayPal developer account for the application making this
request.
The APPID
shown in the script is the global and
shared application identifier for development purposes. (You’d
request an application identifier for production use separately
from PayPal when your application is ready to go live.)
The remaining variables should look familiar: there’s a receiver (who may or may not be the same as the application owner), a purchase amount, and URLs that PayPal uses to redirect the sender back to your site depending on whether or not the purchase was completed or cancelled. The sender is not identified in this request, but the sender’s identify will become known once the sender logs into PayPal to approve the request.
3-Token credentials, request and response formats, and the
application identifier are passed in as headers via
curl
’s -H
option.
The request format is in NVP format, as indicated by
NV
The response format is returned in JSON format as
indicated by JSON
The Pay request is routed to
https://svcs.sandbox.paypal.com/AdaptivePayments/Pay
,
which is the Sandbox URL for the Pay
operation, and
the POST request payload as encoded in name-value pairs follows
the -d
option.
There’s a single recipient (as identified by the
receiverList.receiver(0)
values) that indicates that
this Adaptive Payments transaction is a simple payment and this
single recipient is footing the fees for the transaction as
indicated by the EACHRECEIVER
value.
A sample response from executing the previous Pay
API operation follows:
{ "payKey" : "AP-54G358058T2731358", "paymentExecStatus" : "CREATED", "responseEnvelope" : { "ack" : "Success", "build" : "2428464", "correlationId" : "7ca7e3aa6a999", "timestamp" : "2012-01-14T15:36:31.515-08:00" } }
In short, the response is formatted as JSON as requested by the
X-PAYPAL-RESPONSE-DATA-FORMAT
header, a response envelope
returns an acknowledgment that the request is successful, and the
response indicates that a payment request has been created and
includes a payKey
value that can be used to redirect a
sender to PayPal for approval. To initiate the approval process for a
Sandbox application, an application must redirect the sender back to
https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=value.
Although not germane to the Tweet Relevance integration, it’s
worthwhile to note that if the API caller and the sender are one and
the same (an Implicit Payment), a senderEmail
field can be specified, and
PayPal will implicitly approve the payment without redirecting to
PayPal for explicit approval. You can also use a preapproval to
execute the payment and avoid explicit approval. The required
preapproval fields include a preapproval key and personal
identification number (PIN).
The PaymentDetails
API is used to obtain
information about a payment. You can identify the payment by your
tracking ID, the PayPal transaction ID in an IPN message, or the pay
key associated with the payment. Table 4-2 summarizes the common
request parameters.
Table 4-2. Common PaymentDetails request fields
Field | Descriptions |
---|---|
payKey | This field identifies the payment for which you
wish to set up payment options. This is the key that is
returned in the |
requestEnvelope.errorLanguage | The requestEnvelope is required
information common to each API operation and includes members
such as errorLanguage , the language in which
error messages are displayed, and the level of detail that
should be returned for error messages. |
transactionId | (Optional) The PayPal transaction ID associated with the payment. The IPN message associated with the payment contains the transaction ID. |
trackingId | (Optional) The tracking ID that was specified for this payment in the PayRequest message. Maximum length: 127 characters. |
In short, you pass in one of several possible values that
identifies a payment to PaymentDetails
, and it returns
relevant status information about the payment. Example 4-2 illustrates a trivial Bash
script that makes a PaymentDetails
API request using a
payKey
value returned from Example 4-1. Example usage for the script is to
simply pass in the pay key as a command-line parameter to the
script.
Example 4-2. Bash script illustrating usage of the PaymentDetails API
#!/bin/bash PAYKEY="${1}" USERID="XXX" PASSWORD="XXX" SIGNATURE="XXX" APPID="APP-80W284485P519543T" RESULT=$(curl -s --insecure \ -H "X-PAYPAL-SECURITY-USERID: $USERID" \ -H "X-PAYPAL-SECURITY-PASSWORD: $PASSWORD" \ -H "X-PAYPAL-SECURITY-SIGNATURE: $SIGNATURE" \ -H "X-PAYPAL-REQUEST-DATA-FORMAT: NV" \ -H "X-PAYPAL-RESPONSE-DATA-FORMAT: JSON" \ -H "X-PAYPAL-APPLICATION-ID: $APPID" \ https://svcs.sandbox.paypal.com/AdaptivePayments/PaymentDetails -d "requestEnvelope.errorLanguage=en_US\ &payKey=$PAYKEY"\ ;) echo $RESULT
Sample results from the script follow and illustrate the basic
format of a PaymentDetails
response:
{ "actionType" : "PAY", "cancelUrl" : "http://example.com/cancel", "currencyCode" : "USD", "feesPayer" : "EACHRECEIVER", "ipnNotificationUrl" : "http://example.com/ipn", "memo" : "Simple payment example.", "payKey" : "AP-4U527241GF1114245", "paymentInfoList" : { "paymentInfo" : [ { "pendingRefund" : "false", "receiver" : { "amount" : "1.00", "email" : "XXX", "paymentType" : "SERVICE", "primary" : "false" } } ] }, "responseEnvelope" : { "ack" : "Success", "build" : "2428464", "correlationId" : "4808cadb5297e", "timestamp" : "2012-01-14T17:58:11.358-08:00" }, "returnUrl" : "http://example.com/return", "reverseAllParallelPaymentsOnError" : "false", "sender" : { "useCredentials" : "false" }, "status" : "CREATED" }
Of particular interest in the response for
PaymentDetails
is the status
field that
indicates that the payment
request has been created but not yet completed; however, should you
visit
https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=AP-4U527241GF1114245
and successfully approve the payment, invoking
PaymentDetails
again should return a status
of COMPLETED
. However, a status
of
COMPLETED
does not necessarily mean that the payment was
successfully processed and that payment was rendered—it only means
that the request, regardless of its ultimate outcome—was completed
successfully. If the status
were COMPLETED
,
additional information would be included regarding the specific
details as they relate to the payment(s). For example, the following
sample results show a PaymentDetails
response where
status
is COMPLETED
and the
paymentInfoList
field provides definitive information
about the ultimate outcome of the payment. (In the case of an eCheck
payment, the transactionStatus
would have been
PENDING
.)
{ "status": "COMPLETED", "responseEnvelope": { "ack": "Success", "timestamp": "2012-01-31T22:47:32.121-08:00", "build": "2486531", "correlationId": "e28c831c96f87" }, "returnUrl": "http://example.com", "payKey": "AP-72S344750E3616459", "senderEmail": "XXX", "actionType": "PAY", "sender": { "email": "matthe_1325995267_per@zaffra.com", "useCredentials": "false" }, "paymentInfoList": { "paymentInfo": [ { "refundedAmount": "0.00", "receiver": { "paymentType": "SERVICE", "amount": "9.99", "email": "XXX", "primary": "false" }, "transactionId": "2NB983427X665902U", "senderTransactionStatus": "COMPLETED", "senderTransactionId": "11411689C90721011", "pendingRefund": "false", "transactionStatus": "COMPLETED" } ] }, "currencyCode": "USD", "cancelUrl": "http://example.com/cancel", "feesPayer": "EACHRECEIVER", "reverseAllParallelPaymentsOnError": "false" }
If you’re comfortable working in a Linux or Unix environment or
can comfortably execute curl
commands in a Windows
environment, it’s worthwhile to try manually executing these scripts
to ensure that you understand the fundamentals. Regardless, in the
next section, we’ll implement the same logic as a GAE project.
Let’s now take the concepts concerning Pay
and
PaymentDetails
API requests from the previous sections
and consolidate them into an austere GAE application. If you’ve been
following along closely, Example 4-3 should seem
fairly straightforward. It’s a web app that processes requests for two
URLs: 1) a request on the root context of the application that
triggers a Pay
request and displays the response, and 2)
a /status
request that executes a
PaymentDetails
request for the original Pay
request and displays the response. Since native Python list and
dictionary objects are so close to a JSON representation, it makes
sense to use JSON as both the request and response format, so you’ll
notice a new import
statement that makes available a JSON
module for easily converting between the JSON string representation
and the native Python objects. You’ll also see an import
statement that brings in the memcache
module that’s used
to minimally mimic a session implementation, which the app uses to
store and later look up the payKey
returned from the
Pay
request and pass it through to the
PaymentDetails
request that is executed when
/status
is requested. Go ahead and take a look at the
code; afterward, a play-by-play synopsis is provided that breaks down
the action as a series of coarsely grained steps.
Example 4-3. A sample GAE application that executes a Simple Adaptive Payment—main.py
#!/usr/bin/env python """ A minimal GAE application that makes an Adaptive API request to PayPal and parses the result. Fill in your own 3 Token Credentials and sample account information from your own sandbox account """ import random from google.appengine.ext import webapp from google.appengine.ext.webapp import util from google.appengine.api import urlfetch from google.appengine.api import memcache from django.utils import simplejson as json # Replace these values with your own 3-Token credentials and a sample "seller" # who is the receiver of funds to run this sample code in the developer sandbox user_id = "XXX" password = "XXX" signature = "XXX" receiver = "XXX" class MainHandler(webapp.RequestHandler): # Helper function to execute requests with appropriate headers def _request(self, url, params): # standard Adaptive Payments headers headers = { 'X-PAYPAL-SECURITY-USERID' : user_id, 'X-PAYPAL-SECURITY-PASSWORD' : password, 'X-PAYPAL-SECURITY-SIGNATURE' : signature, 'X-PAYPAL-REQUEST-DATA-FORMAT' : 'JSON', 'X-PAYPAL-RESPONSE-DATA-FORMAT' : 'JSON', 'X-PAYPAL-APPLICATION-ID' : 'APP-80W284485P519543T' } return urlfetch.fetch( url, payload = json.dumps(params), method=urlfetch.POST, validate_certificate=False, headers=headers ) def get(self, mode=""): # /status - executes PaymentDetails when PayPal redirects back to this app after payment approval if mode == "status": payKey = memcache.get(self.request.get('sid')) params = { 'requestEnvelope' : {'errorLanguage' : 'en_US', 'detailLevel' : 'ReturnAll'}, 'payKey' : payKey } result = self._request('https://svcs.sandbox.paypal.com/AdaptivePayments/PaymentDetails', params) response = json.loads(result.content) if result.status_code == 200: # OK # Convert back to indented JSON and display it pretty_json = json.dumps(response,indent=2) self.response.out.write('<pre>%s</pre>' % (pretty_json,)) else: self.response.out.write('<pre>%s</pre>' % (json.dumps(response,indent=2),)) else: # / (application root) - executed when app loads and initiates a Pay request amount = 10.00 # A cheap session implementation that's leveraged in order to lookup the payKey # from the Pay API and execute PaymentDetails when PayPal redirects back to /status sid = str(random.random())[5:] + str(random.random())[5:] + str(random.random())[5:] return_url = self.request.host_url + "/status" + "?sid=" + sid cancel_url = return_url redirect_url = "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=" params = { 'requestEnvelope' : {'errorLanguage' : 'en_US', 'detailLevel' : 'ReturnAll'}, 'actionType' : 'PAY', 'receiverList' : { 'receiver' : [ {'email' : receiver, 'amount' : amount} ], }, 'currencyCode' : 'USD', 'memo' : 'Simple payment example.', 'cancelUrl' : cancel_url, 'returnUrl' : return_url, } result = self._request('https://svcs.sandbox.paypal.com/AdaptivePayments/Pay', params) response = json.loads(result.content) if result.status_code == 200: # OK # Convert back to indented JSON and inject a hyperlink to kick off payment approval pretty_json = json.dumps(response,indent=2) pretty_json = pretty_json.replace(response['payKey'], '<a href="%s%s" target="_blank">%s</a>' % (redirect_url, response['payKey'], response['payKey'],)) memcache.set(sid, response['payKey'], time=60*10) # seconds self.response.out.write('<pre>%s</pre>' % (pretty_json,)) else: self.response.out.write('<pre>%s</pre>' % (json.dumps(response,indent=2),)) def main(): application = webapp.WSGIApplication([('/', MainHandler), ('/(status)', MainHandler)], debug=True) util.run_wsgi_app(application) if __name__ == '__main__': main()
In terms of the overall application flow, here’s how it all breaks down:
The user requests the root context of the application.
A session identifier is created by concatenating some random numbers together.
A Pay
request is executed that requires return
and cancel URLs to be provided so that PayPal knows where to
redirect the user after payment approval.
Details: We’d like for the return URL passed in with the
Pay
request to check the status of the payment
associated with the Pay
request through a
subsequent PaymentDetails
request after the user
has had an opportunity to approve the payment; however, we
won’t have the payKey
value that’s needed for
PaymentDetails
until the Pay
request
completes, and it hasn’t even been executed yet! Thus, we’ll
use the session identifier and specify a return URL of the
form /status?sid=123
on the Pay
request and use memcache
to associate the
sid
value with the payKey
value
that’s returned from the Pay
request after the
Pay
request completes.
Results for the Pay
request are displayed as
JSON with the payKey
hyperlinked such that the user
can click on it and approve the payment, ultimately changing its
status from CREATED
to COMPLETED
.
Details: The hyperlink simply involves passing in the
payKey
as a query string parameter to a standard
URL of the form
https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey=AP-808742956V333525E
.
After the user approves payment, PayPal redirects back to
the application at its return URL, which for this sample
application is of the form /status?sid=123
.
In /status
, the application uses the session
identifier included in the URL to look up the payKey
associated with the transaction and uses it to execute a
PaymentDetails
request. The response, whose
status
field should now be COMPLETED
, is
displayed. Recall that additional details in the response object
provide definitive information regarding the ultimate outcome of
the payment itself.
Because no value for feesPayer
is specified to
override the default value of EACHRECEIVER
, the
receiver pays the fees for this transaction.
Another key point to take away from the application is that the owner of the application need not necessarily be the receiver of the payment. It could certainly be the case that the same owner of the application whose 3-Token credentials are supplied to run the application could also be on the receiving end, but it could just as easily be the case that the owner of the application is a third party who built the application as a fixed-price contract job and maintains the application on behalf of the receiver as part of a business arrangement. However, it’s just as easily the case that perhaps the third party developer could have developed the application for free or at a deep discount in exchange for a cut of the payment. The next section illustrates how a chained payment could be used to accommodate exactly this kind of situation.
If you understand the flow of Example 4-3, the good news is that executing a chained (or parallel payment) literally just requires a couple of additional lines of code. Recall that in a chained payment scenario, the sender perceives that a payment is being sent to a primary receiver; however, the primary receiver essentially acts as a “middle man” who takes a cut and passes on potentially variable portions of the payment to up to five additional receivers. A realistic scenario involving a chained payment could be that the developer of an application takes a cut of a payment and passes on the remaining portion to additional parties such as investors who may be stakeholders in the business venture. While a parallel payment could conceivably be used to get the money into the very same hands, a chained payment allows the additional receivers to remain anonymous so far as the sender is concerned. From the sender’s point of view, there is only a single receiver.
As just mentioned, the changes to Example 4-3 that result
in a chained payment are absolutely minimal. Instead of a single
receiver and amount being specified and passed into the
Pay
request, multiple receivers can be passed. For
example, consider the following receiver configuration:
params = { 'requestEnvelope' : {'errorLanguage' : 'en_US', 'detailLevel' : 'ReturnAll'}, 'actionType' : 'PAY', 'receiverList' : { 'receiver' : [ {'email' : receiver1, 'amount' : amount1, 'primary' : True }, {'email' : receiver2, 'amount' : amount2, 'primary' : False}, {'email' : receiver3, 'amount' : amount2, 'primary' : False} ], }, 'currencyCode' : 'USD', 'memo' : 'Chained payment example.', 'cancelUrl' : cancel_url, 'returnUrl' : return_url, }
This configuration specifies that there is one primary receiver
and two secondary receivers. If amount1
were $10.00,
amount2
were $5.00, and amount3
were $2.00,
the primary receiver would be accepting a $10.00 payment but passing
on $7.00 of it to secondary
receivers—effectively taking a $3.00 cut. An important detail to also
note is that because no value for
feesPayer
is specified to override the default value of
EACH
RECEIVER
, all receivers, including
the primary receiver, pay the fees for this
transaction.
Modifications to Example 4-3 that result
in parallel payment are quite similar to those for a chained payment
except that there is no designated primary receiver and the party who
was the primary receiver takes an explicit cut of the payment in the
parameters. Using the same configuration parameters as with the
chained payment, the only code change required is that
receiver1
no longer be designated as the primary
receiver. However, in order for the same payment amounts to go to the
receivers in the same manner as the chained payment scenario,
amount1
would be an explicit $3.00 instead of $10.00.
From the sender’s point of view, there are three receivers involved
with the parallel payment, and the sender has visibility into how much
of the payment is given to each of the receivers.
params = { 'requestEnvelope' : {'errorLanguage' : 'en_US', 'detailLevel' : 'ReturnAll'}, 'actionType' : 'PAY', 'receiverList' : { 'receiver' : [ {'email' : receiver1, 'amount' : amount1, 'primary' : False}, {'email' : receiver2, 'amount' : amount2, 'primary' : False}, {'email' : receiver3, 'amount' : amount2, 'primary' : False} ], }, 'currencyCode' : 'USD', 'memo' : 'Parallel payment example.', 'cancelUrl' : cancel_url, 'returnUrl' : return_url, }
As with the prior examples, all receivers pay their own portion
of the fees since no value for feesPayer
has been
provided to override the default value of
EACHRECEIVER
.