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
Related
(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.
I have a browser-based app (single page, AngularJS) and am using hello to use third party signin such as Google, FB, Soundcloud, etc.
My app uses a PHP API server.
What's a good way to have the user able to login using Google, but also verify the user on the server side?
I was considering:
The browser app performs an implicit grant with google/fb/etc
I then transfer the access_token from the client to the server, then use, for example, a google-api-php-client with my app id, secret and the user access_token? Using their API such as /me? (which grant type would this be?)
Retrieve some key from the third-party (facebook_id, email, etc), match it against a user in my database, and then consider the user authenticated?
Also, should I perform this on each API request? Or should I just stash the access_token for a bit and assume that the user is still valid until the key expires?
One issue is that not all of those providers support the implicit flow. But assuming they do, the access_token you get for each will be proof that the user authenticated with that system, not necessarily that they have access to call your API. You still need something that asserts that "someone#gmail.com can 'read' resource X in your system"
You probably need something that translates whatever you get from Google, Soundcloud, etc. into a token your app understands. A simple(r) format is to use JWT. (Json Web Tokens).
App -> Intermmediary -> Soundcloud/Google
<-JWT--+ <---whavetever-+
and then:
App - (JWT) -> API
JWT are easy to manipulate, validate and verify. See jwt.io
You might want to look at this blog post also for some additional information (specifically on AngularJS front-ends)
The blog post #eugenio-pace mentioned was really helpful for setting up the client side.
For the server side though, the access_token should be validated.
The SDK's are (in composer) (code below):
Facebook: "facebook/php-sdk-v4" : "4.0.*"
Google: cURL request (didn't care for "google/apiclient")
SoundCloud: "ise/php-soundcloud": "3.*"
(There are others of course, just these three were the ones I chose, and seem decent.)
Last time I did something like this I made the mistake of validating the access_token on every request, which had a huge (obviously negative) impact on performance. Now I just validate it on login and use it to retrieve the user's ID from that service. So, the browser sends me access_token A and says it's from Facebook, I use the sdk above the the access_token with Facebook, and I get back their ID so I know they are who they say they are.
I'd suggest storing the access_token on the server with the expires_in.
(I haven't dealt with refresh token's yet)
Code to validate tokens using the above libraries:
function validateTokenFacebook($token, $id=null) {
// Performed above
// FacebookSession::setDefaultApplication($config->fb->app_id, $config->fb->secret);
$session = new FacebookSession($token);
// Fetch user info
$request = new FacebookRequest($session, 'GET', '/me');
try {
$response = $request->execute();
} catch (\Facebook\FacebookServerException $e) {
$this->mlog->err($e . "\n" . $e->getTraceAsString());
throw new AuthTokenInvalidException();
}
$graphObject = $response->getGraphObject();
$user_id = $graphObject->getProperty('id');
return array(access_token, $user_id);
}
function validateTokenGoogle($token, $id=null) {
$resp=array();
// This key isn't included in the token from hello.js, but
// google needs it
if (!array_key_exists('created', $token)) $token['created'] = $token['expires'] - $token['expires_in'];
$client = new \Google_Client();
$client->setClientId($this->systemConfig->google->app_id);
$client->setClientSecret($this->systemConfig->google->secret);
$client->setRedirectUri($this->systemConfig->google->redirectUri);
$client->setScopes('email');
$client->setAccessToken(json_encode($token));
try {
// Send Client Request
$objOAuthService = new \Google_Service_Oauth2($client);
$userData = $objOAuthService->userinfo->get();
return array($token['access_token'], $userData['id']);
} catch (\Google_Auth_Exception $e) {
throw new AuthException('Google returned ' . get_class($e));
}
}
function validateTokenSoundcloud($token, $id=null) {
$soundcloud = new \Soundcloud\Service(
$this->systemConfig->soundcloud->app_id,
$this->systemConfig->soundcloud->secret,
$this->systemConfig->soundcloud->redirect);
$soundcloud->setAccessToken($access_token);
try {
$response = json_decode($soundcloud->get('me'), true);
if (array_key_exists('id', $response))
return array($access_token, $response['id']);
} catch (Soundcloud\Exception\InvalidHttpResponseCodeException $e) {
$this->mlog->err($e->getMessage());
}
throw new AuthTokenInvalidException();
}
I have a few custom classes above, such as the Exceptions and the systemConfig, but I think it's verbose enough to communicate what they do.
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.
I've successfully made my way through the LinkedIn OAuth process (using the REST API - OAuth 1.0a). However I'm having trouble with my first API call after the callback. I set the UserToken, UserTokenSecret and UserVerfier in the library I am writing, and this call this function to get my profile information:
public function getUserProfile()
{
$consumer = new OAuthConsumer($this->consumer_key, $this->consumer_secret, NULL);
$auth_token = new OAuthConsumer($this->getUserToken(), $this->getUserTokenSecret());
$access_token_req = new OAuthRequest("GET", $this->access_token_endpoint);
$params['oauth_verifier'] = $this->getUserVerifier();
$access_token_req = $access_token_req->from_consumer_and_token($this->consumer,
$auth_token, "GET", $this->access_token_endpoint, $params);
$access_token_req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),$consumer,
$auth_token);
$after_access_request = $this->doHttpRequest($access_token_req->to_url());
$access_tokens = array();
parse_str($after_access_request,$access_tokens);
# line 234 below
$access_token = new OAuthConsumer($access_tokens['oauth_token'], $access_tokens['oauth_token_secret']);
// prepare for get profile call
$profile_req = $access_token_req->from_consumer_and_token($consumer,
$access_token, "GET", $this->api_url.'/v1/people/~');
$profile_req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),$consumer,$access_token);
$after_request = $this->doHttpRequest($profile_req->to_url());
var_dump($after_request);
}
The function var_dumps a string, which is the basic synopsis of my profile:
string(402) " User Name etc. etc. http://www.linkedin.com/profile?viewProfile=&key=28141694&authToken=HWBC&authType=name&trk=api*a137731*s146100* "
That's good. However, the minute I refresh the page, the same function call fails with:
Undefined index: oauth_token, line number: 234
(this line marked with comment in above code block).
Then, of course, the var_dump reports this error from LinkedIn:
string(290) " 401 1310652477038 R8MHA2787T 0 [unauthorized]. The token used in the OAuth request is not valid. "
something to note:
the user token, secret, and verifier are persisted during the initial authorization callback (right before this function is called). So, they are the same during the first call (when it works, right after coming back from linkedin) and during a page reload (when it fails on line 234).
Also, I must admit I'm not 100% sure I understand everything that's going on in this function. I actually took examples from this tutorial (about a different service, not linkedin) http://apiwiki.justin.tv/mediawiki/index.php/OAuth_PHP_Tutorial and combined it with the information I gathered from the LinkedIn API documentation, spread throughout their developer site. Most notable was the addition of the 'verifier' which the tutorial did not use.
Any insight into this problem would be greatly appreciated. Thanks in advance.
-Nick
UPDATE
The only way I've been able to get this going is to do a new OAuth handshake every single time. Is this the way it's supposed to happen? I was under the impression that once I got my user token/secret and verifier, that I could then use these for continuous API calls until the token expired or was revoked.
As it is now, every time the page reloads I'm requesting a new user token, secret and verifier, then immediately calling to get the user profile (which succeeds). Next reload, I get a whole new key/secret and verifier. Seems like quite a lot of work for each call, and as I understood it, you should be able to perform offline operations with this method - and if I need new authorization each time, then I guess I can't do that?
Well. I've finally figured out what was going on so thought I'd post the answer here, just in case someone else runs into this.
The example that I was using as a guide was flawed. After the access token is retrieved, you should then create a new OAuthRequest object, instead of using the existing $access_token_req instance.
So this:
// prepare for get profile call
$profile_req = $access_token_req->from_consumer_and_token($consumer,
$access_token, "GET", $this->api_url.'/v1/people/~');
$profile_req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),$consumer,$access_token);
$after_request = $this->doHttpRequest($profile_req->to_url());
Should be changed to this:
$api_req = new OAuthRequest("GET", $this->api_url.$api_call);
// prepare for get profile call
$api_req = $api_req->from_consumer_and_token($consumer,
$access_token, "GET", $this->api_url.'/v1/people/~');
$api_req->sign_request(new OAuthSignatureMethod_HMAC_SHA1(),$consumer,$access_token);
$after_request = $this->doHttpRequest($api_req->to_url());
I have got a successful oauth TripIt granting process using the same methodology that is used to connect and authenticate users against the LinkedIn and Twitter APIs in PHP (PECL Oauth etc).
However, whenever when I do a valid request (ie a 200 response... no 401 nor 404), all I get in response is:
<Response><timestamp>1301411027</timestamp><num_bytes>80</num_bytes></Response>
I want to list the authenticated user's profile and trip data... The API docs (the pdf) is a bit sketchy on how to do this when the actual user id isn't known, but here are the queries I have attempted:
https://api.tripit.com/v1/list/trip
https://api.tripit.com/v1/list/trip/traveler/true
https://api.tripit.com/v1/get/profile
All returning the same response (as part of the oauth class "last response" method). This is where the LinkedIn API response contents can be found... so what is going on with TripIt? :P
It took a bit of experimenting, but here's an example of one that appears to be working to return data.
$response = $TripIt->_do_request('get/profile');
EDIT:
This one is likely the preferred method.
$response = $TripIt->_do_request('get', 'profile');
I've gone one step further and thrown it into an XML parser.
$response = $TripIt->_do_request('get', 'profile');
$profile = new SimpleXMLElement($response);
Here is one I'm using to get past trips. That third parameter is the one to use for filters.
$response = $TripIt->_do_request('list', 'trip', array('past'=>'true' );
$trips = new SimpleXMLElement($response);