Adaptive Payments with PayPal
When building B2C systems the common scenario is to share the profits among the portal and the business users eg. when renting a house through you website the user pays total amount for the house, lets say 100 USD to the property owner. From that amount the owner will pay share commission to the portal’s website usually a small %.
In order to automate this process we can use PayPal Adaptive payments (chained transaction). The main difference between PayPal chain transaction and parallel transaction is that with the chained transaction the user sees only the total amount that he owns to the house owner. With the parallel transactions the user pays total amount but he sees the list of 2 receivers; the house owner and the portal owner share details.
Although there is a good documentation available on the Paypal website I found it very hard to get a working example of the payment implementation. That’s why I decided to post it here.
Lets start with downloading PayPal SDK. It contains all classes we need to make payment request. I have also included project and SDK at the bottom of this article.
When making a payment request we basically need to create paykey and redirect user to the payment page by passing that key as a parameter.
This simply creates initial payment record on PayPal account. The payment then may be finished or cancelled by the user. We get the paykey after getting response from PayPal with the status “CREATED”.
if (ap.isSuccess.ToUpper() != "FAILURE") { if (PResponse.paymentExecStatus == "CREATED") { return PResponse.payKey; } }
In the init request we need to include basic parameters such as portal and property owner PayPal accounts, api credentials (sandbox account for testing)
var OrderID = Guid.NewGuid().ToString(); var portalPayPalEmail = ConfigurationManager.AppSettings["PORTAL_PAYPAL_EMAIL"]; payRequest.ipnNotificationUrl = ConfigurationManager.AppSettings["PAYPAL_NOTIFICATION_URL"] + "?custom=" + OrderID; // to be processed later in PayPal-Call-back.ashx handler payRequest.cancelUrl = Request.Url.ToString(); payRequest.returnUrl = Request.Url.ToString(); payRequest.memo = OrderID; payRequest.trackingId = OrderID; payRequest.clientDetails = new ClientDetailsType(); payRequest.clientDetails = ClientInfoUtil.getMyAppDetails(); //helper - gets values from web.config payRequest.actionType = "PAY"; // or CREATE payRequest.currencyCode = "USD";//set currency to user settings payRequest.requestEnvelope = new RequestEnvelope(); payRequest.requestEnvelope = ClientInfoUtil.getMyAppRequestEnvelope(); //helper - gets values from web.config
This is how the config file should look like (please fill in with your data)
<appSettings> <!-- PayPal configuration settings START--> <add key="deviceId" value="testID123"/> <add key="ipAddress" value="127.0.0.1"/> <add key="TrustAll" value="true"/> <add key="ENDPOINT" value="https://svcs.sandbox.paypal.com/"/> <add key="PAYPAL_URL" value="https://www.sandbox.paypal.com/"/> <add key="PAYPAL_REDIRECT_URL" value="https://www.sandbox.paypal.com/webscr?cmd=_ap-payment&paykey="/> <add key="PAYPAL_NOTIFICATION_URL" value="https://your-website-url/PayPal-Call-back.ashx"/> <add key="APPLICATION-ID" value="APP-80W284485P5195333"/> <add key="API_REQUESTFORMAT" value="SOAP11"/> <add key="API_RESPONSEFORMAT" value="SOAP11"/> <add key="API_AUTHENTICATION_MODE" value="3TOKEN"/> <add key="PORTAL_PAYPAL_EMAIL" value="portal-paypal-email"/> <add key="API_USERNAME" value="api-username"/> <add key="API_PASSWORD" value="api-password"/> <add key="API_SIGNATURE" value="api-signature eg.AFcWxV21C7fd0v3bYYYRCpSSRl31AI0QDNGia62b0stzcLxkJCYRE9JE"/> <!-- PayPal configuration settings END--> </appSettings>
Next we need to define who will pay the PayPal commission, it can be one of the receivers or sender or all can be paying equally
payRequest.feesPayer = "EACHRECEIVER"; //SENDER,PRIMARYRECEIVER,SECONDARYONLY
Next step is to create receiver list. It can include up to 6 accounts. In our case we have only portal user and the property owner. The total amount of 100 USD will be split to 80 USD for the property owner and 20 USD for portal (minus PayPal commission taken from both receivers)
payRequest.receiverList = new Receiver[2]; //pay the user payRequest.receiverList[0] = new Receiver(); payRequest.receiverList[0].amount = 100; payRequest.receiverList[0].primary = IsChainedTransaction;// set to true to switch to chained transaction payRequest.receiverList[0].primarySpecified = IsChainedTransaction;// set to true to switch to chained transaction payRequest.receiverList[0].paymentType = "SERVICE"; payRequest.receiverList[0].email = "portal-user-paypal-email";//set user paypal email //pay portal share payRequest.receiverList[1] = new Receiver(); payRequest.receiverList[1].amount = 20; payRequest.receiverList[1].primary = false; payRequest.receiverList[1].primarySpecified = false; payRequest.receiverList[1].paymentType = "SERVICE"; payRequest.receiverList[1].email = portalPayPalEmail;
At the end we need to supply credentials and send payment request.
AdapativePayments ap = new AdapativePayments(); ap.APIProfile = ClientInfoUtil.CreateProfile(); //helper - gets values from web.config PayResponse PResponse = ap.pay(payRequest);
The last step is to check if payment was successful and process the order in our application. In the web.config we defined PAYPAL_NOTIFICATION_URL as handler page PayPal-Call-back.ashx. So in the handler when we receive an callback we simply validate it by sending back request to PayPal. When the request is validated we can proceed with our order.
public void ProcessRequest(HttpContext context) { Request = context.Request; //Request["custom"] contains our orderID if (IsRequestFromPaypal() && Request["custom"] != null) { var orderID = Request["custom"]; //check the payment_status is Completed //check that orderID has not been previously processed //check that receiver_email is your Primary PayPal email //check that payment_amount/payment_currency are correct //process payment } } //validate request by sending it back to PayPal bool IsRequestFromPaypal() { var validationRequestContent = Request.Form + "&cmd=_notify-validate"; var validationUrl = System.Configuration.ConfigurationManager.AppSettings["PAYPAL_URL"].TrimEnd('/') + "/cgi-bin/webscr"; // Create a Web Request: var validationRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(validationUrl); validationRequest.ContentLength = validationRequestContent.Length; validationRequest.ContentType = "application/x-www-form-urlencoded"; validationRequest.Method = "POST"; using (var writer = new StreamWriter(validationRequest.GetRequestStream(), System.Text.Encoding.ASCII)) { writer.Write(validationRequestContent); } // Now send the request and get the response: using (var response = validationRequest.GetResponse()) { using (var reader = new StreamReader(response.GetResponseStream())) { var paypalResponse = reader.ReadToEnd(); return paypalResponse == "VERIFIED"; } } }
I have included working example below so you can use it in your testing. Just fill it in with your data. You also need to be logged in to your sandbox account when testing it.
AdaptivePayments