php paypal Server-side REST integration failure - php

(I'm not able to post code/errors etc because: 'You need at least 10 reputation to post more than 2 links'; and everything contains a lot of links; just spent a couple of hours on a fully detailed post).
I've read similar posts here (and other places) but they have not helped.
I'm basically following:
https://developer.paypal.com/docs/integration/direct/express-checkout/integration-jsv4/server-side-REST-integration/#set-up-your-client
but PayPal's documentation is somewhat chaotic, lacking in some crucial details and mysterious in places. Obviously written by a committee.
In a nutshell I have a PayPal payment id: "id": "PAY-0J356327TH335450NK56Y2PQ",
PayPal say return this from my create payment script. I'm not entirely sure how it should be returned: simply echo "PAY-0J356327TH335450NK56Y2PQ"; or return a json string. Tried both, did not solve anything.
Then there is (from the client side button):
onAuthorize: function(data) {
return paypal.request.post(EXECUTE_PAYMENT_URL, {
paymentID: data.paymentID,
payerID: data.payerID
}).then(function() {
// The payment is complete!
// You can now show a confirmation message to the customer
});
Which I read as it calling my execute payment script with 2 variables (paymentID and payerID), which I need to use in my script.
I've tried the obvious:
$paymentID = $_POST["paymentID"];
$payerID = $_POST["payerID"];
The not so obvious (because I'm assuming it's POSTing data although some docs seem somewhat confused on this point):
$paymentID = $_GET["paymentID"];
$payerID = $_GET["payerID"];
My final attempt of:
$ret = file_get_contents("php://input");
$ar = json_decode($ret, TRUE);
$paymentID = $ar["paymentID"];
$payerID = $ar["payerID"];
None of which made any difference.
The errors I'm getting from PayPal are:
ppxo_no_token_passed_to_payment Object ...
ppxo_unhandled_error Object ...
Error: No value passed to payment
decorate ...
I obviously have a valid access token otherwise I'd not get as far as getting a payment id.
Currently stumped; any ideas/pointers appreciated.
This really should be simple.

I had the same error message.
For me it occured when the javascript code receieved the response from my CREATE_PAYMENT_URL ajax request.
The response I was sending back from the server was not valid JSON so when it came time to execute onAuthorize data.paymentID and data.payerID were not available.
I suggests using console.log(data) just above the return statement in onAuthorize to make sure the data json object has a paymentId and payerId.
If not then it may be that your previoius CREATE_PAYMENT_URL is not returning the payment id.
The responses should be json. For example a valid create payment response:
{"id":"PAY-5T1130394T551090NLGRLACY"}
Hope that helps.

Related

Google pub/sub subscription data doesn't match with the app

I'm trying to listen for subscription changes (new and existing) of my Google Play app on the server. Here's the code I'm using. This uses the google/cloud-pubsub composer package:
$projectId = 'app-name';
$keyFile = file_get_contents(storage_path('app/app-name.json'));
$pubsub = new PubSubClient([
'projectId' => $projectId,
'keyFile' => json_decode($keyFile, true)
]);
$httpPostRequestBody = file_get_contents('php://input');
$requestData = json_decode($httpPostRequestBody, true);
info(json_encode($requestData));
$message = $pubsub->consume($requestData);
info(json_encode($message));
The code above works but the problem is that the data I get doesn't match the one I'm getting in the app side. This is a sample data:
{
"message":{
"data":"eyJ2ZXJ...",
"messageId":"16797998xxxxxxxxx",
"message_id":"1679799xxxxxxxxx",
"publishTime":"2020-12-15T02:09:23.27Z",
"publish_time":"2020-12-15T02:09:23.27Z"
},
"subscription":"projects\/app-name\/subscriptions\/test-subs"
}
If you base64_decode() the data, you'll get something like this:
{
version: "1.0",
packageName: "com.dev.app",
eventTimeMillis: "1607997631636",
subscriptionNotification: {
version: "1.0",
notificationType: 4,
purchaseToken: "kmloa....",
subscriptionId: "app_subs1"
}
}
This is where I'm expecting the purchaseToken to be the same as the one I'm getting from the client side.
Here's the code in the client-side. I'm using Expo in-app purchases to implement subscriptions:
setPurchaseListener(async ({ responseCode, results, errorCode }) => {
if (responseCode === IAPResponseCode.OK) {
const { orderId, purchaseToken, acknowledged } = results[0];
if (!acknowledged) {
await instance.post("/subscribe", {
order_id: orderId,
order_token: purchaseToken,
data: JSON.stringify(results[0]),
});
finishTransactionAsync(results[0], true);
alert(
"You're now subscribed! You can now use the full functionality of the app."
);
}
}
});
I'm expecting the purchaseToken I'm extracting from results[0] to be the same as the one the Google server is returning when it pushes the notification to the endpoint. But it doesn't.
Update
I think my main problem is that I'm assumming all the data I need will be coming from Google Pay, so I'm just relying on the data published by Google when a user subscribes in the app.
This isn't actually the one that publishes the message:
await instance.post("/subscribe")
It just updates the database with the purchase token. I can just use this to subscribe the user but there's no guarantee that the request is legitimate. Someone can just construct the necessary credentials based on an existing user and they can pretty much subscribe without paying anything. Plus this method can't be used to keep the user subscribed. So the data really has to come from Google.
Based on the answer below, I now realized that you're supposed to trigger the publish from your own server? and then you listen for that? So when I call this from the client:
await instance.post("/subscribe", {
purchaseToken
});
I actually need to publish the message containing the purchase token like so:
$pubsub = new PubSubClient([
'projectId' => $projectId,
]);
$topic = $pubsub->topic($topicName);
$message = [
'purchaseToken' => request('purchaseToken')
];
$topic->publish(['data' => $message]);
Is that what you're saying? But the only problem with this approach is how to validate if the purchase token is legitimate, and how to renew the subscription in the server? I have a field that needs to be updated each month so the user stays "subscribed" in the eyes of the server.
Maybe, I'm just overcomplicating things by using pub/sub. If there's actually an API which I could pull out data from regularly (using cron) which allows me to keep the user subscription data updated then that will also be acceptable as an answer.
First of all - I have a really bad experience with php and pubsub because of the php PubSubClient. If your script is only waiting for push and checking the messages then remove the pubsub package and handle it with few lines of code.
Example:
$message = file_get_contents('php://input');
$message = json_decode($message, true);
if (is_array($message)) {
$message = (isset($message['message']) && isset($message['message']['data'])) ? base64_decode($message['message']['data']) : false;
if (is_string($message)) {
$message = json_decode($message, true);
if (is_array($message)) {
$type = (isset($message['type'])) ? $message['type'] : null;
$data = (isset($message['data'])) ? $message['data'] : [];
}
}
}
I'm not sure how everything works on your side but if this part publishes the message:
await instance.post("/subscribe", {
order_id: orderId,
order_token: purchaseToken,
data: JSON.stringify(results[0]),
});
It looks like it's a proxy method to publish your messages. Because payload sent with it is not like a PubSub described schema and in the final message it doesn't look like IAPQueryResponse
If I was in your situation I will check few things to debug the problem:
How I publish/read a message to/from PubSub (topic, subscription and message payload)
I will write the publish mechanism as it is described in Google PubSub publish documentation
I will check my project, topic and subscription
If everything is set-up correctly then I will compare all other message data
If the problem persist then I will try to publish to PubSub minimal amount of data - just purchaseToken at the start to check what breaks the messages
For easier debug:
Create pull subscription
When you publish a message check pull subscription messages with "View messages"
For me the problem is not directly in PubSub but in your implementation of publish/receiving of messages.
UPDATE 21-12-2020:
Flow:
Customer create/renew subscription
Publish to pubsub with authentication
PubSub transfers the message to analysis application via "push" to make your analysis.
If you need information like:
New subscribers count
Renews count
Active subscriptions count
You can create your own analysis application but if you need something more complicated then you have to pick a tool to met your needs.
You can get the messages from pubsub also with "pull" but there are few cases I've met:
Last time I've used pull pubsub returns random amount of messages - if my limit is 50 and I have more than 50 messages in the queue I'm expecting to get 50 messages but sometimes pubsub gives me less messages.
PubSub returned messages in random order - now there is an option to use ordering key but it's something new.
To implement "pull" you have to run crons or something with "push" you receive the message as soon as possible.
With "pull" you have to depend on library/package (or whatever in any language it's called) but on "push" you can handle the message with just few lines of code as my php exapmle.

How to use PayPal Express Payments (PHP)

I am using the PayPal PHP SDK found here: https://github.com/paypal/Checkout-PHP-SDK
And I am somewhat puzzled in terms of how to complete the process.
On the outset this seems quite simple:
Setup your credentials
Create the Order
Check the result, and re-direct to approval link
User makes a payment and is sent to the SUCCESS link that you would have set.
i.e. http://example.com/pay/complete/paypal?token=8UK32254ES097084V&PayerID=SEQNPLB2JR9LY
And this is where things get a bit shakey.
Conveniently, a token and a PayerID is returned.
And according to the documentation, you now need to "Capturing the Order" and the following code is provided:
use PayPalCheckoutSdk\Orders\OrdersCaptureRequest;
// Here, OrdersCaptureRequest() creates a POST request to /v2/checkout/orders
// $response->result->id gives the orderId of the order created above
$request = new OrdersCaptureRequest("APPROVED-ORDER-ID");
$request->prefer('return=representation');
try {
// Call API with your client and get a response for your call
$response = $client->execute($request);
// If call returns body in response, you can get the deserialized version from the result attribute of the response
print_r($response);
}catch (HttpException $ex) {
echo $ex->statusCode;
print_r($ex->getMessage());
}
What is confusing is that the OrdersCaptureRequest requires an "APPROVED-ORDER-ID"
But all that has been returned is a "token" and a "PayerID".
So my question is, what is this APPROVED-ORDER-ID, and where do I get it?
Thank you!
what is this APPROVED-ORDER-ID, and where do I get it
At that moment, sourced from token= . It should correspond to an Order Id you received in the response to your step 2 ("Create the Order")
For step 3, it is better to use no redirects whatsoever. Instead, implement this front-end UI, which offers a far superior in-context experience that keeps your site loaded in the background: https://developer.paypal.com/demo/checkout/#/pattern/server
There is no reason for a modern website to be redirecting unnecessarily

FluidReview/SurveyMonkey Apply - webhook formatting/setup questions

I'm having trouble implementing webhooks in FluidReview (used to be SurveyMonkey Apply). Specifically, I want to send a webhook with the applicant and current application status, triggered upon any change in application state, so that we can update our CRM with the latest status data. The problem is that I can't figure out setup the webhooks in FluidReview, and their documentation is abysmal (Fluid Review Webhooks, Fluid Review Triggers). Can anyone help me out by providing an example of setting up a simple or advanced webhook?
Steps followed thusfar:
1) I have a php endpoint on my wordpress site that uses the following code snippet to save the JSON from a webhook to the error log:
if(isset($_GET['fr-listener']) && $_GET['fr-listener'] == 'fr') {
error_log("fr-listener==fr hook caught!");
if($json = json_decode(file_get_contents("php://input"), true)) {
// if($json = json_decode(file_get_contents("php://input"), true)) {
error_log("JSON found");
error_log(print_r($json,true));
error_log(var_dump($json));
// $data = var_export($json, true);
// error_log("data dump: " + $data);
// print_json($json);
} else {
error_log("no JSON found");
print_r($_POST);
$data = $_POST;
}
}
I can use this to successfully catch webhooks from Stripe (I used the above snippet to help develop my Stripe webhook catcher) and get a look at their JSON contents. When I catch one of the webhooks from FluidReview, I get the "no JSON found" response. This is how I have the webhook set:
My Webhook Action
(URL = https://wfadev.pairsite.com/listen?fr-listener=fr, Method = POST, Request content = {{applicant.email}})
2) I have tried both setting simple and advanced webhooks, and neither of them are producing the JSON output I'd expect.
I did some more testing and it turns out that the "Request content" field is just a blank text field. To have it send JSON data from FluidReview, write it out like this, using the piping variables ("{{variable name}}")
{
"first_name": "{{user.first_name}}",
"last_name":"{{user.last_name}}",
"email":"{{user.email}}",
"application_type":"{{user.}}",
"date":"{{date}}",
"trigger":"{{trigger}}"
}

FB Messenger Bot: Webhook updates not being delivered

i am working with Facebook Checkbox Plugin everything is working fine except facebook is not sending request to my webhook url when Confirming Opt-in.
in facebook docs it is mentioned that
After the opt-in event, we will post a webhook event to your server if the checkbox state was checked. This callback has the same format as the opt-in callback, but instead of a sender field, it has an optin object with a user_ref field.
But it is not sending any data. here is my webhook code
if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] == 'subscribe' && $_REQUEST['hub_verify_token'] == 'verificationtoken') {
echo $_REQUEST['hub_challenge'];
}
$data = file_get_contents("php://input");
$fp = file_put_contents( PROTECH_FBB_DIR.'/data.log', $data);
i have also tried hitting my webhook manually and see if it responds. and it does work perfectly normal, so it means facebook is not posting data or maybe i am doing something wrong?
Any help would be highly appreciated. thanks
I am not php developer but have implemented the same logic in javascript, node.js. I would like to share the steps in detail and also the javascript code and hope you can figure out what you can do with it to make your life better :P
As you said, you are receiving the user_ref from the api call. That's correct. Read the documentation once again they have mentioned the user_ref will be received when user check the checkbox plugin. This user_ref is set by you and every-time the page loads this user_ref must be unique then only the checkbox plugin will render, if it is not unique the plugin wont render. And here is the complete logic behind it. You generate the user_ref, when user check the checkbox, you receive this unqiue user_ref, using this user_ref you send message to the user(you can send message to user using user_ref as many time as you want but I will suggest you rather use senderId). When you send the message to user using user_ref, the webhook api will give you a response containing senderId of the user which is actually psid we normally use in our app. This is what you need to save in your db.
Now I will put my code here how I did it.
Receiving the user_ref and sending message to user:
My payload:
function sendTextMessageRef(user_ref, messageText,md) {
var messageData = {
recipient: {
user_ref: user_ref
},
message: {
text: messageText,
metadata: md
}
};
callSendAPI(messageData);
}
function callSendAPI(messageData) {
request({
uri: 'https://graph.facebook.com/v2.6/me/messages',
qs: { access_token: PAGE_ACCESS_TOKEN },
method: 'POST',
json: messageData
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
var recipientId = body.recipient_id;
var messageId = body.message_id;
if (messageId) {
console.log("Successfully sent message with id %s to recipient %s",
messageId, recipientId);
} else {
console.log("Successfully called Send API for recipient %s",
recipientId);
}
} else {
console.error("Failed calling Send API", response.statusCode, response.statusMessage, body.error);
}
});
}
Now, after sending the message, I receive a response in this json format which will include the sender id of the user:
{"sender":{"id":"xxxxxxx"},"recipient":{"id":"xxxxxWhat you are looking for is this*******"},"timestamp":1504698781373,"message":{"is_echo":true,"app_id":xxxxxxxxxxxxxxx,"metadata":"INVITATION__REPLY__qwe__2017-09-05T02xo20__xxxxxxxx__063__yes","mid":"mid.$cAAGcxxxxxxxxVxuAtJ","seq":120162,"text":":)"}}
In above received json data, the recipient.id is what you are looking for.
Here To make you understand what I did in my chat bot is first user select the checkbox plugin, I receive the call on my server, if check if it contains user_ref, if yes then I send a text message to user with a custom metadata using user_ref. When user receives the message, the webhook send me a json data in the above given format. To identify for which user_ref I have received this response, I set custom metadata which is combination of some string+user_ref. Using this I identify the sender.id of the user for which I previously sent message using user_ref. The sender.id is my pageid and recipient.id the the user id which you are trying to get and using which we generally send message to the user and is also know as psid.
Hope this helps, if you still get some issue using the above mentioned solution, then do update about it :)

