Passing 'Custom' Value back from Paypal - php

I'm attempting to pass order IDs to Paypal from an order page, then back to the thanks page so I can mark an order as paid, but I'm running into problems. Currently I'm passing these variables to Paypal like this:
$vars = array(
'cmd' => "_cart",
'upload' => "1",
'business' => "paypalemail#gmail.com",
'item_name_1' => "Test Item",
'amount_1' => "1.00",
'return' => "http://www.website.com/thanks.php",
'custom' => $order_id,
);
header('Location: https://www.paypal.com/cgi-bin/webscr?' . http_build_query($vars));
When they payment clears and I'm redirected, it doesn't appear that the order id is being sent back, however. This is the url I'm given:
thanks.php?tx=61G92224EW780500P&st=Completed&amt=1.00&cc=USD&cm=57&item_number=
I expected to see a "custom=" followed by the order number somewhere in this string. But it isn't present. Did I do something wrong?

Working on the same today :-)
You obviously want to use PDT instead of IPN, there are in fact some cases when this makes sense (e.g. small stores). PayPal PDT returns ONLY the tx value in GET format. It then expects that you construct a POST containing:
tx you just received
id token of the account
cmd with the fixed value "_notify-synch"
Only this post is answered with a body containing the variables, one each line.
See https://cms.paypal.com/cms_content/en_US/files/developer/PP_OrderMgmt_IntegrationGuide.pdf page 19.

The reason you are not getting the 'custom' GET parameter is because for some reason on the return URL, PayPal renames it to 'cm' so in the original example above, I assume that the custom parameter had been sent to PayPal will a value of 57 and you can see cm=57 in the return URL.
I think the custom parameter is still called 'custom' in the IPN call.

Related

How to send funds from one personal PayPal account to another in PHP

I tried use checkout api in Paypal to send funds from one personal account to another, But after clicking the continue button, refreshed checkout page and didnt transfer the funds.
Here is the PHP code what I did:
private function sendDirectlyPayment(float $amount, string $currency, string $address, string $backUrl): array
{
$params = [
'intent' => 'CAPTURE',
'purchase_units' => [
[
'amount' => [
'currency_code' => $currency,
'value' => (string) $amount
],
'payee' => [
'email_address' => $address
]
]
]
];
$data = $this->encodeData($params);
$headers = $this->getAuthHeaders($data);
$res = $this->execute(self::POST, '/v2/checkout/orders', $data, $headers);
return $res;
}
Not sure how to make to work api properly.
You set up the payment correctly, and the payer is approving the payment. But you are missing the final 'capture' API step, so no transaction is created. You need to implement the v2/checkout/orders/.../capture API call after approval.
The best way is to make two callable routes on your server, one for 'Create Order' and one for 'Capture Order', documented here. These routes should return only JSON data (no HTML or text). The latter one should (on success) store the payment details in your database before it does the return (particularly purchase_units[0].payments.captures[0].id, the PayPal transaction ID)
Pair those two routes with the following approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
----
The smart button gives the best user experience -- however, if for some reason your use case is better without it, then what you need to do is specify a return_url in the create order call. When the payer clicks continue and reaches the return_url, it is there when you would display an order review page and have a button that triggers the capture API call for the final action.
If you want to skip having a review page and do the capture immediately, this is also possible to do, but you need to set the application_context object's user_action parameter to PAY_NOW, so that the last button has appropriate text instead of 'Continue'.

PayPal IPN POST results in 0 PHP Laravel

I'm struggling with a simple storage script that should be called via PayPal IPN to save the current payment.
I'm using
$input = json_encode($request->all());
$paylog = Paylog::create([
'data' => $input,
]);
and the result from any incoming POST is always "0".
Where is my fault?
Perhaps you mean to access $_POST
IPN messages should be verified with a postback to ipnpb.paypal.com before doing anything with their contents
IPN is a very old and clunky technology, why are you using it?
To obtain details on a 'current' payment, you should be using an API like v2/checkout/orders to facilitate the checkout. For this you need two routes on your server, one for 'Set Up Transaction' and one for 'Capture Transaction', documented here: https://developer.paypal.com/docs/checkout/reference/server-integration/
The best approval flow to pair with your two routes is https://developer.paypal.com/demo/checkout/#/pattern/server
Solution:
//Make sure encode values to UTF8
$input = json_encode(array_map('utf8_encode',$request->all()));
//And store
$paylog = Paylog::create([
'data' => $input,
]);
Or to make things a little more efficient:
$paylog = Paylog::create([
'data' => $json_encode(array_map('utf8_encode',$request->all())),
]);

Description of the item is never actually displayed to the end use via Coinbase API

