PayPal - Get transaction details for recurring profile - php

On making the TransactionSearch request I receive the list of the transactions with the TRANSACTIONID field for the transactions, corresponding to the recurring payments, in the form e.g. "I-BRPN2RUD8W0G" (current is fake).
For the rest transactions - I get usual 17 single-byte alphanumeric string. That means, that for recurring payments PayPal returns ProfileID, but not TransactionID.
As a result when I request the GetTransactionDetails with this transaction id passed to PayPal I receive valid details for ordinary payments and ERROR with the message "The transaction id is not valid" for the case of recurring payments.

You will need to set IPN as suggested by Sanjiv. You can get the fields as per IPN Variables. In case of refund you will also need to use parent_txn_id
If you are new with this and finding tough, you can use IPN listener class and then integrate below code
$listener = new IpnListener();
try {
$verified = $listener->processIpn();
} catch (Exception $e) {
return Log::error($e->getMessage());
}
if ($verified) {
$data = $_POST;
$user_id = json_decode($data['custom'])->user_id;
$subscription = ($data['mc_gross_1'] == '10') ? 2 : 1;
$txn = array(
'txn_id' => $data['txn_id'],
'user_id' => $user_id,
'paypal_id' => $data['subscr_id'],
'subscription' => $subscription,
'expires' => date('Y-m-d H:i:s', strtotime('+1 Month')),
);
Payment::create($txn);
} else {
Log::error('Transaction not verified');
}
Save this file code in file let say, ipn.php and now assign web path for this file in your paypal account.
PS: make sure your IPN file is on publicly accessible URL. Do not use local or restricted server.

You have to set IPN in your Paypal merchant account (specially for recurring payments), Which sends you back a transaction details when a recurring payment happens, from there you can get $_POST['txn_id'] which is your TRANSACTIONID if $_POST['txn_type'] is recurring_payment. Save the details in your database and then you can call GetTransactionDetails method when you need transaction details. More

Related

How to complete pending transactions in paypal payouts

