I am developing a module for a payment gateway that can authorize and capture in two different steps. So far I am able to authorize the order by running the following code:
class My_Gateway_Model_Method_Cc extends Mage_Payment_Model_Method_Cc
{
protected $_code = 'mycode';
protected $_canSaveCc = true;
protected $_canRefund = true;
protected $_isGateway = true;
protected $_canAuthorize = true;
protected $_canCapture = true;
protected $_canCapturePartial = false;
public function authorize(Varien_Object $payment, $amount){
/* code to call the payment gateway and authorize the charge */
}
public function capture(Varien_Object $payment, $amount){
/* some code to capture the payment */
}
}
The problem is that when I place an order, and then I go into the invoicing panel I get this message:
'Invoice will be created without communication with payment gateway'
There is no option to capture the authorized payment nor it call the capture method when invoicing.
Ok, I have found the solution:
Basically, Magento closes any authorization transaction by default, and it enables the option to capture the payment on invoice when the authorization transaction is open. So all you have to do is to configure the transaction to stay openend (not closed)
So here is what I did:
public function authorize(Varien_Object $payment, $amount)
{
// Leave the transaction opened so it can later be captured in backend
$payment->setIsTransactionClosed(false);
/*
* Place all the code to connect with your gateway here!!!
*
*/
return $this;
}
have a look at this blog Magento Transactions section at the end.
if($result['status'] == 1){ // on success result from payment gateway
$payment->setTransactionId($result['transaction_id']);
$payment->setIsTransactionClosed(1);
$payment->setTransactionAdditionalInfo(Mage_Sales_Model_Order_Payment_Transaction::RAW_DETAILS, array('key1'=>'value1','key2'=>'value2'));
}
You have set
$_isGateway = true;
So the you also need to set the gateway url
In your payment model, the one that extends Mage_Payment_Model_Method_Abstract, you'll need to implement the method:
function getOrderPlaceRedirectUrl() {
return 'http://www.where.should.we.pay.com/pay';
}
Typically you redirect the user to a page on your site, /mymodule/payment/redirect for example, and then handle the redirection logic in the action of the controller. This keeps your payment model clean and stateless, while allowing you to some some kind of "You are now being transferred to the gateway for payment" message.
Save everything you need to decide where to redirect to in a session variable, again typically Mage::getSingleton('checkout/session').
Magento have a pretty solid, if messy, implementation of this for Paypal standard. You can checkout how they do it in app/code/core/Mage/Paypal/{Model/Standard.php,controllers/StandardController.php}.
Related
I want to assign different SMTP hosts to different authenticated users so that the privileged users can send mails faster through a dedicated SMTP server.
I can change the host in the service provider like:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->extend('swift.transport', function ($transportManager, $app) {
$app->make('config')->set('mail.host', 'just.testing.com');
return new TransportManager($app);
});
}
}
However since I need the authenticated user I created a listener listening to "Authenticated" event and moved the code there like:
class ChangeSmtpServer
{
public function handle($event)
{
app()->extend('swift.transport', function ($transportManager, $app) use ($event) {
$app->make('config')->set('mail.host', $event->user->smtp_server);
return new TransportManager($app);
});
}
}
The host is not changed this time... So inside the service provider I can overwrite the setting but not inside the listener.
Any ideas why?
Your code works on my setup just fine. Actually it should still work if you keep it in AppServiceProvider because Laravel will only resolve bindings when they are relevant. So the code pertaining to Mail driver configuration will not be run until you actually try to send a Mail. By that point your user will already be authenticated. However...
This will only work when you send your mail synchronously. When you want to send from a Queue worker, there won't be any authenticated user and the Authenticated event will never be called. You need a way to keep track of which user is sending the e-mail.
Here is my solution:
Add a sender argument to your Mail class constructor (the one in App\Mail) that takes in the User object that's sending the e-mail.
public $sender;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(User $sender)
{
$this->sender = $sender;
}
Then add this method that configures your SwiftMailer instance
private function usingSendersSmtp()
{
$mailTransport = app()->make('mailer')
->getSwiftMailer()
->getTransport();
if ($mailTransport instanceof \Swift_SmtpTransport) {
/** #var \Swift_SmtpTransport $mailTransport */
$mailTransport->setHost($this->sender->smtp_host);
$mailTransport->setUsername($this->sender->smtp_username);
$mailTransport->setPassword($this->sender->smtp_password);
// Port and authentication can also be configured... You get the picture
}
return $this;
}
And finally call it inside your build method:
public function build()
{
return $this->usingSendersSmtp()
->view('test');
}
When sending the mail, instantiate your class like new YourMailClass(auth()->user()) and then send it or queue it with the Mail facade to whomever you like. It also might be a good idea to create an abstract class that inherits Illuminate\Mail\Mailable and move these extra stuff over there so you won't have to duplicate this in every other mail class. Hope this helps!
I want to access route request parameters inside laravel form requests authorize. I cant find an example describing this.
// Works fine when you want id
dd($this->route('myResourceName'));
// How to do when I want something else???
dd($this->route('anotherAttribute'));
// Above give null probably because it is a resourceful controller
On a side note, I dont understand this design, whats the point?
$this->route('anyAttribute') would be the easiest, right?
Edit: more extensive example
class UpdateSlotAPIRequest extends APIRequest
{
public function __construct(){
parent::__construct();
$this->slot = Slot::find($this->route('slot'));
$this->access_token = $this->route('access_token'); // this is not working!
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
// If administrator is logged in all is good.
// If slot is free its ok.
// If its not free but you provide good access_token its also fine.
return Auth::check() || $this->slot->isAvailable() || $this->slot->isValidAccessToken($this->access_token);
}
...
```
$access_token = request()->input('access_token');
Found it in https://laravel.com/docs/5.4/helpers
I am working on API based Payment gateway in magento, When user directly pay from credit card, I am doing my all process by calling api specific function in capture method of payment gateway.
When I will enable 3D secure option for payment gateway I need to redirect user to 3rdparty for verification for that I am using getOrderPlaceRedirectUrl with condition.
With Condition I ma also saving order with pending status but Magento generate the invoice and mark as paid and change status to processing. that I need to do once get successful authentication from 3rdparty.
for updating order status using following code:
$order->setState( Mage_Sales_Model_Order::STATE_NEW, true );
$order->save();
If anyone can help how can I control to not generate invoice in capture method will be very appreciated.
If you use capture() in your payment method, and your capture() method returns without throwing an exception, then Magento assumes that the capture is done, "the money is in your pocket", so it makes the invoice. This is not good if you use a 3rd party payment gateway.
You can do the following: set your *payment_action* to order in your config.xml
<config>
<default>
<payment>
<yourPaymentCode>
<order_status>pending_payment</order_status>
<payment_action>order</payment_action>
...
In your payment method set the attributes and implement the order() method.
Snd set getOrderPlaceRedirectUrl, but you already did that.
class Your_Module_Model_PaymentMethod
extends Mage_Payment_Model_Method_Abstract
{
// set these attributes
protected $_code = 'yourPaymentCode';
protected $_isGateway = true;
protected $_canCapture = false;
protected $_canAuthorize = false;
protected $_canOrder = true;
public function order(Varien_Object $payment, $amount)
{
// initialize your gateway transaction as needed
// $gateway is just an imaginary example of course
$gateway->init( $amount,
$payment->getOrder()->getId(),
$returnUrl,
...);
if($gateway->isSuccess()) {
// save transaction id
$payment->setTransactionId($gateway->getTransactionId());
} else {
// this message will be shown to the customer
Mage::throwException($gateway->getErrorMessage());
}
return $this;
}
And somehow the gateway has to respond. In my case they redirect the customer to $responseUrl given in init(), but they warn that it is possible that the user's browser crashes after payment but before they can be redirected to our store: In that case they call the URL in the background, so handling that URL cannot rely on session data. I made a controller for this:
public function gatewayResponseAction()
{
// again the imaginary example $gateway
$order = Mage::getModel('sales/order')->load( $gateway->getOrderId() );
$payment = $order->getPayment();
$transaction = $payment->getTransaction( $gateway->getTransactionId() );
if ($gateway->isSuccess())
{
$payment->registerCaptureNotification( $gateway->getAmount() );
$payment->save();
$order->save();
$this->_redirect('checkout/onepage/success');
}
else
{
Mage::getSingleton('core/session')
->addError($gateway->getErrorMessage() );
// set quote active again, and cancel order
// so the user can make a new order
$quote = Mage::getModel('sales/quote')->load( $order->getQuoteId() );
$quote->setIsActive( true )->save();
$order->cancel()->save();
$this->_redirect('checkout/onepage');
}
}
Try setting the state to pending payment, something like the following;
$order->setState(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT,
'pending_payment',
'3D Secure Auth Now Taking Place')->save();
P.S. The PayPal module that comes with Magento does exactly what you are trying to do, so take a look at PayPal Standard model and controller for some pointers.
Is there a way to catch the event when the customer verifies it's account? I need this feature to enable user's access to other integrated subsystem
Since confirmAction() doesnt seem to fire any events in
/app/code/core/Mage/Customer/controllers/AccountController.php
You could do either
Overriding Frontend Core Controllers to create you own event using Mage::dispatchEvent() or add code directly to confirmAction in AccountController.php
Use #Pavel Novitsky answer but you may need to check that you are on the confirm account controller or check for the changing of email verification flag, because this event will trigger every time a customer information is change/updated
eg
public function myObserver(Varien_Event_Observer $observer)
{
if(Mage::app()->getRequest()->getControllerName() == '....account_confirm'){
$customer = $observer->getCustomer();
....
}
}
Every model has standard load_before, load_after, save_before, save_after, etc. events. Look at the Mage_Core_Model_Abstract to get the list of all predefined events.
For customers you can use customer_save_after event. In observer check original data vs new data:
public function myObserver(Varien_Event_Observer $observer)
{
$customer = $observer->getCustomer();
$orig_active_flag = $custoner->getOrigData('is_active');
$new_active_flag = $customer->getData('is_active');
// do something here …
return $this;
}
Even you can create your own event after customer vefication using below code.
Mage::dispatchEvent('Yuor_Unique_Event_Name', array());
Now using this event you can do anything you want.
I have problem with magento messages. I am building custom module which in theory should be able to restrict access to some parts of the store. I have created an observer which hook into controller_action_predispatch event and checks if current request can be accessed by the user. If the action cannot be accessed the observer redirects user and sets the error info. I want to set the redirect url to the page the customer is coming from in order to avoid clicking through entire shop. I am looking at the HTTP_REFERER and use it if it is set, otherwise I redirect customer to homepage. The problem is that in the later case (homepage redirect) everything works great but when I set url based on the referer I do not see error message in message box.
The code from the observer ($name variable is a string):
Mage::getSingleton('core/session')->addError('Acces to '.$name.' section is denied');
$url = Mage::helper('core/http')->getHttpReferer() ? Mage::helper('core/http')->getHttpReferer() : Mage::getUrl();
Mage::app()->getResponse()->setRedirect($url);
What I found interesting is that if I do any change in the observer file and save it, then the next request which fails and gets redirected to referer url shows the error information but any subsequent loses the messages.
I was thinking that the problem is in the full url and my local instalation (I am using .local domain) but so I tried adding
$url = str_replace(Mage::getBaseUrl(), '/', $url);
but this did not helped.
I also tried redirect using php header() function without any result as well.
All cache is disabled. The workflow which triggers the problem is as follows:
I'm going to any accessible page (for example /customer/account)
Click on cart link (cart for this account is disabled)
Return to /customer/account and the error message is displayed
Click on cart link again
Return to /customer/account but no error message
Any hint on where to look will be appreciated.
//A Success Message
Mage::getSingleton('core/session')->addSuccess("Some success message");
//A Error Message
Mage::getSingleton('core/session')->addError("Some error message");
//A Info Message (See link below)
Mage::getSingleton('core/session')->addNotice("This is just a FYI message...");
//These lines are required to get it to work
session_write_close(); //THIS LINE IS VERY IMPORTANT!
$this->_redirect('module/controller/action');
// or
$url = 'path/to/your/page';
$this->_redirectUrl($url);
This will work in a controller, but if you're trying to redirect after output has already been sent, then you can only do that through javascript:
<script language=”javascript” type=”text/javascript”>
window.location.href=”module/controller/action/getparam1/value1/etc";
</script>
Your messages get lost because you use an unfavorably way for a redirect in controller_action_predispatch. Your solution causes on the one hand the "message lost" and on the other hand, it wastes processing power of your server.
When you take a look at Mage_Core_Controller_Varien_Action::dispatch(), you'll see that your solution doesn't stop the execution of the current action, but it should do that with a redirect. Instead Magento executes the current action to its end, including the rendering of the message you had added before. So no wonder why the message gets lost with the next client request, Magento had it already rendered before, with the server response which includes your redirect.
Further you'll see in Mage_Core_Controller_Varien_Action::dispatch() only one possibility to stop the execution of the current action and skip directly to the redirect, which is in line 428 catch (Mage_Core_Controller_Varien_Exception $e) [...]. So you have to use Mage_Core_Controller_Varien_Exception which is quite unpopular, but the only right solution for your purpose. The only problem is, this class has a bug since it was introduced in Magento 1.3.2. But this can be easily fixed.
Just create your own class which is derived from Mage_Core_Controller_Varien_Exception:
/**
* Controller exception that can fork different actions,
* cause forward or redirect
*/
class Your_Module_Controller_Varien_Exception
extends Mage_Core_Controller_Varien_Exception
{
/**
* Bugfix
*
* #see Mage_Core_Controller_Varien_Exception::prepareRedirect()
*/
public function prepareRedirect($path, $arguments = array())
{
$this->_resultCallback = self::RESULT_REDIRECT;
$this->_resultCallbackParams = array($path, $arguments);
return $this;
}
}
So you can now implement your solution realy clean with that:
/**
* Your observer
*/
class Your_Module_Model_Observer
{
/**
* Called before frontend action dispatch
* (controller_action_predispatch)
*
* #param Varien_Event_Observer $observer
*/
public function onFrontendActionDispatch($observer)
{
// [...]
/* #var $action Mage_Core_Model_Session */
$session = Mage::getSingleton('core/session');
/* #var $helper Mage_Core_Helper_Http */
$helper = Mage::helper('core/http');
// puts your message in the session
$session->addError('Your message');
// prepares the redirect url
$params = array();
$params['_direct'] = $helper->getHttpReferer()
? $helper->getHttpReferer() : Mage::getHomeUrl();
// force the redirect
$exception = new Your_Module_Controller_Varien_Exception();
$exception->prepareRedirect('', $params);
throw $exception;
}
}
this will work , so try it:
$url = 'path/to/your/page';
$this->_redirectUrl($url);
return false;
This means you are not allowing again to execute anything else.