I am trying to create a simple payment system for bitcoins using coinbase API and this lovely script I found on github
Everything works smoothly, but I would like to be able to take the users delivery address as well.
I am using the following to create a payment button:
$response = $coinbase->createButton("This is an item", "$amount", "BTC", "Trackcode", array(
"description" => "Item Description here"
));
echo $response->embedHtml;
I noticed that the description of the item is never actually displayed to the end user.
"description" => "Item Description here"
I am thinking this may be used as a reference for the merchant once a transaction has taken place. If this is true, I could take advantage of this by having the users delivery address parsed in to the "item description". This should then be displayed to me in my merchant account over at coinbase.
I know I could quite easily test this myself by making a transaction, but my "wallet" is empty at the moment.
Does anyone with any experience with coinbase know if this is true?
I found the official Coinbase script quite unsuccesfull, so I wrote my own.
Hope this helps...
https://github.com/rjmacarthy/coinbase-api-php
Have you tried using their api library? https://github.com/coinbase/coinbase-php It has a nice documentation and is almost error free. It should be fairly simple if you use this api. Here is the sample provided for payment buttons in the github doc.
$response = $coinbase->createButton("Your Order #1234", "42.95", "EUR", "my custom tracking code for this order", array(
"description" => "1 widget at €42.95"
));
echo $response->button->code;
// '93865b9cae83706ae59220c013bc0afd'
echo $response->embedHtml;
// '<div class=\"coinbase-button\" data-code=\"93865b9cae83706ae59220c013bc0afd\"></div><script src=\"https://coinbase.com/assets/button.js\" type=\"text/javascript\"></script>'
The description parameter is only displayed on Coinbase's Payment Pages. The custom parameter (4th argument in createButton function) is recommended for returning data after payment is made, however, the description parameter will also be returned in the Callback response for Payment Buttons or Payment iFrames.
Not sure if this helps, but Coinbase also allows an include_address parameter (boolean) which prompts the buyer for their shipping address before displaying the payment options.
Parameter Reference: https://coinbase.com/api/doc/1.0/buttons/create.html

How to protect sensitive pages for paypal payment processing?

I'm using the paypal express checkout, which follows this flow:
When they submit on the paypal website it follows a return url on my site which shows the order review with a confirm button, and two GET variables are passed back from paypal: token and payerId. The token gives me permission to request shipping info & later finalize the payment.
The first problem is I can access the 'checkout with paypal' page directly by typing in the URL into the address bar and it will submit the request to paypal, if the $_SESSION['Payment_Amount'] variable is not set it processes with the payment amount as 0 and throws an error.
SetExpressCheckout API call failed. Detailed Error Message: This transaction cannot be processed. The amount to be charged is zero.
I know I can set another session variable on the cart page to make sure they visit the cart first, and then clear the variable after checking for it, but another problem remains that the user only needs to visit the cart page once and the variable will be set to allow them to visit the sensitive page which sends a token request to paypal.
The next problem is that after going through all the steps and the user pressing the 'confirm order' button, the request is sent to paypal to process the order/money for that token. The user can press the 'BACK' button on the page and see the order-review again, then the user can press confirm order again and an error will show that an order was already processed for that token.
GetExpressCheckoutDetails API call failed. Detailed Error Message: A successful transaction has already been completed for this token.
That's clearly a good thing but what should I implement to prevent the user from accessing sensitive pages? Will I need to track certain keys in my back-end database?
At the moment i'm working on localhost with paypal's sandbox.
You have to create a process somehow that guarantees that the user follows the needed steps in the right order and prevents him from jumping out of this order.
Tracking the steps in the users session seems like the natural thing to do. If the session does not allow the step requested, redirect him elsewhere instead of asking paypal.
The deluxe version would be you implemented a state machine for easier improvements later on. State machines have the disadvantage of looking like huge overhead at first, and are too much hassle to implement later if you initially took a different approach. That's why it is important to think about using one from the start.
What if you want to add another payment provider later? A state machine could be easily extended for this - anything else might be a mess then.
Edit:
Actually, the only thing paypal expects you to send to them after the user is back on your site is the amount you want to charge. This info can be passed by putting it into the return url you send to paypal. Try adding some checksum there to prevent data errors and easy tampering (Paypal lets the process fail if the amount is incorrect nevertheless), and you are basically done. No session at all needed.
Edit2: Here is an excerpt of my code that defines the nvp parameters for paypals first step. You need the necessary auth stuff inside, too.
public function preparePayment(...) {
$nvp = array(
'METHOD' => 'SetExpressCheckout',
'VERSION' => '52.0',
'RETURNURL' => 'https://'.$request->server['HTTP_HOST'].'/'.$request->getLanguage().'/paypal/success/'.$this->hashAmount($amount),
'CANCELURL' => 'https://'.$request->server['HTTP_HOST'].'/'.$request->getLanguage().'/paypal/cancel',
'CURRENCYCODE' => $amount->getCurrency(),
'AMT' => number_format($amount->getAmount(), 2, '.', ''),
'ITEMAMT' => number_format($amount->getNettoAmount(), 2, '.', ''),
'TAXAMT' => number_format($amount->getVatAmount(), 2, '.', ''),
'PAYMENTACTION' => 'Sale',
'LOCALECODE' => strtoupper($request->getLanguage())
);
}
protected function hashAmount(Currency_Class $amount) {
return urlencode(
sprintf(
'%s-%s-%s-%u',
number_format($amount->getNettoAmount(), 2, '', ''),
number_format($amount->getVatAmount(), 2, '', ''),
strtoupper($amount->getCurrency()),
$this->makeChecksumString(number_format($amount->getNettoAmount(), 2, '', ''), strtoupper($amount->getCurrency()))
)
);
}
protected function makeChecksumString($amount, $currency) {
return crc32(sprintf('%sSaltValue%s', $amount, $currency));
}
protected function dehashAmount($string) {
$parts = array();
$found = preg_match('/^(\d+)\-(\d+)\-([A-Z]+)\-(\d+)$/', $string, $parts);
if ($found) {
$check = sprintf('%u', $this->makeChecksumString($parts[1], $parts[3]));
if ($check == $parts[4]) {
$netto = floatval(substr($parts[1], 0, -2) .'.'. substr($parts[1], -2));
$vat = floatval(substr($parts[2], 0, -2) .'.'. substr($parts[2], -2));
}
}
return ...
}

