Magento custom payment notification - php

I have successfully integrated a custom payment solution for magento with a lot of help from this tutorial http://www.junaidbhura.com/how-to-make-a-custom-magento-payment-extension-for-an-external-gateway/
I am at the last stage of notifying the website if a payment has been successful or not.
I have a PaymentController.php file below but am not sure how to link this to the payment gateway notification.
The payment gateway provide a notification to the server via an HTTP GET request, letting you know if a payment was accepted or declined. This is below
http://www.websitedomain.co.uk/mygateway/payment/response?Operator=&SessionID=&Note=&Tariff=&Status=&Mobile=
The code for the payment controller where I need to enter the code is below
class Myname_Mygateway_PaymentController extends Mage_Core_Controller_Front_Action {
// The redirect action is triggered when someone places an order
public function redirectAction() {
$this->loadLayout();
$block = $this->getLayout()->createBlock('Mage_Core_Block_Template','mygateway',array('template' => 'mygateway/redirect.phtml'));
$this->getLayout()->getBlock('content')->append($block);
$this->renderLayout();
}
// The response action is triggered when your gateway sends back a response after processing the customer's payment
public function responseAction() {
if($this->getRequest()->isPost()) {
/*
/* Your gateway's code to make sure the reponse you
/* just got is from the gatway and not from some weirdo.
/* This generally has some checksum or other checks,
/* and is provided by the gateway.
/* For now, we assume that the gateway's response is valid
*/
$validated = true;
$orderId = ''; // Generally sent by gateway
if($validated) {
// Payment was successful, so update the order's state, send order email and move to the success page
$order = Mage::getModel('sales/order');
$order->loadByIncrementId($orderId);
$order->setState(Mage_Sales_Model_Order::STATE_PROCESSING, true, 'Gateway has authorized the payment.');
$order->sendNewOrderEmail();
$order->setEmailSent(true);
$order->save();
Mage::getSingleton('checkout/session')->unsQuoteId();
Mage_Core_Controller_Varien_Action::_redirect('checkout/onepage/success', array('_secure'=>true));
}
else {
// There is a problem in the response we got
$this->cancelAction();
Mage_Core_Controller_Varien_Action::_redirect('checkout/onepage/failure', array('_secure'=>true));
}
}
else
Mage_Core_Controller_Varien_Action::_redirect('');
}
// The cancel action is triggered when an order is to be cancelled
public function cancelAction() {
if (Mage::getSingleton('checkout/session')->getLastRealOrderId()) {
$order = Mage::getModel('sales/order')->loadByIncrementId(Mage::getSingleton('checkout/session')->getLastRealOrderId());
if($order->getId()) {
// Flag the order as 'cancelled' and save it
$order->cancel()->setState(Mage_Sales_Model_Order::STATE_CANCELED, true, 'Gateway has declined the payment.')->save();
}
}
}
}

You have the line $validated = true;
Here you have to enter your own conditions if the payment has been validated.
So if this is the response from the payment gateway use the variables in the response
http://www.websitedomain.co.uk/mygateway/payment/response?Operator=&SessionID=&Note=&Tariff=&Status=&Mobile=
for example
if ( $_GET('Status') == 'Paid' ){
$validated = true;
}
You have to read the payment gateway documentation on info for their codes. For example the Status variable can have values like "Paid", "Pending", "Expired" or "1", "2", "3". Depends on the gateway.

Related

Razorpay integration issue on mobile [laravel]

