Integrating with PayPal with no web site - rest

I was thinking about integration in the background without any website other than the payment page as part of a desktop application in c++.
Would it be possible to following the following scenario:
1. Generate the invoice / sale and via REST API obtain some sort of unique ID for the transaction to come.
2. Redirect to Paypal web site to a ad-hoc payment page, using the unique ID.
3. In the background, check every few minutes, via REST API, if the payment was made.

We finally found a way, which is why I publish this answer along with some code (a POC) we have developed. This is a POC for a built-in payment processing engine which allows you to accept payments from any credit card holder (regardless of being a PayPal customer) and pay for unlocking a software product or for specific features.
To process payments you need to apply as a PayPal developer and obtain your own PayPal credentials. You will then receive 2 sets of credentials. One for tests ("sandbox") and the other for real life.
First you can use the Sandbox to test the API
Void InitPayPal(BOOL Sandbox, LPTSTR User, LPTSTR password, LPTSTR signature, LPTSTR successUrl, LPTSTR failedURL)
Sandbox – indicates whether you are testing your integration using PayPal's Sandbox account, or going live.
User – your PayPal user name
Password – your PayPal password
Signature – you PayPal signature
successUrl – a url leading to a web page which you wish to be shown after successful payment.
failedURL – a url leading to a web page which you wish to be shown after failed / cancalled payment.
This function is straight forward:
void InitPayPal(BOOL Sandbox, LPTSTR User, LPTSTR password, LPTSTR signature, LPTSTR successUrl, LPTSTR failedURL, LPWSTR ProductName)
{
m_sandbox = Sandbox;
m_user = User;
m_password = password;
m_signature = signature;
m_SuccessURL = successUrl;
m_FailureURL = failedURL;
m_ProductName = ProductName;
CUR_CHAR = L"$";
SYSTEMTIME st;
GetSystemTime(&st);
g_tPayStart = CTime(st);
InitilizedPaypal = TRUE;
}
Initiating a payment
When you wish to initiate a payment from your program, you call the following function which I wrote which generally build a string (ExpChkoutStr) and use the following PayPal API call:
// Send string to PayPal server
WinHttpClient WinClient1(ExpChkoutStr.GetBuffer());
WinClient1.SetRequireValidSslCertificates(false);
WinClient1.SendHttpRequest(L"GET");
httpResponseContent1 = WinClient1.GetResponseContent();
CString strTransactionRet = UrlDecode(httpResponseContent1.c_str());
The WinHTTP class was developed by Cheng Shi.
The Express Checkout String (ExpChkoutStr) is generated by another function which uses the member variables' values and the transaction details into a single string:
CString result;
result = (m_sandbox) ? PAYPAL_SANDBOX_HTTPS : PAYPAL_REAL_HTTPS;
result += Q_USER;
result += m_user;
result += AND_PASSWORD;
result += m_password;
result += AND_SIGNATURE;
result += m_signature;
result += AND_PAYMENTAMOUNT;
result += strAmount;
result += L"&METHOD=SetExpressCheckout";
result += AND_RETURN_URL;
result += m_SuccessURL;
result += AND_CANCEL_URL;
result += m_FailureURL;
result += AND_VERSION;
result += L"&NOSHIPPING=1";
result += L"&ADDROVERRIDE=0&BRANDNAME=Secured Globe, Inc.";
result += L"&PAYMENTREQUEST_0_DESC=";
result += L"Item name: " + strUnits + L"(" + UnitName + L") ";
result += L"Price: " + strAmount;
result += L"&NOTETOBUYER=Here you can add a note to the buyer";
The result from the PayPal server is a "token" used to figure out a one-time web page (LinkToOpen ) that must be opened in order for the end user to confirm the purchase:
// Extract token from response
CString sToken = ExtractElement(strTransactionRet, L"TOKEN");
if (sToken == L"")
{
wprintf(L"Internal error: (Paypal): no token was generated (%s)", strTransactionRet);
MessageBox(NULL, L"Internal payment processing error", L"", MB_OK);
return FALSE;
}
CString LinkToOpen = (m_sandbox) ? SANDBOX_PAYPAL_CHECKOUT : REAL_PAYPAL_CHECKOUT;
LinkToOpen += L"&token=";
LinkToOpen += sToken;
We then programatically open this one-time web page using the default web browser:
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CString command_line;
command_line.Format(L"cmd.exe /c start \"link\" \"%s\" ", LinkToOpen);
// LinkToOpen
if (!CreateProcess(NULL, // No module name (use command line)
command_line.GetBuffer(),
NULL, // Process handle not inheritable
NULL, // Thread handle not inhberitable
FALSE, // Set handle inheritance to FALSE
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi) // Pointer to PROCESS_INFORMATION structure
)
{
wprintf(L"CreateProcess failed (%d).\n", GetLastError());
// At this stage you would want to mark this transaction as "failed"
return FALSE;
}
Then the rest is to maintain a small database of all pending transactions and follow up each of them until it is either succeed, failed, cancelled or if a timeout has passed.
To extract elements from the PayPal server response, we wrote this small function:
CString ExtractElement(CString EntireString, CString ElementName)
{
CString result = L"";
CString WhatToFind = ElementName + L"=";
int foundToken = EntireString.Find(WhatToFind);
if (foundToken > -1)
{
int EndToken = EntireString.Find(L"&", foundToken);
if (EndToken != -1)
{
result = EntireString.Mid(foundToken + ElementName.GetLength()+1, EndToken - foundToken - ElementName.GetLength()-1);
}
}
return result;
}