Verifying a Paypal transaction via POST information

I'm at a complete loss. I think I might be getting "mis-informed", but I'll try explain my situation as best I can.
The Idea
We have a form for users to purchase credits. Type in credit number,
click pp button.
Upon click of button, a post is made to set the
transaction log information and set it as pending (works fine).
Upon valid post return it continues to submit the paypal form (works also).
The user is redirected to paypal page and makes payment (so far so good).
after payment made, they click the return and are directed toward the "success" page (still working).
upon reaching this page I take in post data from pp (uh oh, here's where it gets sticky)
verify the data is "true" pp data and update the transaction log (HOW!?)
What I'm being told & what i've tried
I was initially going to use IPN to do a post back to paypal to verify the recieved data (ensure it wasn't spoofed), however, I'm being told that for cost purposes and having to setup an "ipn server" we can't use IPN ....
Ok, so I was gonna use PDT, except either I missed a major step in my attempt or it ISNT working right at all because I'm not doing somthing right. Here is where I'm lost, i've tried a dozen different things, including a direct link post, using sparks (for CI) to set the data and call to paypal link, and etc ...
I've looked over every paypal question on here and a half dozen other forums and can't seem to get anything going.
Can anyone "clearly" tell me how I can verify the POST data of a successful paypal transaction and maybe even tell me if i'm being misinformed about the IPN, cause I looked over the docs and I can't find what i've been told, nor can I really find my solution.
I feel stupid, please help.
When your user clicks a PayPal button and goes to PayPal, when they complete the transaction, an IPN POST is made to a URL of your choosing. So you don't have to have another web server.
When the IPN request comes in, PayPal wants you to re-send the entire POST they made to you back to them, including all of the fields, in the exact order, at which point they will return the word 'VERIFIED' or 'INVALID.' If verified, then do whatever it is that you need to do to toggle your txn log from pending to verified. Also, any information you include in your button (your button is actually a form so you can include your own fields) is included in the POST. Useful for keeping a 'transaction id' or some other identifier for mapping back to your transaction.
If the IPN fails it will resend in n+4 minute increments (where n is how long it waited the last time - 4 minutes, next after 8 minutes, next after 12 minutes, etc) for a few days.
Finally made it work correctly thanks to the update in info on IPN.
My solution added the following line to my form:
<input type="hidden" name="notify_url" value="<?= site_url('payment/notifyTest'); ?>">
Then in the notifyTest function i ran this:
$pDat = $this->input->post(NULL, TRUE);
$isSandBox = array_key_exists('test_ipn', $pDat) && 1 === (int)$pDat['test_ipn'] ? TRUE : FALSE;
$verifyURL = $isSandBox ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr';
$token = random_string('unique');
$request = curl_init();
curl_setopt_array($request, array
(
CURLOPT_URL => $verifyURL,
CURLOPT_POST => 0,
CURLOPT_POSTFIELDS => http_build_query(array('cmd' => '_notify-validate') + $pDat),
CURLOPT_RETURNTRANSFER => 0,
CURLOPT_HEADER => 0,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_CAINFO => 'cacert.pem',
));
$response = curl_exec($request);
$status = curl_getinfo($request, CURLINFO_HTTP_CODE);
curl_close($request);
if($status == 200 && $response == 'VERIFIED') {
// SUCCESS
$data = array (
... => ...
);
$this->db->insert('transactions', $data);
}
else {
// FAILED
$data = array (
... => ...
);
$this->db->insert('transactions', $data);
};
THE IMPORTANT DIFFERENCE AS WE FOUND -> DO NOT SET YOUR CURL VARS TO TRUE OR FALSE
USE 0 FOR TRUE AND 1 FOR FALSE, IT MIGHT SOUND STUPID, BUT IT WOIKED!!!

Categories