Laravel Cashier to synchronise payments to the first of the next month - laravel

I have been trying to integrate a payment system for a subscription service using Laravel Cashier.
I have set up my RegistrationController so that it works out how many days until the first day of the next month.
Using $user->trial_ends_at = Carbon::now()->addDays($datediff);
When creating a user it sets the trial end date for the first of the next month.
From here I want to use the $user->onTrial() method to create the actual subscription.
Please note: Even though the user is created there is no customer created in Stripe as the subscription has not yet been created.
My questions:
In the registration form the card details are taken so the users can subscribe and set up their payment method. This involves stripe validating the card and sending back a token.
Can this token be stored and used later?
Assuming I can use this token later on, to create the actual subscription in cashier I have to check whether the users trial has ended using the onTrial method.
The subscription is created using this:
$user->subscription($subscription_plan)->create($stripeToken);
Where is best to add the above code so that the user is subscribed and the billing period is started at the end of the trial?
This isn't mentioned at all in the in the documentation so I was just wondering if I create a new controller to actually start the subscription where to call the function and how to get it to run as each users trial ends.
Edit:
Would something like this be possible using Queues?
http://laravel.com/docs/4.2/queues
$start_subscription = Carbon::now()->addDays($datediff);
Queue::later($start_subscription, 'RegistrationController#subscribe',
array('stripeToken' => $stripeToken, 'details' => $otherDetails));
and then run the subscription method for each user at the set time.

I noticed some issues with creating the Stripe customer at the same time as the subscription. I was ending up with lots of extra customers inside Stripe because if the customer is created, but the subscription has an error, it doesn't connect them in Cashier, but the customer is connected in Stripe.
So to separate the two, I created a helper method in the User model. It will let me create the customer without creating a subscription. I can then create a subscription at a later time.
I think this addresses your issue.
Helper methods in user model:
/**
* Method to create stripe customer without subscription
*/
public function createStripeCustomer($token, $properties) {
$gateway = $this->subscription();
$customer = $gateway->createStripeCustomer($token, $properties);
$gateway->updateLocalStripeData($customer);
}
public function getStripeCustomer() {
$gateway = $this->subscription();
return $gateway->getStripeCustomer();
}
Then in my API controller where I handle the subscription:
if (!$user->getStripeId()) {
$user->createStripeCustomer($inputs['token'], ['email' => $user->email]);
//this can be done at a much later time, the customer is already stored.
$user->subscription($inputs['plan'])->create(null, [], $user->getStripeCustomer());
Flash::success("Subscription Success!");
} elseif (!$user->getStripeSubscription()) {
$user->subscription($inputs['plan'])->create($inputs['token'], [], $user->getStripeCustomer());
Flash::success("Subscription Success!");
}

As for the Stripe token, you'll want to use that within a few moments of its creation. It's not expected to last for longer than that.
As for your other questions, I'm not a Laravel person, so I couldn't speak to that, but PHP's strtotime() function takes a variety of strings and returns a timestamp. So if you always wanted to have billing start at noon of the first of the next month, you'd set your timestamp to:
strtotime('first day of next month 12:00');
Hope that helps,
Larry
PS I work on Support at Stripe.

Related

Application wide subscription to a channel