I am trying to implement a single Payout functionality in Paypal. I have referred to the sample code given by Paypal's documentation here. Everything seems to be working in order but the response given by PayPal indicates this: "batch_status": "PENDING". Here is my payout function:
public function payoutWithPaypal()
{
$request_amount = session()->get('request_amount');
$transaction_id = session()->get('transaction_id');
$receiver_email = session()->get('receiver_email');
$payouts = new \PayPal\Api\Payout();
$senderBatchHeader = new \PayPal\Api\PayoutSenderBatchHeader();
$senderBatchHeader->setSenderBatchId(uniqid())->setEmailSubject("You have a Payout!");
$senderItem = new \PayPal\Api\PayoutItem();
$senderItem->setRecipientType('Email')
->setNote('Thanks for your patronage!')
->setReceiver($receiver_email)
->setSenderItemId("001")
->setAmount(new \PayPal\Api\Currency('{
"value":"'.$request_amount.'",
"currency":"USD"
}'));
$payouts->setSenderBatchHeader($senderBatchHeader)->addItem($senderItem);
$request = clone $payouts;
try {
$output = $payouts->create(array('sync_mode' => 'false'), $this->_api_context);
} catch (\Exception $ex) {
dd($ex);
}
return $output;
}
The solutions provided here have not really solved my issue.
Payouts run in a batch. For a batch status to begin as 'PENDING' is normal. The status of each payout within the batch is what matters. You can query them as needed, and if they are individually pending some reason may be given.
The most common reason for a PayPal payment to be pending in sandbox or live is if there is no PayPal account with a confirmed email (in sandbox or live, respectively) at the address to which the payment was sent. Receivers have 30 days to create an account and/or confirm the email on their account to accept the payment, otherwise it will be automatically refunded after 30 days. Reminders are sent to that email during this period.

Magento Credit card number mismatch with credit card type exception

I am using stripe credit card payment method for website on my magento store and developing a mobile application. I am developing the api's using native magento api. Problem occurred on create order api, everything till adding payment for stripe credit card works fine but when I hit the create order api it throws the exception.
"Credit card number mismatch with credit card type exception"
Below is api code, Please share your knowledge for this issue. Thanks in advance.
$proxy = new SoapClient($this->_client); //soap handle
$sessionId = $proxy->login($this->_apiuser, $this->_apikey);
$resultCustomerAddresses = $proxy->call($sessionId, "cart_customer.addresses", array($shoppingCartId, $arrAddresses));
if ($resultCustomerAddresses != TRUE)
{
return json_encode(array('status' => 0, 'result' => array(),'message' => 'Error in saving address'));
}
$resultShippingMethods = $proxy->call($sessionId, "cart_shipping.list", array($shoppingCartId));
$randShippingMethodIndex = rand(0, count($resultShippingMethods)-1 );
$shippingMethod = $resultShippingMethods[$randShippingMethodIndex]["code"];
$resultShippingMethod = $proxy->call($sessionId, "cart_shipping.method", array($shoppingCartId, $shipping_method));
//$resultTotalOrder = $proxy->call($sessionId,'cart.totals',array($shoppingCartId));
$paymentMethod = array(
"method" => $payment_method
);
$resultPaymentMethod = $proxy->call($sessionId, "cart_payment.method", array($shoppingCartId, $payment_method));
$licenseForOrderCreation = null;
$resultOrderCreation = $proxy->call($sessionId,"cart.order",array($shoppingCartId, null, $licenseForOrderCreation));
I had the same problem and successfully solved it, see this answer: https://stackoverflow.com/a/41948259/1052675
Basically, you supply the card information before you save the quote. It will validate the card against regex patterns and configured purchase limits and make sure you can use the payment method.
Then it will forget the payment information.
So before you tell it to submit the order, you need to supply the card info again.
My solution was a custom endpoint for simplicity on the front end app and it enabled me to keep the card info in memory to re-save between saving the quote and submitting the order.

Stripe doubles anything

I am using a function I created that I have tried creating customers from, and creating charges from. For whatever reason it seems to be double charging in test mode (Not bringing into live mode under these conditions) and I'm trying to understand why. I had it going through a few functions so I made it all happen in one function to make sure that it had nothing to do with what I had made. I'm lost on why this is happening. I try to make charges from token, doubles in less than a second. I try to create a customer from token, doubles in less than a second. I am using Stripes latest stripe-php library.
public function invoice($invoice = null) {
//Provides billing info for invoice.ctp
$this->loadModel('Invoice');
$billingi = $this->Invoice->get($invoice, [
'contain' => ['Items'],
]);
$dollars = 0;
foreach ($billingi->items as $item) {
$dollars += $item->price;
}
$cents = bcmul($dollars, 100);
$price = floatval($cents);
if ($this->request->is('post')) {
$stripeToken = $this->request->data('stripeToken');
//Sets stripe API
\Stripe\Stripe::setApiKey("sk_test_QVYouMViTf1k3zfVu2VAyZge");
//Retrieves stripe token from stripe API
//$response = \Stripe\Token::retrieve($stripeToken);
\Stripe\Customer::create(array(
"description" => "Test customer",
"source" => $stripeToken // obtained with Stripe.js
));
$this->Flash->success(__('Thank you for your payment!'));
return $this->redirect(['action' => 'approved', $invoice]);
}
/*
if ($response && $this->checkExists($response->card->cvc_check, $response->card->address_zip_check) == true) {
$this->insertCharge($invoice, $response, $price);
} else {
//Throw error because cvc_check or zip came back null (doesn't exist)
}
}
*/
$this->set('billingi', $billingi);
$this->set('_serialize', ['billing']);
}
The reason why there are things commented out is because I wanted to test the function without it, but adding it back later when I understand what the issue is.
In your code, the only API request sent to Stripe is a customer creation request (\Stripe\Customer::create(...)).
This doesn't charge the user -- it merely validates the card from the token in the source parameter, and creates a persistent customer object that you can in turn use to create actual charges. This tutorial explains this flow.
There's nothing in your code that would cause the API request to be sent twice. It's very unlikely the issue is on Stripe's end. More likely, your code is being called twice for some reason that's not related to Stripe. You'd need to add traces to your code to figure out what exactly is being called in what order.

Omnipay token billing not working with stripe

I am using ignited/laravel-omnipay package for omnipay in laravel.
I am trying to implement token billing using stripe as shown here https://github.com/thephpleague/omnipay#token-billing.
Customer are getting created successfully on stripe but i am not able to make payment with the returned customer id.
Here's my code
$token = Input::get('stripetoken');
$gateway = Omnipay::create('Stripe');
$gateway->setApiKey('My Key');
$gateway->setTestMode(true);
$cardresponse = $gateway->createCard(array('token' =>$token))->send();
if ($cardresponse->isSuccessful()) {
$card_id = $cardresponse->getCardReference();
$data = $cardresponse->getData();
$customerid = $data['id'];
$cardid = $data['default_source'];
}
$paymentresponse = $gateway->purchase(array('amount' => '10.00','currency' => 'USD', 'cardReference' => $card_id))->send();
echo $paymentresponse->getMessage();
I am getting following response.
No such token: cus_8FwPaLNKdWcfRW
And when i check my stripe dashboard then customer with this id exists and has a card assigned.
Thanks for helping.
Since you're creating a customer object, you need to update your charge creation request to pass the customer ID in the customer parameter rather than in the source parameter (which causes the error you're seeing).
I'm not familiar with Omnipay but I think this should work:
$paymentresponse = $gateway->purchase(array('amount' => '10.00','currency' => 'USD', 'customerReference' => $card_id))->send();