Related

Send Money to Paypal Account ASP.Net Server Side Code

I am having a difficult time finding halfway descent documentation or examples on how to send money to another Paypal account.
I have installed the Nuget package PaypalSDK version 1.0.4. I have read the documentation at https://developer.paypal.com/home. I have browsed and tried to implement the sample code at https://github.com/paypal/Checkout-NET-SDK.
The problem I am having is that I am having is that I am not seeing notifications of payments sent or received in my sandbox account. I can successfully execute a checkout with the Javascript button in my shopping cart view. But eventually I want to add the capability to send money from my Paypal business account to another Paypal business account, without the other Paypal Business Account owner having to be logged in to my website.
Does the money recipient have to authorize the money I send, or should it just get deposited into their account once I send it?
Here is my code:
namespace MyShoppingCart.Helpers.Paypal
{
public class CaptureOrderSample
{
static string PayPalClientID = Startup.StaticConfig.GetValue<string>("Paypal:ClientID");
static string PayPalClientSecret = Startup.StaticConfig.GetValue<string>("Paypal:ClientSecret");
public static HttpClient client()
{
// Creating a sandbox environment
PayPalEnvironment environment = new SandboxEnvironment(PayPalClientID, PayPalClientSecret);
// Creating a client for the environment
PayPalHttpClient client = new PayPalHttpClient(environment);
return client;
}
public async static Task<HttpResponse> createOrder(string Email)
{
HttpResponse response;
// Construct a request object and set desired parameters
// Here, OrdersCreateRequest() creates a POST request to /v2/checkout/orders
var order = new OrderRequest()
{
CheckoutPaymentIntent = "CAPTURE",
PurchaseUnits = new List<PurchaseUnitRequest>()
{
new PurchaseUnitRequest()
{
AmountWithBreakdown = new AmountWithBreakdown()
{
CurrencyCode = "USD",
Value = "100.00"
},
Payee = new Payee
{
Email = Email // "payee#email.com"
}
}
}
//,
//ApplicationContext = new ApplicationContext()
//{
// ReturnUrl = "https://www.example.com",
// CancelUrl = "https://www.example.com"
//}
};
// Call API with your client and get a response for your call
var request = new OrdersCreateRequest();
request.Prefer("return=representation");
request.RequestBody(order);
response = await client().Execute(request);
var statusCode = response.StatusCode;
Order result = response.Result<Order>();
Debug.WriteLine($"Status: {result.Status}");
Debug.WriteLine($"Order Id: {result.Id}");
Debug.WriteLine($"Intent: {result.CheckoutPaymentIntent}");
Debug.WriteLine("Links:");
foreach (LinkDescription link in result.Links)
{
Debug.WriteLine($"\t{link.Rel}: {link.Href}\tCall Type: { link.Method}");
}
return response;
}
}
}
And this is currently called from my Orders controller when an order is completed. This is just for testing purposes.
[Authorize]
public async Task<IActionResult> CompleteOrder()
{
var items = _shoppingCart.GetShoppingCartItems();
Models.Order order = await _ordersService.StoreOrderAsync(items);
PrepareSellerEmail(items, order, "You Have a New Order!");
PrepareBuyerEmail(items, order, "Thank You for Your Order!");
await _shoppingCart.ClearShoppingCartAsync(_serviceProvider);
DeleteCartIDCookie();
//OrderRequest request = Helpers.CreateOrderSample.BuildRequestBody("USD", "100.00", "sb-r43z1e9186231#business.example.com");
//var client = Helpers.Paypal.CaptureOrderSample.client();
var result = Helpers.Paypal.CaptureOrderSample.createOrder("sb-r43z1e9186231#business.example.com");
//var response = await PayPalClient.client().execute.(request);
return View("OrderCompleted");
}
The output of the result is:
Status: CREATED
Order Id: 51577255GE4475222
Intent: CAPTURE
Links:
self: https://api.sandbox.paypal.com/v2/checkout/orders/51577255GE4475222 Call Type: GET
approve: https://www.sandbox.paypal.com/checkoutnow?token=51577255GE4475222 Call Type: GET
update: https://api.sandbox.paypal.com/v2/checkout/orders/51577255GE4475222 Call Type: PATCH
capture: https://api.sandbox.paypal.com/v2/checkout/orders/51577255GE4475222/capture Call Type: POST
This is a screen capture from my sandbox account:
Am I supposed to do something else to actually execute the transfer?
Edit: I figured out how to use the Paypal Payouts API.
First I installed the Nuget Package. It's simply called PayoutsSdk. I'm using version 1.1.1.
For the payout to execute, you need the client() method that is listed above in this post, and this CreatePayout() method listed below.
public async static Task<HttpResponse> CreatePayout()
{
var body = new CreatePayoutRequest()
{
SenderBatchHeader = new SenderBatchHeader()
{
EmailMessage = "Congrats on recieving 1$",
EmailSubject = "You recieved a payout!!"
},
Items = new List<PayoutItem>()
{
new PayoutItem()
{
RecipientType="EMAIL",
Amount=new Currency()
{
CurrencyCode="USD",
Value="1",
},
Receiver="sb-r43z1e9186231#business.example.com",
}
}
};
PayoutsPostRequest request = new PayoutsPostRequest();
request.RequestBody(body);
var response = await client().Execute(request);
var result = response.Result<CreatePayoutResponse>();
Debug.WriteLine($"Status: {result.BatchHeader.BatchStatus}");
Debug.WriteLine($"Batch Id: {result.BatchHeader.PayoutBatchId}");
Debug.WriteLine("Links:");
foreach (PayoutsSdk.Payouts.LinkDescription link in result.Links)
{
Debug.WriteLine($"\t{link.Rel}: {link.Href}\tCall Type: {link.Method}");
}
return response;
}
Of course I'll add parameters to the method for email, amount, currency code, email message, and subject.
Right now, I am calling this method from the controller method like this: var result = Helpers.Paypal.CaptureOrderSample.CreatePayout(); where Helpers.Paypal are folders that contain a class called CaptureOrderSample, which I will probably rename.
To send money from your account to another account, there are several different options:
Automate the sending with the Payouts API or Payouts Web (spreadsheet upload). For live, payouts can only be used if the live account sending the payment is approved for payouts.
Log into the account that is going to send the money in https://www.paypal.com or https://www.sandbox.paypal.com and click on the menu for Pay & Get Paid -> Send Money .
Use a PayPal Checkout integration, with or without the Orders API, and specify a payee that is to receive the money. You must log in with the paying (sending) account to approve the sending, and finally the order must be captured (via API or client side actions.order.capture()) which is what results in a PayPal transaction. If the final capture step is not performed, no money will be sent and the order will merely remain created or approved and eventually expire (72 hours after creation or 3 hours after approval)
In the sandbox, no actual emails are sent with notifications. Instead, the developer.paypal.com dashboard has a "Notifications" tab on the left, and of course activity will also be visible in each sandbox account by logging into the account. Only captured activity is likely to be visible.