Duplicate Request in DoExpressCheckoutPayment (Digital Goods Checkout Express)

Long story short, I believe I've implemented the flow correctly, but on the final DoExpressCheckoutPayment I am getting:
ACK => SuccessWithWarning
L_ERRORCODE0 => 11607
L_SHORTMESSAGE0 => Duplicate Request
L_LONGMESSAGE0 => A successful transaction has already been completed for this token
Is this simply because I'm doing a GetExpressCheckoutDetails request before this? (the GetExpressCheckoutDetails ACK is "Success")
Note that the other data returned from DoExpressCheckoutPayment looks good:
PAYMENTINFO_0_PAYMENTSTATUS => Completed
PAYMENTINFO_0_ACK => Success
Should I just look for PAYMENTINFO_0_ACK and ignore the rest?
Sidenote- In case it's of interest, I'm using the PHP lib at https://github.com/thenbrent/paypal-digital-goods though I changed the stuff in the examples return.php to GetExpressCheckoutDetails on a new class since, of course, it made no sense to use the same purchase data every time and it has to be dynamic
EDIT: Okay I'm baffled. If I only call the GetExpressCheckoutDetails, then the response is:
CHECKOUTSTATUS => PaymentActionNotInitiated
However, if I call GetExpressCheckoutDetails and then DoExpressCheckoutPayment, the response of the preceding GetExpressCheckoutDetails becomes:
CHECKOUTSTATUS => PaymentActionCompleted (and it follows that the result of the subsequent DoExpressCheckoutPayment has the error of Duplicate Request)
How does that even make sense?! Did vanilla PHP just become asynchronous? Has paypal allocated enough money to buy a time machine? I'm probably missing something very basic, but I really don't see it yet :\
EDIT 2 Some Sample Code (didn't strip it to make it 100% vanilla, but should be pretty straightforward):
public static function completePaypalPurchase() {
self::configurePaypal(''); // Not relevent, just some setting of API keys and stuff
$paypalAPI = new PayPal_Purchase(); // Just to get purchase info so we can form the real purchase request
$response = $paypalAPI->get_checkout_details(); // Uses token from GET automatically
echo("RESPONSE FROM GET CHECKOUT");
print_r($response);
$ack = strtoupper($response['ACK']);
$userID = (int)$response['CUSTOM']; // This was passed earlier and is retrieved correctly
$numCredits = (int)$response['L_QTY0'];
//NOTE: If I comment out the below, then the $response above has CHECKOUTSTATUS => PaymentActionNotInitiated
// BUT If I do not comment it out, leaving it as-is then the $response above has CHECKOUTSTATUS => PaymentActionCompleted
// That's the core of the problem and where I'm stuck
if($ack == "SUCCESS" && $numCredits && $userID && $userID == $loggedInUserID) {
$paypalAPI = self::getPaypalPurchaseCredits($userID, $numCredits); // This creates a new PayPal_Purchase() with this info. In fact, it's the same method and therefore should return the same sort of object as the one used at the beginning of the flow
$response = $paypalAPI->process_payment();
$ack = strtoupper($response['ACK']);
echo("RESPONSE FROM DO PAYMENT");
print_r($response);
if(isset($response['PAYMENTINFO_0_TRANSACTIONID']) && $ack == "SUCCESS") {
$transactionID = $response['PAYMENTINFO_0_TRANSACTIONID'];
return(new APIReturn(true, array('ack'=>$ack, 'userid'=>$userID, 'numcredits'=>$numCredits, 'transactionid'=>$transactionID)));
}
}
return(new APIReturn(false, self::ERROR_NORESULT));
}
The correct order the calls is SetExpressCheckout, GetExpressCheckoutDetails, and then DoExpressCheckoutPayment. If you're geting a duplicate order error then you must be calling DECP twice somehow. You need to step through your code and see exactly how that's happening. It may be something in the class that you're using.
On that note, you may be interested in taking a look at my class instead. It makes everything very simple as it turns it all into PHP arrays and handles the gritty work for you.
If you don't want to start over with new class, though, then again, you need to step through what's happening with your code and through the class methods to see where it's getting posted twice.
Another thing I notice is that you're only checking for ACK = Success. That means when ACK = SuccessWithWarning it'll be treated as a failure. You need to handle both Success and SuccessWithWarning (which a decent class library would handle for you.)
Sorry I don't have a more definitive answer, but again, somewhere either in your code or in the library it must be getting posted twice. Are you logging the raw API requests and responses along the way? If so you'd be able to confirm it's getting hit twice because you'd have 2 sets of DECP requests and responses logged.

Categories