I have integrated the Razorpay payment gateway in my laravel project. It works well on desktop but does not work on mobile .
Here is my code
public function pay(Request $request)
{
//Input items of form
$input = $request->all();
//get API Configuration
$api = new Api(ENV('RZP_KEY_ID'), ENV('RZP_KEY_SECRET'));
//Fetch payment information by razorpay_payment_id
$payment = $api->payment->fetch($input['razorpay_payment_id']);
if(count($input) && !empty($input['razorpay_payment_id'])) {
try {
$paymentDetails = $api->payment->fetch($input['razorpay_payment_id'])->capture(array('amount'=>$payment['amount']));
} catch (\Exception $e) {
//delete params from session
if(Session::has('amount')){
Session::pull('amount');
Session::pull('converted_amount');
Session::pull('currency');
Session::pull('charge');
}
return redirect(route('user.deposit.index'))->with('fail', 'Your Deposit request failed');
}
//record payment
//somwe codes for validating and recording payment removed
}
}
When I try to make payment using a mobile device, after inputing the credit card details, Instead of a popup windown that appears on desktop, I get redirected to https://api.razorpay.com/v1/payments/create/checkout to enter otp and confirm the payment.
After a Payment, it will redirect back to my site, here are the problems;
All sessions would be deleted and I would need to login again,
The redirect is a post request but if I relogin, it becomes a get request
How can I solve this problem
**Modifed:
For those who may have this same problem. Simply move the specific route from web.php to api.php

How to capture payment data from Stripe Payment Element