Get Paypal parameters in cancel_return

I want to get a parameter from Paypal which I am sending in 'custom' named hidden field. I can get the same on success page but not in cancel page. Does it need some efforts or Paypal do not provide any value on cancel page?
Which language are you using to Integrate paypal?
I am using Java application. And I can tell you how I have approached this one. And I used Paypal Payments Pro Hosted Checkout solution. I guess you are using the same too.
Logging into the manager paypal account, we can actually configure the Success, Failure and Cancel url.
For example let us say the below are the urls we have configured
paySuccessUrl = "http://mywebsite.com/ecommerce/paySuccess.htm";
payFailureUrl = "http://mywebsite.com/ecommerce/payFailure.htm";
payCancelUrl = "http://mywebsite.com/ecommerce/payFailure.htm";
We can only have one url for success, one for failure and one for cancel. But actually since the user can initiate payment transaction in various pages, it will be difficult for us to have only one single url per status.
So to approach this, I put the urls in the Java Session object.
For example : if user is in page a and initiated a payment transaction , what I typically do is
url a : http://mywebsite.com/ecommerce/a.htm
String successUrl = "http://mywebsite.com/eccommerce/successA.htm";
String failureUrl = "http://mywebsite.com/ecommerce/failureA.htm";
String cancelUrl = "http://mywebiste.com/ecommerce/cancelA.htm";
// Here append as many no of parameters as you wish
Example : cancelUrl = cancelUrl + "prm1=" + val1 + "&par2=" + val2;
For url b
// url b : http://mywebsite.com/ecommerce/b.htm
// String successUrl = "http://mywebsite.com/eccommerce/successB.htm";
// String failureUrl = "http://mywebsite.com/ecommerce/failureB.htm";
// String cancelUrl = "http://mywebiste.com/ecommerce/cancelB.htm";
HttpSessionn session = (HttpSession)request.getSession(true);
session.setAttribute("successUrl", successUrl);
session.setAttribute("failureUrl", failureUrl);
session.setAttribute("cancelUrl", cancelUrl);
So if pay is success
#RequestMapping("paySuccess.htm")
public void paySuccess(HttpServletRequest request, HttpServletResponse response) {
// Do some processing
HttpSession session = (HttpSession) request.getSession(true);
String successUrl = (String) session.getAttribute("successUrl");
response.sendRedirect(successUrl);
// which intern call "http://mywebsite.com/eccommerce/successA.htm" url
// or "http://mywebsite.com/eccommerce/successB.htm"
}
So if pay is failed then
#RequestMapping("payFailure.htm")
public void paySuccess(HttpServletRequest request, HttpServletResponse response) {
// Do some processing
HttpSession session = (HttpSession) request.getSession(true);
String failureUrl = (String) session.getAttribute("failureUrl");
response.sendRedirect(failureUrl);
// which intern call "http://mywebsite.com/eccommerce/failureA.htm" url
// or "http://mywebsite.com/eccommerce/failureB.htm"
}
And similarly for the pay cancel url
#RequestMapping("payCancel.htm")
public void paySuccess(HttpServletRequest request, HttpServletResponse response) {
// Do some processing
HttpSession session = (HttpSession) request.getSession(true);
String cancelUrl = (String) session.getAttribute("cancelUrl");
response.sendRedirect(cancelUrl);
// which intern call "http://mywebsite.com/eccommerce/cancelA.htm" url
// or "http://mywebsite.com/eccommerce/cancelB.htm"
}
So this way you don't need to send any custom parameters to paypal and expect paypal to send them back.
You can have your own cacel url in the session object with as many number of parameters appended as you wish.
I hope this helps you.