Quick question about Pusher.js.
I've started working on a notifications functionality today and wanted to do this with Pusher.js, since I used it for my chat.
I'm working in Laravel. What I want to achieve is an application wide subscription to a channel. When a user registrates a channel is being made for him "notifications_channel", which I store in the database. I know how to register a user to a channel, but once he leaves that page, the room vacates. That's not really what I'm looking for, since I'd like to send the user notifications no matter where he is on the platform.
I couldn't really find anything like this in the documentation, so I thought maybe one of you guys know how to do so.
Here are some snippets of what I do:
When the user registrates I fire this:
$generateChannel = User::generateNotificationsChannel($request['email']);
This corresponds to this in my model:
public static function generateNotificationsChannel($email){
$userID = User::getIdByMail($email);
return self::where('email', $email)->update(['notifications_channel' => $userID."-".str_random(35)]);
}
It's fairly basic, but for now that's all I need.
So for now, when the user logs in the Index function of my HomeController is being fired, which gathers his NotificationsChannel from the database and sends it to the view.
public function index()
{
$notificationsChannel = User::getUserNotificationsChannel(\Auth::user()->id);
return view('home', compact('notificationsChannel', $notificationsChannel));
}
Once we get there I simply subscribe the user to that channel and bind him to any events linked to the channel:
var notifications = pusher.subscribe('{{$notificationsChannel}}');
channel.bind('new-notification', notifyUser);
function notifyUser(data){
console.log(data);
}
So as you can see, for now it's pretty basic. But my debug console shows me that the channel vacates as soon as the user leaves /home.
So the question is, how do I make him subscribed to the channel, no matter where he is on the platform?
Any and all help would be deeply appreciated!
Thanks in advance.
I've found a fix to this issue, I've decided to send the notifications-channel to my Master layout, which I use to extend all views after the user has logged in. In my master layout I subscribe the user to his own notifications channel.
For people who might be interested in how I did it:
I altered the boot function of my AppServiceProvider which you can find in \app\Providers\AppServiceProvider. The code looks like this:
public function boot()
{
view()->composer('layouts.app', function($view){
$channel = User::getUserNotificationsChannel(\Auth::user()->id);
$view->with('data', array('channel' => $channel));
});
}
In my Master layout I simply subscribed the user by grabbing the channel name.

How to deal with non existent emails

