How to get payer email after a success payment wit paypal API (Laravel paypal/rest-api-sdk-php)? - laravel

I'm using the paypal API with Laravel, everything works fine. I just need to find the way to get the user email address and name.
$payment = Payment::get($payment_id, $this->_api_context);
$execution = new PaymentExecution();
$execution->setPayerId( $request->query('PayerID') );
$result = $payment->execute($execution, $this->_api_context);
if ($result->getState() == 'approved') {
// I should get the info about the payer here
}

The v1/payments PayPal-PHP-SDK is deprecated, you should not use it for any new integration.
The current supported SDK is the v2/checkout/orders Checkout-PHP-SDK, which you should use instead.
You can try the srmklive package "v3" if you like, otherwise integrate directly.
You should be able to create two routes, one for 'Create Order' and one for 'Capture Order', documented here. These routes should return only JSON data (no HTML or text). Pair those two routes with the following approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
The capture order response may have details about the payer and payment, if not use can do a 'Get' API call on the order ID for this information. Store it in your database before returning the JSON to the client code.

Related

PayPal Sandbox 400 error after redirection

i use laravel V6
$apiContext = new \PayPal\Rest\ApiContext(
new \PayPal\Auth\OAuthTokenCredential(
'token..',
'token..'
)
);
//dd($transaction);
$callbackUrl = url('/paypal/status');
$payer = new \PayPal\Api\Payer();
$payer->setPaymentMethod('paypal');
$amount = new \PayPal\Api\Amount();
$amount->setTotal('1.00');
$amount->setCurrency('USD');
$transaction = new \PayPal\Api\Transaction();
$transaction->setAmount($amount);
$redirectUrls = new \PayPal\Api\RedirectUrls();
$redirectUrls->setReturnUrl($callbackUrl)
->setCancelUrl($callbackUrl);
$payment = new \PayPal\Api\Payment();
$payment->setIntent('sale')
->setPayer($payer)
->setTransactions(array($transaction))
->setRedirectUrls($redirectUrls);
try {
$payment->create($apiContext);
//dd($payment);
//dd($payment->getApprovalLink());
return redirect()->away($payment->getApprovalLink());
} catch (\PayPal\Exception\PayPalConnectionException $ex) {
var_dump(json_decode($ex->getData()));
exit(1);
}
that's the controller, I followed the documentation enter link description here
but I get a 400 error enter link description here
The link it generates is as follows https://www.sandbox.paypal.com/webapps/hermes?flow=1-P&ulReturn=true&token=EC-88M93704JL735000S&country.x=US&locale.x=es_XC#/checkout/genericError?code=UEFZTUVOVF9ERU5JRUQ%3D
I can not understand why the error .. the client_id secret, are fine .. anyway it is directly in the controller and check that the sandbox account has a balance
For that ClientId/Secret it looks like the receiving account in the PayPal sandbox is from a country that cannot receive any payments, such as Bolivia. Create a new sandbox business account at https://www.paypal.com/signin?intent=developer&returnUri=https%3A%2F%2Fdeveloper.paypal.com%2Fdeveloper%2Faccounts%2F for a different country that is able to receive PayPal payments, and then create a REST app for this new sandbox business account in the 'My Applications' side tab.
It looks like you are using an obsolete PHP integration, with the old v1 payments SDK
You should instead use the v2 Checkout-PHP-SDK, with two routes, one for 'Set Up Transaction' and one for 'Capture Transaction', documented here: https://developer.paypal.com/docs/checkout/reference/server-integration/
Instead of redirecting to the approval URL, use this front-end UI: https://developer.paypal.com/demo/checkout/#/pattern/server -- this gives an "in context" checkout experience that keeps your site loaded in the background, which provides a much superior modern web experience

Support 3D Secure card Stripe payment to start subscription