paypal masspay allways returning success, even with inexisting users

I'm currently testing my paypal integration. When using Masspay, every user name that I enter who hace a mail-like format(an # and a ".") I get a "success" response. I'm using the following code:
def emails =[]
def amounts = []
emails[0]=params.cuenta;
amounts[0]=params.cantidad;
//masspay.massPayCode("Dinero recibido de TikCode.com", "info#tikcode.com", emails, amount, "EUR");
String emailSub = "Dinero recibido de TikCode.com";
String emailAddress = "info#tikcode.com";
String [] receiverEmailItems = emails;
String [] amountItems = amounts;
String currencyCode = "EUR";
CallerServices caller = new CallerServices();
String responseValue = null;
APIProfile profile = ProfileFactory.createSignatureAPIProfile();
/*
WARNING: Do not embed plaintext credentials in your application code.
Doing so is insecure and against best practices.
Your API credentials must be handled securely. Please consider
encrypting them for use in any production environment, and ensure
that only authorized individuals may view or modify them.
*/
// Set up your API credentials, PayPal end point, and API version.
profile.setAPIUsername("myusername");
profile.setAPIPassword("mypasswordapi");
//profile.setSignature("AFcWxV21C7fd0v3bYYYRCpSSRl31ARykt6evANuQsOANN9TjehZjqIl3");
profile.setSignature("mysignature");
profile.setEnvironment("sandbox");
caller.setAPIProfile(profile);
MassPayRequestType pprequest = new MassPayRequestType();
pprequest.setVersion("90.0");
println("llegas aqui?")
// Add request-specific fields to the request.
MassPayRequestItemType[] massPayItem = new MassPayRequestItemType[receiverEmailItems.length];
int j = 0
for(int i=0;i<receiverEmailItems.length; i++)
{
String recreceiverEmail=receiverEmailItems[i];
if(recreceiverEmail != null && recreceiverEmail.length()!= 0)
{
MassPayRequestItemType massItemReq = new MassPayRequestItemType();
massItemReq.setReceiverEmail(receiverEmailItems[i]);
BasicAmountType amount = new BasicAmountType();
amount.set_value(amountItems[i]);
amount.setCurrencyID(CurrencyCodeType.fromString(currencyCode));
//massItemReq.setUniqueId(uniqueIdItems[i]);
//massItemReq.setNote(noteItems[i]);
massItemReq.setAmount(amount);
massPayItem[j]=massItemReq;
j++;
}
}
pprequest.setEmailSubject(emailSub);
// pprequest.setReceiverType(ReceiverInfoCodeType.fromString("abdel#publidirecta.com"));
pprequest.setMassPayItem(massPayItem);
// Execute the API operation and obtain the response.
MassPayResponseType ppresponse = (MassPayResponseType) caller.call("MassPay", pprequest);
responseValue = ppresponse.getAck().toString();
// println(ex)
// ex.printStackTrace();
return responseValue;
That's as intended; MassPay allows money to be transferred to non-registered users.
These people will then receive an email from PayPal, letting them know that money is waiting for them, and they'll just need to sign up to PayPal and confirm their email address to receive it.
The money will then be available to be withdrawn to a bank account, or spent as PayPal balance.