Issue with creating an intermediate pending order

I have created a custom payment module and currently it calls validateOrder() after the redirection from the payment website, and this method creates the order, sends email etc. But the issue is if user closed the payment website before it can redirect back to the PrestaShop website the order won't be created in this case. So, I want to create an order(say with "pending" status) before I redirect to the payment website and after redirection from the payment website I can simply mark the same payment as done and send mails etc.
Currently for this I was trying to call validateOrder twice, once in hookdisplayPayment(here I set the status as "pending") and once after redirection. But now after redirection I am getting "The cart cannot be loaded, or an order has already been placed using this cart". I think that's because I can't update the same order twice using the same Card Id.
Note that I want to send the emails only once, once the payment is successful. Currently for this I am using a custom payment status with 'send_email' set to 0.
What's a good workaround for this?
I would like to support versions 1.5+ and 1.6+ if that matters.
A better way to do it than my first answer would be to create a override in your module of function validateOrder.
You will modify:
/** #var Order $order */
$order = new Order();
Into:
/** #var Order $order */
$order = new Order($this->currentOrder);
Then test if is loaded object, skip the part where it sets the order fields. If it's not loaded, set the order fields appropriately with the pending status.
Also test if $this->currentOrder is set where the email is sent, if it's not set skip the email part. If it's set it means the order is pending and you should change the status and send the email.
After you override the function, you can call validateOrder twice, before and after redirection.
You could try something like this:
Before making the redirection you can call once function validateOrder and set status as pending. This will set for your module the variable $this->currentOrder with the id of the pending order.
After redirection don't call again validateOrder, but create your own function to call, eg. validateOrderAfterRedirect in which you check that the payment was made and change the status of the current order. It will be something like this:
// your way of checking that te payment was made
$payment_completed = $this->paymentIsComplete();
if($payment_completed) {
$order = new Order($this->currentOrder);
if(Validate::isLoadedObject($order) && $order->getCurrentOrderState() == [id of pending status]) {
$order->setCurrentState([id of payment accepted status]);
}
}
Create an order with "pending payment" status before the website is redirected to payment system. Once the customer returns the system should just change the payment status to "completed". If the customer closes the payment site, the status will remain "pending" and should be manually updated after checking the payment system.
Many payment gateways provide a mechanism where on completed or failed payment they post data including amount paid and cart ID to a URL you supply to them.
When you process this information using a server-side script at that stage you can validate the order. This should happen before the user is redirected back to your website. Once they are redirected to your site it will already have acknowledged payment in the background.
The reason this method is preferred is that it is the only way to ensure the customer cannot manipulate a URL to make your store think they have paid for an order when in fact no money has changed hands, after which you could end up shipping products for free.
You can do it by adding some like this
$result = $this->validateOrder((int) $cart->id, Configuration::get('PPQ_CREATED_STATUS'), $total, $this->displayName, NULL, array(), (int) $currency->id, false, $customer->secure_key);
in any plays where you need before redirection(where $this is payment module instance)
And after redirection to confirm page I have use wrote this
public function hookPaymentReturn($params)
{
$id_module = (int) Tools::getValue('id_module');
if ($id_module === (int) $this->id) {
$orderHistory = new OrderHistory();
$orderHistory->changeIdOrderState(Configuration::get('PPQ_SUCCESS_STATUS'), $params['objOrder']);
}
}
For mail sending you can configure needed order status
For my case (need work only with paypal, I have change write my own one page checkout module and write my own payment module and befour redirect to paypal I have wrote this
public function hookPayment($params)
{
$customer = &$this->context->customer;
$cart = &$this->context->cart;
$currency = &$this->context->currency;
if (
$customer->isLogged(true) &&
$cart->nbProducts()
) {
$total = (float) $cart->getOrderTotal(true, Cart::BOTH);
$result = $this->validateOrder((int) $cart->id, Configuration::get('PPQ_CREATED_STATUS'), $total, $this->displayName, NULL, array(), (int) $currency->id, false, $customer->secure_key);
if ($result) {
if (!Configuration::get('PPQ_TEST_MODE')) {
$paypal_url = 'https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=' . Configuration::get('PPQ_PROFILE');
} else {
$paypal_url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_xclick&business=' . Configuration::get('PPQ_PROFILE');
}
$order_confirmation_url = $this->context->link->getPageLink('order-confirmation', null, null, array(
'id_cart' => (int) $cart->id,
'id_module' => (int) $this->id,
'id_order' => (int) $this->currentOrder,
'key' => $customer->secure_key,
));
$this->context->smarty->assign(array(
'paypal_url' => $paypal_url,
'order_confirmation_url' => $order_confirmation_url,
'order_id' => (int) $this->currentOrder,
'shop_name' => $this->context->shop->name,
'total_without_shipping' => Tools::convertPriceFull((float) $cart->getOrderTotal(true, Cart::BOTH_WITHOUT_SHIPPING)),
'total_shipping' => Tools::convertPriceFull((float) $cart->getOrderTotal(true, Cart::ONLY_SHIPPING)),
'currency_iso' => Tools::strtoupper($currency->iso_code)
));
return $this->display(__FILE__, 'paypalquick.tpl');
} else {
$this->context->controller->errors[] = $this->l('Can\'t create order. Pleas contact with us');
}
} else {
$this->context->controller->errors[] = $this->l('Problem with loginin or cart empty');
}
}
and tpl
<form id="paypalquick" action="{$paypal_url}" method="post" enctype="multipart/form-data">
<input type="hidden" value="{l s='%s order #%s' sprintf=[$shop_name|escape:'html':'UTF-8', $order_id|intval] mod='paypalquick'}" name="item_name"/>
<input type="hidden" value="{$total_without_shipping}" name="amount"/>
<input type="hidden" value="{$total_shipping}" name="shipping"/>
<input type="hidden" value="{$currency_iso}" name="currency_code"/>
<input type="hidden" value="{$order_confirmation_url}" name="return"/>
<div class="text-center">
<button class="submit">{l s='Go to PayPal for payment' mod='paypalquick'}</button>
</div>
But it was my private cas you can't use it on default but you can see how to make it.
I think we need you to call another hook (that you create) at the time of validation on the site (before leaving that matter) who put a pending status, and keep ValidateOrder hook () to to payment confirmed
Regards,
Arthur

Categories