I've finally managed to implement the new Stripe Payment Element in Laravel via the Payment Intents API. However, I now need to capture information about the payments and store them in my database - specifically, I need the following data:
Transaction ID
Payment status (failed/pending/successful, etc.)
Payment method type (card/Google Pay/Apple Pay/etc.)
The amount actually charged to the customer
The currency the customer actually paid in
The postcode entered by the user in the payment form
All of this information seems to be available in the Payment Intent object but none of the several Stripe guides specify how to capture them on the server. I want to avoid using webhooks because they seem like overkill for grabbing and persisting data that I'm already retrieving.
It also doesn't help that, thanks to how the Stripe documentation's AJAX/PHP solution is set up, trying to dump and die any variables on the server-side causes the entire client-side flow to break, stopping the payment form from rendering and blocking any debugging information. Essentially, this makes the entire implementation of Payment Intents API impossible to debug on the server.
Does anyone who's been here before know how I would go about capturing this information?
Relevant portion of JavaScript/AJAX:
const stripe = Stripe(<TEST_PUBLISHABLE_KEY>);
const fonts = [
{
cssSrc:
"https://fonts.googleapis.com/css2?family=Open+Sans:wght#300;400;500;600;700&display=swap",
},
];
const appearance = {
theme: "stripe",
labels: "floating",
variables: {
colorText: "#2c2c2c",
fontFamily: "Open Sans, Segoe UI, sans-serif",
borderRadius: "4px",
},
};
let elements;
initialize();
checkStatus();
document
.querySelector("#payment-form")
.addEventListener("submit", handleSubmit);
// Fetches a payment intent and captures the client secret
async function initialize() {
const { clientSecret } = await fetch("/payment/stripe", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": document.querySelector('input[name="_token"]').value,
},
}).then((r) => r.json());
elements = stripe.elements({ fonts, appearance, clientSecret });
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
}
async function handleSubmit(e) {
e.preventDefault();
setLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost.rc/success"
},
});
if (error.type === "card_error" || error.type === "validation_error") {
showMessage(error.message);
} else {
showMessage("An unexpected error occured.");
}
setLoading(false);
}
// Fetches the payment intent status after payment submission
async function checkStatus() {
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
const { paymentIntent } = await stripe.retrievePaymentIntent(clientSecret);
switch (paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Your payment was not successful, please try again.");
break;
default:
showMessage("Something went wrong.");
break;
}
}
Routes file:
Route::post('/payment/stripe', [TransactionController::class, "stripe"]);
TransactionController:
public function stripe(Request $request) {
Stripe\Stripe::setApiKey(env(<TEST_SECRET_KEY>));
header('Content-Type: application/json');
try {
$paymentIntent = Stripe\PaymentIntent::create([
'amount' => 2.99,
'currency' => 'gbp',
'automatic_payment_methods' => [
'enabled' => true,
],
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
$this->storeStripe($paymentIntent, $output);
echo json_encode($output);
} catch (Stripe\Exception\CardException $e) {
echo 'Error code is:' . $e->getError()->code;
$paymentIntentId = $e->getError()->payment_intent->id;
$paymentIntent = Stripe\PaymentIntent::retrieve($paymentIntentId);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}
How can I capture the above information from the Payment Intent to store in my database?
I know you're not going to like this but I'll say it anyway. I honestly think implementing a webhook endpoint, listener and receiver function is your best bet and here is why:
The Stripe Payment Intent captures the lifecycle of a payment as it moves through multiple states. Because the various payment networks outside of Stripe do not guarantee specific response times these transitions can be asynchronous.
Therefore you cannot be certain when is the appropriate time to query the API for your completed Payment Intent unless you are listening for the payment_intent.succeeded event. Additionally, in some cases payment methods can be declined well after initial processing (e.g. suspected fraudulent cards, etc.). Using the webhooks approach keeps you informed of these changes.
Lastly, while you may only care to store this data in your DB now, scope does tend to increase and implementing webhook listeners early means you will have a solution ready if you need to take additional actions like
Sending email notifications to your customers
Adjusting revenue reconciliation
processing fulfillment actions
Other stuff.....
At the recommendation of RyanM I opted instead for the webhook solution, which turned out to be easier than I expected using Spatie's Stripe Webhooks package (although it seems to be run by someone who cares more about closing issues than fixing potential bugs, so opting for Stripe Cashier instead would probably be both easier and a more pleasant developer experience).
Note that by default Stripe webhooks return an Event object that itself contains other objects relevant to the event, such as a PaymentIntent for payment_intent.succeeded, for example, and any associated Charge objects. Therefore it's necessary to drill down a little to get all of the information needed.
$paymentIntent = $this->webhookCall->payload["data"]["object"];
$paymentID = $this->webhookCall->payload["data"]["object"]["id"]; // Transaction ID
$charge = $this->webhookCall->payload["data"]["object"]["charges"]["data"][0];
$transaction = Transaction::where("gateway_payment_id", $paymentID)->first();
$transaction->payment_status = strtoupper($paymentIntent["status"]); // Payment status
$transaction->payment_method = $charge["payment_method_details"]["type"]; // Payment method
$transaction->amount = ($paymentIntent["amount_received"]/100); // Amount charged, in pounds
$transaction->currency = strtoupper($paymentIntent["currency"]); // Currency charged in
$transaction->postcode = $charge["billing_details"]["address"]["postal_code"] ?? "N/A"; // Postcode if entered by the user - otherwise default to N/A

Why my Stripe Webhook function is not working(test mode)?

I has create a webhook php to trigger the paymentintent event status. I had successfully tigger it but the function of detected event is no working which i just want to redirect to another page if successful or not. I try to echo text, it also not showing. I can see the response body(show the text that i want to echo" in the dashboard of Stripe. Did i misunderstanding the concept of it ? This is my code:
<?php
/*
Template Name: webhook
*/
include_once(get_template_directory_uri().'/customFile/stripe-php-7.71.0/init.php');
\Stripe\Stripe::setApiKey('xxx'); // this is true, i just replace with xxx
$payload = #file_get_contents('php://input');
$event = null;
$paymentstatus = "Payment Failed";
try {
$event = \Stripe\Event::constructFrom(
json_decode($payload, true)
);
} catch(\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch(\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}
// Handle the event
switch ($event->type) {
case 'payment_intent.succeeded':
$paymentIntent = $event->data->object; // contains a \Stripe\PaymentIntent
//handlePaymentIntentSucceeded($paymentIntent);
echo"Success";
echo "<script>location.href='http://localhost/wordpress/';</script>";
break;
case 'payment_intent.payment_failed':
$paymentMethod = $event->data->object; // contains a \Stripe\PaymentMethod
//handlePaymentMethodAttached($paymentMethod);
echo "<script>location.href='http://localhost/wordpress/shop/';</script>";
break;
}
http_response_code(200);
Webhooks are requests that come from Stripe to your web server. There's no user attached to these events so trying to redirect after you receive a webhook event makes no sense. All that's happening is you're sending a <script> string back to Stripe, who promptly ignores it because all they are about is the response status code.
If you want to redirect your user after they've made a successful payment, you should either that on the client after they've confirmed the PaymentIntent or in your success_url if you're using Checkout.

How do I get the information returned in the IPN after a transaction in PayPal using ci_merchant library?

I am a newbie to codeigniter and so far I have been doing great in developing my first application. However, when I tried to integrate CI_MERCHANT library to the site I am getting redirected to paypal just fine and I can even complete the transaction successfully and get redirected to my website. However, I am stuck on how to verify the "hidden information" sent by paypal to my application in addition to extracting this information and posting it to the database.
in my controller I have this:
public function place_order($id=NULL){
$this->merchant->load('paypal_express');
$id=$this->session->userdata('id');
$customer_id=$id;
$rules=$this->order_m->rules_place_order;
$this->form_validation->set_rules($rules);
if ($this->form_validation->run() == FALSE) // validation hasn't been passed
{
$this->data['subview']='customer/order_view';
$this->load->view('templates/header_customer');
$this->load->view('customer/_layout_main',$this->data);
$this->load->view('templates/footer_customer');
}
else // passed validation proceed to post success logic
{
// build array for the model
$data=$this->order_m->array_from_order(array('topic_title','discipline','academic_level','type_of_service','paper_format','pages','no_of_sources','no_of_slides','paper_details','deadline','timezones'));
$data['customer_id']=$id;
$this->order_m->save_data($data);
$this->db->where('customer_id',$id);
//get the last inserted id
$no=$this->db->insert_id();
$settings=$this->merchant->default_settings();
//payment for order
$params = array(
'amount' => 100.00,
'currency' => 'USD',
'return_url' => 'http://localhost/customers/order/paypal',
'cancel_url' => 'http://localhost/customers/order'
);
$response=$this->merchant->purchase($params);
}
}
public function paypal(){
var_dump($_GET);
$this->merchant->load('paypal_express');
$settings=$this->merchant->default_settings();
$params = array(
'amount' => 100.00,
'currency' => 'USD',
);
$response=$this->merchant->purchase_return($params);
var_dump($response);
if ($response->status() == Merchant_response::AUTHORIZED)
{
echo "status is AUTHORIZED";
}
if ($response->status() == Merchant_response::FAILED)
{
echo "status is FAILED";
}
if ($response->status() == Merchant_response::REDIRECT)
{
echo "status is REDIRECT";
}
if ($response->status() == Merchant_response::COMPLETE)
{
echo "status is COMPLETE";
}
if ($response->status() == Merchant_response::REFUNDED)
{
echo "status is REFUNDED";
}
This redirects me successfully to paypal and I can complete the transaction.
However, I am unable to proceed from here since I am a newbie to payment processing. Kindly point me in the right direction on how to:
1. Verify every transaction with paypal and be able to visualize and post this information to my database.
2. Compare the information I posted to the database prior to redirecting the customer to paypal with what I receive from paypal.
First check the detailed info about IPN here
When you are creating button in step3 you can specify IPN the url
Key points
You will receive the data at IPN url in $_POST variable
read that $_POST variable with mail('email', 'subject', 'data with $_POST') or writing into the log file
You need to call the purchase_return() method when the customer is sent to your return_url. This will confirm the transaction with PayPal.

Checkout my order basket with PayPal

On my site user can fill their order basket with items.
Once this is finished they can click the checkout button.
I want them to checkout using PayPal.
Once the user click on the checkout button the user is redirected to PayPal and sees an overview of the products to pay for.
If the user goes through the payment process the user is redirected to my success page.
However I expect the success page to also receive the transaction id of the payment but paypal only sends back a token and a payerid.
My checkout form looks like this:
<form action="/en/checkout">
<input type="submit" name="submit" value="Checkout">
</form>
My code that does the checkout is:
function checkoutAction()
{
$request = $this->getRequest();
require_once(LIB_PATH.'/MFW/Paypal/Flows/Paypal_NVP.php');
$paypal_nvp = new MFW_Paypal_NVP();
// this should normally be filled by looping though the basket items
$data = array('L_PAYMENTREQUEST_0_NAME0'=>'Single License',
'L_PAYMENTREQUEST_0_NUMBER0'=>'1111-2222-3333-4444-5555-6666-7777-8888',
'L_PAYMENTREQUEST_0_AMT0'=>39.99, // or enterprise 299.00
'L_PAYMENTREQUEST_0_QTY0'=>1,
);
$_SESSION['Payment_Amount'] = 39.99;
$result = $paypal_nvp->CallShortcutExpressCheckout(59.98, $data);
$ack = strtoupper($result['ACK']);
if($ack == 'SUCCESS' || $ack == 'SUCCESSWITHWARNING') {
$paypal->RedirectToPayPal($result['TOKEN']);
exit();
}
}
The code in the Paypal_NCP class:
function generate_nvp_string($total_value, $data = array())
{
$params = array('PAYMENTREQUEST_0_AMT'=>$total_value,
'PAYMENTREQUEST_0_PAYMENTACTION'=>$this->payment_type,
'RETURNURL'=>$this->return_url,
'CANCELURL'=>$this->cancel_url,
'PAYMENTREQUEST_0_CURRENCYCODE'=>$this->currency,
);
$params = array_merge($params, $data);
$nvp_string = '';
foreach($params as $name => $value) {
$nvp_string.= '&'.$name.'='.$value;
}
// example string
// &PAYMENTREQUEST_0_AMT=39.99&PAYMENTREQUEST_0_PAYMENTACTION=Sale&RETURNURL=http://return-address&CANCELURL=http://cancel-address&PAYMENTREQUEST_0_CURRENCYCODE=EUR&L_PAYMENTREQUEST_0_NAME0=Single License&L_PAYMENTREQUEST_0_NUMBER0=1111-2222-3333-4444-5555-6666-7777-8888&L_PAYMENTREQUEST_0_AMT0=39.99&L_PAYMENTREQUEST_0_QTY0=1
return $nvp_string;
}
function CallShortcutExpressCheckout($total_value, $data = array())
{
$_SESSION['currencyCodeType'] = $this->currency;
$_SESSION['PaymentType'] = $this->payment_type;
$result = $this->hash_call('SetExpressCheckout', $this->generate_nvp_string($total_value, $data));
$ack = strtoupper($result['ACK']);
if ($ack == 'SUCCESS' || $ack == 'SUCCESSWITHWARNING') {
$_SESSION['TOKEN'] = urldecode($result['TOKEN']);
}
return $result;
}
So how do I get the information of the transaction for me to be able to process the payment in the backoffice? (I need an transaction ID for this)
You're only calling SetExpressCheckout. In order to finalize a transaction with Express Checkout, you must also call (optional) GetExpressCheckoutDetails to get the PayerID (a unique identifier of the buyer) and (required) DoExpressCheckoutPayment.
To recap:
To use Express Checkout, you would call the SetExpressCheckout API. In the API call, you specify the details of the products, amounts, and the RETURNURL. This is what you're doing in the code above.
Once you post this data to PayPal's API endpoint, you receive a token in return. You would then redirect the buyer, and append the token to the following URL: https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-XXXXXXX
Once the buyer has agreed to your purchase, he is redirected back to the URL you specified in the RETURNURL.
You should now show the order confirmation, and call the GetExpressCheckoutDetails API**.
When calling GetExpressCheckoutDetails, supply the token. In the GetExpressCheckoutDetails API response you'll find a PayerID.
Now you're ready to call DoExpressCheckoutPayment, and charge the buyer. Remember to include both the token and the payerID when calling DoExpressCheckoutPayment.
With regards to IPN: You don't really need it anymore, as you'll also get the TransactionID back in the API response to DoExpressCheckoutPayment.
IPN would be useful if you would subsequently want to 'keep track' of the transaction. E.g., get notified in case of any refunds / chargebacks, etc.
This simply requires setting up an IPN script and including NOTIFYURL=http://.... in both SetExpressCheckout and DoExpressCheckoutPayment.
** The PayerID is appended in the GET of your RETURNURL as well. So you could skip calling GetExpressCheckoutDetails if you wanted to.
(Partial copy of my answer at Why is DoExpressCheckoutPayment required for Paypal? )

Categories