How to use Paypal adaptive payments with IPN?

I am using the adaptive payment system from Paypal. Using a sandbox account, I was able to make a PayRequest and get forwarded to Paypal to do the payment.
It's then looking like:
Request=
Apr 24, 2012 10:35:46 PM com.paypal.adaptive.api.requests.PayRequest execute
INFO: Sending PayRequest with: requestEnvelope.errorLanguage=en_US&actionType=PAY&receiverList.receiver(0).email=seller_1334320690_biz%40email.org&receiverList.receiver(0).amount=5.0&currencyCode=EUR&feesPayer=SENDER&cancelUrl=https%3A%2F%2Flocalhost%3A8443&returnUrl=http%3A%2F%2Flocalhost%2F&ipnNotificationUrl=http%3A%2F%2Flocalhostu%2Ffinishdeposit&
Response=
Apr 24, 2012 10:35:48 PM com.paypal.adaptive.api.requests.PayPalBaseRequest makeRequest
INFO: Received Response: responseEnvelope.timestamp=2012-04-24T13%3A35%3A48.587-07%3A00&responseEnvelope.ack=Success&responseEnvelope.correlationId=c8dee8023cca6&responseEnvelope.build=2756816&payKey=AP-1UF57245CJ360523K&paymentExecStatus=CREATED
I'm now trying to figure out, how i can check, the payment was successfully completed.
So I tried to implement the ipn system, which works using the sandbox tools.
However, I don't know how to connect the 2 together. i.e. when a payment is made, I am assuming I need to create a record in a database that this user has made a payment, probably as pending/created?
Then wait for the ipn to return to notify me that the payment is made, and update the database table to say complete?
How can i correlate the PayRequest to the IPN-Notification, i'll get from paypal? Paypal is only sending a few information with the IPN-Notification like:
item_number=AK-1234
residence_country=US
verify_sign=ArcmaOINNZx08uC3iQY0zhEQN3IZAz70ynRk93Or8ixRi23bb4rGNIrd
address_country=United States
address_city=San Jose
address_status=unconfirmed
payment_status=Completed
business=seller#paypalsandbox.com
payer_id=TESTBUYERID01
first_name=John
shipping=3.04
payer_email=buyer#paypalsandbox.com
mc_fee=0.44
txn_id=484221854
quantity=1
receiver_email=seller#paypalsandbox.com
notify_version=2.1
txn_type=web_accept
test_ipn=1
payer_status=verified
mc_currency=USD
mc_gross=12.34
custom=xyz123
mc_gross_1=9.34
payment_date=11:54:48 Apr 22, 2012 PDT
charset=windows-1252
address_country_code=US
address_zip=95131
address_state=CA
tax=2.02
item_name=something
address_name=John Smith
last_name=Smith
payment_type=instant
address_street=123, any street
receiver_id=TESTSELLERID1
I cant find something usable in that IPN-Notifcation. The best would be if i could get the same correlation-id with the IPN-Notification i already got with the pay-response. So i could save the response-correlation-id on my database and then check against it if i receive the IPN-Notification with the same correlation-id.
The test IPN they give you in the sandbox is terrible. Look at a real one triggered to your actual callback (even a test), and you'll see it has the payKey defined; this is what you use to look it up.
Note that they require port 80 for the IPN callback (though that's not documented anywhere).
Here's a real IPN notification (translated to JSON, info specific to my app redacted):
{"payment_request_date":"Sun Jun 24 06:12:20 PDT 2012",
"return_url":"http://redacted/paypal/transactions/3?status=completed",
"fees_payer":"EACHRECEIVER",
"ipn_notification_url":"http://redacted/paypal/notifications",
"sender_email":"redacted",
"verify_sign":"AFcWxVredacted",
"test_ipn":"1",
"transaction[0].id_for_sender_txn":"redacted",
"transaction[0].receiver":"redacted",
"cancel_url":"http://redacted/paypal/transactions/3?status=canceled",
"transaction[0].is_primary_receiver":"false",
"pay_key":"AP-redacted",
"action_type":"PAY",
"transaction[0].id":"redacted",
"transaction[0].status":"Completed",
"transaction[0].paymentType":"SERVICE",
"transaction[0].status_for_sender_txn":"Completed",
"transaction[0].pending_reason":"NONE",
"transaction_type":"Adaptive Payment PAY",
"transaction[0].amount":"USD 1.00",
"status":"COMPLETED",
"log_default_shipping_address_in_transaction":"false",
"charset":"windows-1252",
"notify_version":"UNVERSIONED",
"reverse_all_parallel_payments_on_error":"true"}
Note that you have to set reverse_all_parallel_payments_on_error in the PAY request manually. Even though they recommend doing so (and it'll probably save you angst) it's false by default.
Also, you can use PaymentDetails to get all the same info directly if you miss the IPN.
I don't know what #swade1987 was looking at, but my IPNs don't include any info about the fee amount. (That's actually how I found this post; trying to figure out why. The PP API documentation is horrid.)
Additional documentation can be found here https://developer.paypal.com/docs/classic/adaptive-payments/integration-guide/APIPN/
A bit late but for whoever bumps into here from a search engine...
I just started dealing with Paypal APIs myself lately. The IPN message the OP is quoting is the one delivered at the IPN notification URL defined in the seller profile. In contrast, the IPN quoted by #sai, is the Adapative Payments IPN, delivered to the ipnNotificationUrl defined in the Pay, ExecutePaypement or Preapproval API requests.
They are two different types of IPN messages and are documented, look for Payment Information Variables and Pay/Preapproval Message Variables. You can get both types of IPNs if you opt for both of them.
Concerning the IPN message quoted by the OP, you can use the value of txn_id field to get PaymentDetails by transactionId. The transationId is as good as the payKey to reference a completed payment.
This should help you massively.
namespace Gateway
{
public class MerchantSellerIPNService : IMerchantSellerIPNService
{
/// <summary>
/// This is the method which is hit when using the URL in the PAY request to PayPal.
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
public string ProcessIPN(Stream stream)
{
// Declare locally used variables.
byte[] requestArray = null;
string requestString = null;
string responseString = null;
StreamReader IPNReturnReader;
StreamWriter streamWriter;
MemoryStream responseStream = new MemoryStream();
HttpWebRequest payPalRequest;
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
// Get the URL to send the IPN received back to PayPal (use either of the two below depending on the environment.)
<add key="PAYPAL_IPN_URL" value="https://www.sandbox.paypal.com/cgi-bin/webscr" />
<add key="PAYPAL_IPN_URL" value="https://www.paypal.com/cgi-bin/webscr"/>
string IPNReturnURL = ConfigurationManager.AppSettings["PAYPAL_IPN_URL"];
// Read in the data provided from PayPal
StreamReader streamReader = new StreamReader(stream);
// Obtain the email address and pre-approval key passed to use via PayPal for later use.
string strPayPalMessage = streamReader.ReadToEnd();
// Initalize the POST web request we are going to send to PayPal to valid the IPN we received from them.
payPalRequest = (HttpWebRequest)WebRequest.Create(IPNReturnURL);
payPalRequest.Method = "POST";
payPalRequest.ContentType = "application/x-www-form-urlencoded";
// Create an array containing the IPN message PayPal sent to us.
requestArray = encoding.GetBytes(strPayPalMessage);
// Then add the necessary string to the back to use for verfication.
requestString = Encoding.ASCII.GetString(requestArray);
requestString += "&cmd=_notify-validate";
payPalRequest.ContentLength = requestString.Length;
// Now write the updated IPN message back to PayPal for verification.
streamWriter = new StreamWriter(payPalRequest.GetRequestStream(), System.Text.Encoding.ASCII);
streamWriter.Write(requestString);
streamWriter.Close();
// Read the response from PayPal and process it.
IPNReturnReader = new StreamReader(payPalRequest.GetResponse().GetResponseStream());
responseString = IPNReturnReader.ReadToEnd();
IPNReturnReader.Close();
if (responseString == "VERIFIED")
{
try
{
if (strPayPalMessage.Contains("payment_status=Completed"))
{
if (ProcessPaymentIPNMessage(strPayPalMessage))
PayPalInStore.Utils.ErrorHandler.LogError(new Exception("ProcessPaymentIPNMessage - Able to create new payment Transaction Detail Record"), "DEBUG");
else
PayPalInStore.Utils.ErrorHandler.LogError(new Exception("ProcessPaymentIPNMessage - Unable to create new payment Transaction Detail Record"), "DEBUG");
}
else if (strPayPalMessage.Contains("payment_status=Refunded"))
{
if (ProcessRefundIPNMessage(strPayPalMessage))
PayPalInStore.Utils.ErrorHandler.LogError(new Exception("ProcessRefundIPNMessage - Able to create new refund Transaction Detail Record"), "DEBUG");
else
PayPalInStore.Utils.ErrorHandler.LogError(new Exception("ProcessRefundIPNMessage - Unable to create new refund Transaction Detail Record"), "DEBUG");
}
else
{
PayPalInStore.Utils.ErrorHandler.LogError(new Exception("MerchantSellerIPNService - ProcessIPN - Unknown message type"), "DEBUG");
}
}
catch (Exception ex)
{
PayPalInStore.Utils.ErrorHandler.LogError(new Exception("MerchantSellerIPNService - ProcessIPN failed"), "DEBUG");
}
}
else if (responseString == "INVALID")
{
PayPalInStore.Utils.ErrorHandler.LogError(new Exception("MerchantSellerIPNService - Invalid IPN Message Received: "), "DEBUG");
}
else
{
PayPalInStore.Utils.ErrorHandler.LogError(new Exception("MerchantSellerIPNService - Fatal IPN Message Received: "), "DEBUG");
}
return "MerchantSellerIPNService Completed";
}
/// <summary>
/// Method used to process the Payment IPN notification message and update the database as required.
/// </summary>
/// <returns></returns>
private bool ProcessPaymentIPNMessage(string PayPalIPNMessage)
{
// Firstly, we need to split the IPN message into sections based on the & sign.
string[] PayPalMessageElemetsArray = PayPalIPNMessage.Split('&');
// Now obtain the list of information (from the message) we require to make the TransactionDetail record.
string merchantTransactionId = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("txn_id=", StringComparison.Ordinal));
string feeAmount = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("mc_fee=", StringComparison.Ordinal));
string grossAmount = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("mc_gross=", StringComparison.Ordinal));
// TODO: REMOVE THIS ITS FOR DEBUGGING PURPOSES
string errorMessage2 = String.Format("ProcessPaymentIPNMessage - merchantTransactionId: {0}, feeAmount: {1}, grossAmount: {2}", merchantTransactionId, feeAmount, grossAmount);
PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage2), "DEBUG");
try
{
// We now need to remove the variable name and '=' from the elements so we only have the necessary information.
merchantTransactionId = merchantTransactionId.Replace("txn_id=", "");
feeAmount = feeAmount.Replace("mc_fee=", "");
grossAmount = grossAmount.Replace("mc_gross=", "");
// Now convert the values obtained from the IPN message and calculate the net amount.
decimal dFeeAmount = Convert.ToDecimal(feeAmount);
decimal dGrossAmount = Convert.ToDecimal(grossAmount);
decimal dNetAmount = Math.Round((dGrossAmount - dFeeAmount), 2);
try
{
// Finally create the new transaction fee record.
TransactionDetail transactionDetail = new TransactionDetail();
transactionDetail.MerchantTransactionId = merchantTransactionId;
transactionDetail.Gross = dGrossAmount;
transactionDetail.Fee = Decimal.Negate(dFeeAmount);
transactionDetail.Net = dNetAmount;
transactionDetail.TransactionType = (int)TransactionDetailTransactionType.InStorePayment;
transactionDetail.Save();
}
catch (Exception ex)
{
string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to create new TransactionDetail record for Merchant Transaction ID: {0}", merchantTransactionId);
PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG");
return false;
}
return true;
}
catch (Exception ex)
{
string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to create new TransactionDetail record for Merchant Transaction ID: {0}", merchantTransactionId);
PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG");
return false;
}
}
/// <summary>
/// Method used to process the Refund IPN notification message and update the database as required.
/// </summary>
/// <returns></returns>
private bool ProcessRefundIPNMessage(string PayPalIPNMessage)
{
// Firstly, we need to split the IPN message into sections based on the & sign.
string[] PayPalMessageElemetsArray = PayPalIPNMessage.Split('&');
// Now obtain the list of information (from the message) we require to make the TransactionDetail record.
string merchantTransactionId = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("txn_id=", StringComparison.Ordinal));
string parentTransactionId = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("parent_txn_id=", StringComparison.Ordinal));
string feeAmount = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("mc_fee=", StringComparison.Ordinal));
string grossAmount = Array.Find(PayPalMessageElemetsArray, element => element.StartsWith("mc_gross=", StringComparison.Ordinal));
try
{
// We now need to remove the variable name and '=' from the elements so we only have the necessary information.
merchantTransactionId = merchantTransactionId.Replace("txn_id=", "");
parentTransactionId = parentTransactionId.Replace("parent_txn_id=", "");
feeAmount = feeAmount.Replace("mc_fee=", "").Replace("-", "");
grossAmount = grossAmount.Replace("mc_gross=", "").Replace("-", "");
// Now convert the values obtained from the IPN message and calculate the net amount.
decimal dFeeAmount = Convert.ToDecimal(feeAmount);
decimal dGrossAmount = Convert.ToDecimal(grossAmount);
decimal dNetAmount = Math.Round((dGrossAmount - dFeeAmount), 2);
// Now create the new transaction fee record.
try
{
// Finally create the new transaction fee record.
TransactionDetail transactionDetail = new TransactionDetail();
transactionDetail.MerchantTransactionId = merchantTransactionId;
transactionDetail.Gross = dGrossAmount;
transactionDetail.Fee = Decimal.Negate(dFeeAmount);
transactionDetail.Net = dNetAmount;
transactionDetail.TransactionType = (int)TransactionDetailTransactionType.InStoreRefund;
transactionDetail.Save();
}
catch (Exception ex)
{
string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to create new TransactionDetail record for Merchant Transaction ID: {0}", merchantTransactionId);
PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG");
return false;
}
// Finally update the PurchaseRefund record with the Parent Transaction Id (used as a backup incase the API IPN message for the payment wasn't received).
try
{
PurchaseRefund refund = PurchaseRefund.SingleOrDefault(x => x.RefundTransactionId == merchantTransactionId);
if (refund != null)
{
refund.ParentTransactionId = parentTransactionId;
refund.Save();
}
}
catch (Exception ex)
{
string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to update PurchaseRefund record (Transaction ID: {0}) with Parent Transaction Id: {1}", merchantTransactionId, parentTransactionId);
PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG");
return false;
}
// If all is succesful we can return true.
return true;
}
catch (Exception ex)
{
string errorMessage = String.Format("ProcessPaymentIPNMessage - Unable to create new TransactionDetail record for Merchant Transaction ID: {0}", merchantTransactionId);
PayPalInStore.Utils.ErrorHandler.LogError(new Exception(errorMessage), "DEBUG");
return false;
}
}
}
}