My payment model idea for my application is really simple: Having a (laravel) website with a member area and some special funcionality, whereas a member account costs 19.90 / year. I wanted to integrate Stripe to my registration flow to allow a payment to happen. When the payment has succeeded, I create a subscription which will then automatically renew this payment each year.
So good so far - I managed to get it working using the Guide on how to set up a subscription by Stripe. However, cards that required 3D Secure authentication did not work yet, and this is a must-have.
So I read further and used a PaymentIntent (API Docs). However, current behavior is the following:
I create a PaymentIntent and pass the public key to the frontend
Customer enters credentials and submits
3D Secure Authentication happens correctly, returning me a payment_method_id
On the server side, I retrieve the PaymentIntent again. It has status succeeded and the payment is recieved on my Stripe Dashboard.
I then create the customer object (with the payment method I got from the PaymentIntent), and with that customer, create the subscription
The subscription has status incomplete and it seems that the subscription tries to again charge the customer but fails because of the 3D Secure validation that would be necessary the second time.
So my actual question is: How can I create a subscription which notices somehow that the customer has already paid with my PaymentIntent and the PaymentMethod that I'm passing to it?
Some Code
Create the PaymentIntent and pass that to the frontend
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
$intent = \Stripe\PaymentIntent::create([
'amount' => '1990',
'currency' => 'chf',
]);
$request->session()->put('stripePaymentIntentId',$intent->id);
return view('payment.checkout')->with('intentClientSecret',$intent->client_secret);
Frontend Checkout when clicking "Buy"
// I have stripe elements (the card input field) ready and working
// using the variable "card". The Stripe instance is saved in "stripe".
// Using "confirmCardPayment", the 3DS authentication is performed successfully.
stripe.confirmCardPayment(intentClientSecret,{
payment_method: {card: mycard},
setup_future_usage: 'off_session'
}).then(function(result) {
$('#card-errors').text(result.error ? result.error.message : '');
if (!result.error) {
submitMyFormToBackend(result.paymentIntent.payment_method);
}
else {
unlockPaymentForm();
}
});
Backend after submitting
// Get the PaymentMethod id from the frontend that was submitted
$payment_method_id = $request->get('stripePaymentMethodId');
// Get the PaymentIntent id which we created in the beginning
$payment_intent_id = $request->session()->get('stripePaymentIntentId');
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
// Get the Laravel User
$user = auth()->user();
// Firstly load Payment Intent to have this failing first if anything is not right
$intent = \Stripe\PaymentIntent::retrieve($payment_intent_id);
if ($intent instanceof \Stripe\PaymentIntent) {
// PaymentIntent loaded successfully.
if ($intent->status == 'succeeded') {
// The intent succeeded and at this point I believe the money
// has already been transferred to my account, so it's paid.
// Setting up the user with the paymentMethod given from the frontend (from
// the 3DS confirmation).
$customer = \Stripe\Customer::create([
'payment_method' => $payment_method_id,
'email' => $user->email,
'invoice_settings' => [
'default_payment_method' => $payment_method_id,
],
]);
$stripeSub = \Stripe\Subscription::create([
'customer' => $customer->id,
'items' => [
[
'plan' => env('STRIPE_PLAN_ID'),
]
],
'collection_method' => 'charge_automatically',
'off_session' => false,
]);
// If the state of the subscription would be "active" or "trialing", we would be fine
// (depends on the trial settings on the plan), but both would be ok.
if (in_array($stripeSub->status,['active','trialing'])) {
return "SUCCESS";
}
// HOWEVER the state that I get here is "incomplete", thus it's an error.
else {
return "ERROR";
}
}
}
I finally got a working solution running for my site. It goes like this:
1 - Backend: Create a SetupIntent
I created a SetupIntent (SetupIntent API Docs) to cover the checkout flow entirely. The difference to a PaymentIntent (PaymentIntent API Docs) is that the PaymentIntent goes from collecting the card details, preparing the payment and effectively transferring the amount to the account, while the SetupIntent only prepares card collection, but does not yet execute the payment. You will get a PaymentMethod (PaymentMethod API Docs) from it, which you can use later.
$intent = SetupIntent::create([
'payment_method_types' => ['card'],
]);
Then I passed the $intent->client_secret key to my client side JavaScript.
2 - Frontend: Collect card details with Elements
On the frontend, I placed the Stripe card element to collect the card details.
var stripe = Stripe(your_stripe_public_key);
var elements = stripe.elements();
var style = { /* my custom style definitions */ };
var card = elements.create('card',{style:style});
card.mount('.my-cards-element-container');
// Add live error message listener
card.addEventListener('change',function(event) {
$('.my-card-errors-container').text(event.error ? event.error.message : '');
}
// Add payment button listener
$('.my-payment-submit-button').on('click',function() {
// Ensure to lock the Payment Form while performing async actions
lockMyPaymentForm();
// Confirm the setup without charging it yet thanks to the SetupIntent.
// With 3D Secure 2 cards, this will trigger the confirmation window.
// With 3D Secure cards, this will not trigger a confirmation.
stripe.confirmCardSetup(setup_intent_client_secret, {
payment_method: {card: card} // <- the latter is the card object variable
}).then(function(result) {
$('.my-card-errors-container').text(event.error ? event.error.message : '');
if (!result.error) {
submitPaymentMethodIdToBackend(result.setupIntent.payment_method);
}
else {
// There was an error so unlock the payment form again.
unlockMyPaymentForm();
}
});
}
function lockMyPaymentForm() {
$('.my-payment-submit-button').addClass('disabled'); // From Bootstrap
// Get the card element here and disable it
// This variable is not global so this is just sample code that does not work.
card.update({disabled: true});
}
function unlockMyPaymentForm() {
$('.my-payment-submit-button').removeClass('disabled'); // From Bootstrap
// Get the card element here and enable it again
// This variable is not global so this is just sample code that does not work.
card.update({disabled: false});
}
3 - Backend: Create Customer and Subscription
On the backend, I received the $payment_method_id which I submitted from the frontend.
Firstly, we need now to create a Customer (Customer API Docs) if it does not yet exist. On the customer, we will attach the payment method from the SetupIntent. Then, we create the Subscription (Subscription API Docs) which will start the charge from the SetupIntent.
$customer = \Stripe\Customer::create([
'email' => $user->email, // A field from my previously registered laravel user
]);
$paymentMethod = \Stripe\PaymentMethod::retrieve($payment_method_id);
$paymentMethod->attach([
'customer' => $customer->id,
]);
$customer = \Stripe\Customer::update($customer->id,[
'invoice_settings' => [
'default_payment_method' => $paymentMethod->id,
],
]);
$subscription = \Stripe\Subscription::create([
'customer' => $customer->id,
'items' => [
[
'plan' => 'MY_STRIPE_PLAN_ID',
],
],
'off_session' => TRUE, //for use when the subscription renews
]);
Now we have a Subscription object. With regular cards, the state should be active or trialing, depending on your trial days setting on the subscription. However when dealing with 3D Secure test cards, I got the subscription still in an incomplete state. According to my Stripe support contact, this can also be a problem because of not yet fully working 3D Secure test cards. However I assume that this can also happen on production environments with some sort of cards, so we have to deal with it.
On subscriptions with status incomplete you can retrieve the latest invoice from $subscription->latest_invoice like so:
$invoice = \Stripe\Invoice::retrieve($subscription->latest_invoice);
On your invoice object, you will find a status and a hosted_invoice_url. When the status is still open, I now present the user the URL to the hosted invoice which he has to complete first. I let him open the link in a new window, which shows a nice looking invoice hosted by stripe. There, he is free to again confirm his credit card details including the 3D Secure workflow. In case he succeeds there, the $subscription->status changes to active or trialing after you re-retrieve the subscription from Stripe.
This is some sort of fool proof strategy that if anything with your implementation goes wrong, just send them to Stripe to complete it. Just be sure to hint the user that in case he has to confirm his card twice, it won't be charged twice but only once!
I was not able to create a working version of #snieguu's solution because I wanted to use Elements and not collect the credit card details separately to then create a PaymentMethod by myself.
Have You considered the opposite approach that payment intents(Also the first one) will be generated by subscription - not created manually?
So the flow will be:
Create a payment method
Create Customer(using the payment method)
Create Subscription(using Customer and payment method) - that creates also the first invoice
Retrieve payment intent from Subscription by latest_invoice.payment_intent.id. Here You can choose if this should be handled by You or Stripe. See this: How to get PaymentIntent next_action.type = redirect_to_url instead of use_stripe_sdk for Subscription
Allow finishing 3D secure flow
You have a constant price for a subscription, so it will be charged upfront:
https://stripe.com/docs/billing/subscriptions/multiplan#billing-periods-with-multiple-plans
Conventional plans that charge a fixed amount on an interval are billed at the start of each billing cycle.

Laravel Cashier Re-attempt Pending Invoice after Card Updates

I am using Laravel 5.3 with Cashier. If a customer updates their card details, how can I check if there is a pending invoice and ask Stripe to re-attempt the charge on the new card? At the moment, I have set the settings for attempts in Stripe dashboard. But from what I understand, Stripe does not automatically attempt to charge the customer if they updated their card details and it waits for the next attempt date to try again. Thats why I want to manually attempt to charge the customer on pending invoice as soon as they update their card. I read the Cashier documentation and Github page but this case is not covered there.
$user->updateCard($token);
// Next charge customer if there is a pending invoice
Can someone help me out please.
After testing and talking with Stripe support, I found out the problem with the current updateCard() method used in Laravel Cashier.
With the current updateCard() method, the card is added to the sources list and then sets the new card as the default_source. the result of this method has 2 outcomes:
Multiple cards gets added to the list although the recent one is set as default_source
When updating the card using this method, if there are any unpaid invoices (i.e. invoices in past_due state), they are not automatically charged.
In order for stripe to re-attempt charging customer on all invoices in past_due state, the source parameter needs to be passed. So I have created a new method something like this:
public function replaceCard($token)
{
$customer = $this->asStripeCustomer();
$token = StripeToken::retrieve($token, ['api_key' => $this->getStripeKey()]);
// If the given token already has the card as their default source, we can just
// bail out of the method now. We don't need to keep adding the same card to
// a model's account every time we go through this particular method call.
if ($token->card->id === $customer->default_source) {
return;
}
// Just pass `source: tok_xxx` in order for the previous default source
// to be deleted and any unpaid invoices to be retried
$customer->source = $token;
$customer->save();
// Next we will get the default source for this model so we can update the last
// four digits and the card brand on the record in the database. This allows
// us to display the information on the front-end when updating the cards.
$source = $customer->default_source
? $customer->sources->retrieve($customer->default_source)
: null;
$this->fillCardDetails($source);
$this->save();
}
I have created a Pull request for this addition. Since editing the Billable file directly for any changes is not a good idea, if this doesnt get added to Cashier, then you can use the following in the Controller file to do it directly from there:
$user = Auth::User();
$customer = $user->asStripeCustomer();
$token = StripeToken::retrieve($token, ['api_key' => config('services.stripe.secret')]);
if (!($token->card->id === $customer->default_source)) {
$customer->source = $token;
$customer->save();
// Next synchronise user's card details and update the database
$user->updateCardFromStripe();
}

Braintree Drop-In UI v.zero how to retrieve the payment ID with PHP / to stay PCI QSA A

I'm new to braintree and I read that we need to be PCI QSA compliant...
There is the QSA A type and for that the v.zero ( Drop-In UI ) seems to be the best approach.
I followed the steps in: https://developers.braintreepayments.com/start/hello-client/javascript/v2
The form have action="/checkout" (or any other path...) but this tutorial doesn't provide the needed code to retrieve the payment status (example paid, ID of transaction).
I don't want to store credit card info.
Full Disclosure: I work as a developer for Braintree
That portion of the tutorial outlines how the client sends the credit card information to the Braintree servers which returns a payment method nonce via the post parameters.
You can then use the payment method nonce to create and inspect transactions and payment methods while remaining PCI compliant. For example, if you have the form that posts to /checkout, you could do something like this in the checkout logic:
$amount = '10.00'; /* replace with the amount you want */
$nonce = $_POST["payment_method_nonce"];
$result = Braintree\Transaction::sale([
'amount' => $amount,
'paymentMethodNonce' => $nonce
]);
if ($result->success){
$transaction = $result->transaction;
/* inspect the transaction here */
} else {
/* handle any errors */
}
All of this is detailed in the next portion of the tutorial which can be found here and if you have any questions you can always reach out to Braintree support.

Setting payment method to cart with Magento API

I'm using Magento API to create orders. My code fails when I want to add payment method to the cart :
$paymentMethod = array(
“method” => “paypal_standard”
);
$resultPaymentMethod = $proxy->call(
$sessionId,
“cart_payment.method”,
array(
$shoppingCartId,
$paymentMethod
)
);
I'm getting following error: Payment method is not allowed.
In admin section in System->Configuration->PayPal I have set Website Payments Standard but I didn't enabled any option in System->Configuration->Payment Methods cause there is none available for PayPal.
When I call:
$proxy->call($session, 'cart_payment.list')
method I get an empty array as there isn't any available payment method set. Does someone knows how and where paypal payment setting is saved in Magento ?
If I set another Payment method like "checkmo" then the order is created fine. The thing is that I only need to allow Paypal standard payment.
So my question is: How can I set payment method to PayPal to the cart so my order will be successfully created?
Thanks.
I have also facing this problem and find reason for it.
$method->canUseInternal() used in payment method api. When we use paypal or other redirect able methods in payment methos api in that case $method->canUseInternal() it getting false value.
So for this type situation we need to create own custom coding.
api function refreance:
protected function _canUsePaymentMethod($method, $quote){
if (!($method->isGateway() || $method->canUseInternal())) {
return false; }
if (!$method->canUseForCountry($quote->getBillingAddress()->getCountry())) {
return false;
}
if (!$method->canUseForCurrency(Mage::app()->getStore($quote->getStoreId())->getBaseCurrencyCode())) {
return false;
}
To pay with Paypal you need your customer to be redirected to Paypal. Because of this fact, you may not be allowed to use this payment method using API. I recommend you to have look at isAvailable() of the payment method to customize this behaviour.

Resources