I am making an app that allow to register to a newsletter and then manage it.
There is the normal validation if the field is empty, if is email type, unique ecc..
But how can i check if the email really exists?
If someone enter a fake mail, that mail is entered in the database, and this can mess up the database. And there are other annoyance.
Is possible to check if the mail really exists?
Otherwise it is possible to automatically delete an unregistered user after 10 days?
You can create a rule to check if the e-mail format is valid:
$validator = Validator::make(
Input::all(),
array('email' => 'required|email')
);
if ($validator->fails())
{
return "This is not a valid e-mail";
}
But this will only check for the format (name#domain.tld), because there is no way to know if an e-mail address really exists.
Well... this is not entirely true, there is a way: send an e-mail to that address and if you don't get an error message, the address exists, probably.
The best way to do what you need is, when a user creates an account in your system, send him/her a confirmation e-mail, with a link where he/she should click to validate that address. If the account is not validated for x days you delete it. This is a question that might help you on that: Laravel 4: how to make confirmation email?.
About your last question: create an Artisan Command that checks for unvalidated accounts and delete them. Take a look at the docs, it's easy, Laravel does almost all the job for you. And you can run this command once a day using cron or task manager:
php /var/www/your-site-dir/artisan check:accounts
EDIT:
This question will show you how to create a command: Creating and using Laravel 4 commands
And in the method fire() you should do something like:
public function fire()
{
$dt = Carbon\Carbon::now();
User::where('activated', false)
->where('created_at', '<', $dt->subDays(10))
->delete();
}
There are some APIs you can use to verify the validity and existence of emails, but I am not sure how good they are. The free ones usually limit you to something like 10 an hour which probably would not be nearly enough for you, depending on how busy your site is.
Check out http://verify-email.org/register/levels.html
If you are worried about the sheer amount of emails being entered, you could probably log $_SERVER['REMOTE_ADDR'], which would be the client's IP address in the DB on emails and check to make sure that is unique as well before you save additional records to the table.
As far as how to actually validate the existence and validity of the entered email yourself, I believe you'd have to use fsockopen and issue smtp commands to servers. I did find this php class which attempts to do what we are talking about http://www.webdigi.co.uk/blog/wp-content/uploads/2009/01/smtpvalidateclassphp.txt
I doubt it works, but should get you a nice starting point.

Magento - Get customer group ID of the user logging in before they log in

So during login, I want to check if the user is in a specific customer group and prevent login if that check fails.
I have extended the AccountController, and added in a quick check:
$customer = Mage::getModel('customer/customer')->loadByEmail($login['username']);
if ($customer->getGroupId() != 2) {
$msg = Mage::getSingleton('core/session')->addError($this->__('You must have a wholesale account to access this area.'));
header('Location: '. Mage::getUrl('customer/account/login'));
exit;
}
However, running this returns session error message, "Customer website ID must be specified when using the website scope".
Basically, I just need to grab the group id of the user who is attempting to login, and I figured I could grab this through the method supplied in the customer model, loadByEmail(). But yeah, it's Friday, and apparently the MageLords want me to stay late.
I have tried a number of methods to get this working, including allowing the login, then checking the ID, then doing $session->logout() if the check fails, but this was preventing me from displaying a session error message since the logout() method is clearing all session messages (including 'core/session').
Any ideas?
You should set website_id before working with loadByEmail method. I know, it seems weird, but this is dictated by the fact, that customer could be a website-scoped entity. So the loadByEmail method will throw exception if customer model is not assigned to any website.
$customer = Mage::getModel('customer/customer');
$customer->setWebsiteId(Mage::app()->getStore()->getWebsiteId())
->loadByEmail($login['username']);

Missing amount and order summary in PayPal Express Checkout

I have integrated paypal into codeigniter with paypal_helper (didn't rememeber where I found it, but it is a slightly rewritten version of Paypals original code for express checkout. I try calling this function,
CallShortcutExpressCheckout( $paymentAmount, $currencyCodeType,
$paymentType, $returnURL, $cancelURL)
sending $paymentAmount as int, $currencyCodeType as "NOK" and $paymentType as "Sale".
Both in Sandbox and live, no amount appears on the paypal site...
What could be wrong?
Edit, to further explain my process. I use this, mostly as specified in the https://www.paypal-labs.com/integrationwizard/ecpaypal/cart.php. This should be doable without the form? The paymentAmount could be sent as a standard variable, when calling the function CallShortcutExpressCheckout?:
$resArray = CallShortcutExpressCheckout ($paymentAmount, $currencyCodeType, $paymentType, $returnURL, $cancelURL);
$ack = strtoupper($resArray["ACK"]);
if($ack=="SUCCESS" || $ack=="SUCCESSWITHWARNING")
{
RedirectToPayPal ( $resArray["TOKEN"] );
}
else
{
//Display a user friendly Error on the page using any of the following error information returned by PayPal
$ErrorCode = urldecode($resArray["L_ERRORCODE0"]);
$ErrorShortMsg = urldecode($resArray["L_SHORTMESSAGE0"]);
$ErrorLongMsg = urldecode($resArray["L_LONGMESSAGE0"]);
$ErrorSeverityCode = urldecode($resArray["L_SEVERITYCODE0"]);
echo "SetExpressCheckout API call failed. ";
echo "Detailed Error Message: " . $ErrorLongMsg;
echo "Short Error Message: " . $ErrorShortMsg;
echo "Error Code: " . $ErrorCode;
echo "Error Severity Code: " . $ErrorSeverityCode;
}
The token is saved in a database. The user gets redirected to Paypal, where no amount is listed.
As you're not passing so called 'line item details' (product data), PayPal doesn't display the total amount.
If you only want to show the amount for the current purchase, redirect buyers to https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-xxxxxx&useraction=commit (instead of https://www.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=EC-xxxxx)
If you want to start sending line-item details to PayPal, include the following in your SetExpressCheckout API request:
// Total amount of the purchase, incl shipping, tax, etc
PAYMENTREQUEST_0_AMT=300.0
// Total amount of items purchased, excl shipping, tax, etc
PAYMENTREQUEST_0_ITEMAMT=300.0
// Authorize the funds first (Authorization), or capture immediately (Sale)?
PAYMENTREQUEST_0_PAYMENTACTION=Sale
// First item
L_PAYMENTREQUEST_0_NAME0=Item1
L_PAYMENTREQUEST_0_QTY0=1
L_PAYMENTREQUEST_0_AMT0=100.00
// Second item
L_PAYMENTREQUEST_0_NAME1=Item2
L_PAYMENTREQUEST_0_QTY1=1
L_PAYMENTREQUEST_0_AMT1=200.00
If you want to see this in your own history as well, you'll also need to include this in DoExpressCheckoutPayment.
This was also posted in php paypal express checkout problem
After an extensive reading on messy Paypal docs site this is a short ExpressCheckout guide working on year 2013. I wanted to have item details shown on paypal payment page and merchant transaction history page.
Paypal documentation links
https://developer.paypal.com/webapps/developer/docs/classic/api/
https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/SetExpressCheckout_API_Operation_NVP/
https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/DoExpressCheckoutPayment_API_Operation_NVP/
https://developer.paypal.com/webapps/developer/docs/classic/api/merchant/GetExpressCheckoutDetails_API_Operation_NVP/
You can call following url methods directly on web browser, update token and payerid parameters accordingly.
This is a digital goods so shipping and handling fees are not given. Single item row. Amount and tax fees are given. Do not require a confirmed delivery address, no shipping address fields, no allow freetext note, payer don't need paypal account and no registration required (solutiontype=sole). Activate credit card section on paypal site (landingpage=billing). Use customized brand title on paypal site. Use custom field to give own value for tracking purpose. Merchant site transaction history must show item details (give item details on SetExpressCheckout and DoExpressCheckoutPayment methods).
SetExpressCheckout method opens a new transaction
https://api-3t.sandbox.paypal.com/nvp?
USER=<userid>
&PWD=<pwd>
&SIGNATURE=<mysig>
&METHOD=SetExpressCheckout
&VERSION=98
&PAYMENTREQUEST_0_PAYMENTACTION=SALE
&REQCONFIRMSHIPPING=0
&NOSHIPPING=1
&ALLOWNOTE=0
&SOLUTIONTYPE=Sole
&LANDINGPAGE=Billing
&BRANDNAME=MY+WEBSHOP+TITLE
&PAYMENTREQUEST_0_AMT=22.22
&PAYMENTREQUEST_0_TAXAMT=4.30
&PAYMENTREQUEST_0_ITEMAMT=17.92
&PAYMENTREQUEST_0_DESC=mypurdesc
&PAYMENTREQUEST_0_CUSTOM=custom1
&PAYMENTREQUEST_0_CURRENCYCODE=EUR
&L_PAYMENTREQUEST_0_NUMBER0=itemid1
&L_PAYMENTREQUEST_0_NAME0=MyItem1
&L_PAYMENTREQUEST_0_DESC0=Item1+description
&L_PAYMENTREQUEST_0_QTY0=1
&L_PAYMENTREQUEST_0_AMT0=17.92
&L_PAYMENTREQUEST_0_TAXAMT0=4.30
&RETURNURL=https://myserver.com/webapp/paypal.jsp%3Fcmd=successexp
&CANCELURL=https://myserver.com/webapp/paypal.jsp%3Fcmd=cancelexp
Reply must have ACK=Success or ACK=SuccessWithWarning, read TOKEN value
Redirect user browser to Paypal site, give token value
https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=<token>
User uses paypal account or credit card. Paypal redirects user to return or cancel url.
Redirect destination url gets token and PayerID parameter values.
Transaction is not completed yet we must call doExpressCheckoutPayment method.
Show confirm dialog on screen (with OK, CANCEL button) or simple case
commit a transaction and show "Thank you, purchase completed" message.
User has already accepted a payment in paypal site and expects transaction be finalized.
You may commit transaction within a same request-response handler or using
asynchronous background task. Paypal site may temporarily be unavailable so don't expect it to work immediately.
Commit transaction if redirect was success, use token and payerid
https://api-3t.sandbox.paypal.com/nvp?
USER=<userid>
&PWD=<pwd>
&SIGNATURE=<mysig>
&METHOD=DoExpressCheckoutPayment
&VERSION=98
&PAYMENTREQUEST_0_PAYMENTACTION=SALE
&PAYMENTREQUEST_0_AMT=22.22
&PAYMENTREQUEST_0_TAXAMT=4.30
&PAYMENTREQUEST_0_ITEMAMT=17.92
&PAYMENTREQUEST_0_CURRENCYCODE=EUR
&L_PAYMENTREQUEST_0_NUMBER0=itemid1
&L_PAYMENTREQUEST_0_NAME0=MyItem1
&L_PAYMENTREQUEST_0_QTY0=1
&L_PAYMENTREQUEST_0_AMT0=17.92
&L_PAYMENTREQUEST_0_TAXAMT0=4.30
&token=<token>
&payerid=<payerid>
Read ACK=Success and verify fields
ACK=Success
PAYMENTINFO_0_PAYMENTSTATUS=Completed
PAYMENTINFO_0_ACK=Success
PAYMENTINFO_0_AMT=22.22 total amount must match
PAYMENTINFO_0_FEEAMT=0.99 (just for fun, read paypal comission fee)
PAYMENTINFO_0_CURRENCYCODE=EUR currency must match
(Optional) Read transaction details from Paypal
You can use this during transaction workflow or any time if stored a token for later use.
https://api-3t.sandbox.paypal.com/nvp
?USER=<userid>
&PWD=<pwd>
&SIGNATURE=<mysig>
&METHOD=GetExpressCheckoutDetails
&VERSION=98
&token=<token>
Read response parameters.
ACK=Success
CHECKOUTSTATUS=PaymentActionCompleted
PAYMENTREQUEST_0_AMT=22.22
PAYMENTREQUEST_0_TAXAMT=4.30
PAYMENTREQUEST_0_CURRENCYCODE=EUR
(Optional) Read and save transaction id, correlation id and token id and write to logtable.
PAYMENTREQUEST_0_TRANSACTIONID=11E585715B622391E
CORRELATIONID=4534b683c335f
I'm willing to receive comments if there is any logic errors.
Check this link, hope it helps in some sense:
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_api_ECGettingStarted
PAYMENTREQUEST_0_AMT=amount //for amount

Magento Multiple Authorize.net Gateways

I've seen this question asked with regard to currency type, but what I am asking is how to configure a second Authorize.net account on the same store for a different credit card type. So, we want some credit cards to use the first main Authorize.net gateway, but the others to use the secondary Authorize.net account so that the payments can be routed into two different bank accounts. This is for purposes of reconciliation and is a constraint; can't be modified.
I'm figuring that all I need to do is figure out once the order has been submitted (but before it has been sent via API to Authorize.net) which card type it is, to know which credentials to pass to the API, but I'm unsure as to where to add this code, or the best way in which to add it.
Any insight or advice would be greatly appreciated.
By default, there is no way to accomplish this, so you'll need to use some custom code. Specifically, override the Authnet payment class Mage_Paygate_Model_Authorizenet:
class MyNamespace_MyModule_Model_Authorizenet extends Mage_Paygate_Model_Authorizenet {
/**
* Prepare request to gateway
*
* #link http://www.authorize.net/support/AIM_guide.pdf
* #param Mage_Sales_Model_Document $order
* #return unknown
*/
protected function _buildRequest(Varien_Object $payment)
//see below
}
}
In that function, on line 277 for me, the following code is executed to set the Authnet account:
$request->setXLogin($this->getConfigData('login'))
->setXTranKey($this->getConfigData('trans_key'))
->setXType($payment->getAnetTransType())
->setXMethod($payment->getAnetTransMethod());
Instead, you want something along these lines:
if(whatever cc type) {
// set alternate gateway
} else {
// set default gateway
}
To accomplish this, you'll also want to create new options in the backend to hold the credentials in an encrypted form. Hope that helps!
Thanks,
Joe

Resources