delayed chained payment

Can anyone tell me how to automatically execute a delayed payment (let's say it's 5 days after the primary receiver receive the payment) in a chained payment manner? The key is automatic execution, not having to manually approve and pay the secondary receiver. Please illuminate with some sample code.
I have used "actionType" => "PAY_PRIMARY" so that primary receiver get money.
But how can I code so that secondary receiver get money?
Check this answer for the solution. Basically you just need to execute an ExecutePayment operation with the payKey within 90 days to send the payment to the secondary party.
actionType is PAY_PPRIMARY
you then trigger this payment within 90 days.
Its delayed but not time-elapsed.
https://cms.paypal.com/cms_content/US/en_US/files/developer/PP_AdaptivePayments.pdf
well may be its too late but it will help someone in future for sure.
As we integrated paypal delayed chained payment, you can set a primary account in which all the amount will go and you can also set secondary account in which account will be transfered once they approved by primary account holder.
string endpoint = Constants_Common.endpoint + "Pay";
NVPHelper NVPRequest = new NVPHelper();
NVPRequest[SampleNVPConstant.requestEnvelopeerrorLanguage] = "en_US";
//NVPRequest[SampleNVPConstant.Pay2.actionType] = "PAY";
//the above one is for simple adoptive payment payment
NVPRequest[SampleNVPConstant.Pay2.actionType] = "PAY_PRIMARY";
//the above one for deleayed chained payment
NVPRequest[SampleNVPConstant.Pay2.currencyCode] = "USD";
NVPRequest[SampleNVPConstant.Pay2.feesPayer] = "EACHRECEIVER";
NVPRequest[SampleNVPConstant.Pay2.memo] = "XXXXXXXX";
Now we have to set primary and secondary receivers:
//primary account
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiveramount_0] = TotalAmount;
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiveremail_0] = "XXXx.xxxxx.com";
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiverprimary_0] = "true";
//secondary accounts
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiveramount_1] = (somemoney out of total amount);
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiveremail_1] = "xxxxx.xxxx.com";
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiverprimary_1] = "false";
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiveramount_2] = (somemoney out of total amount);
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiveremail_2] = x.x.com;
NVPRequest[SampleNVPConstant.Pay2.receiverListreceiverprimary_2] = "false";
Don't forget that you have to give a valid paypal account while using delayed chained payment.
Now you get your pay_key which you have to use to execute your payment whithin 90 days so that other secondary receivers get money.
Here is the working code:
String endpoint = Constants_Common.endpoint + "ExecutePayment";
NVPHelper NVPRequest = new NVPHelper();
//requestEnvelope.errorLanguage is common for all the request
NVPRequest[SampleNVPConstant.requestEnvelopeerrorLanguage] = "en_US";
NVPRequest[SampleNVPConstant.ExecutePayment.payKey] = "your pay key";
string strrequestforNvp = NVPRequest.Encode();
//calling Call method where actuall API call is made, NVP string, header value adne end point are passed as the input.
CallerServices_NVP CallerServices = new CallerServices_NVP();
string stresponsenvp = CallerServices.Call(strrequestforNvp, Constants_Common.headers(), endpoint);
//Response is send to Decoder method where it is decoded to readable hash table
NVPHelper decoder = new NVPHelper();
decoder.Decode(stresponsenvp);
if (decoder != null && decoder["responseEnvelope.ack"].Equals("Success") && decoder["paymentExecStatus"].Equals("COMPLETED"))
{
//do something
}
Hope it will help someone.