Laravel Cashier - Stripe webhook handling - laravel-5

I am handling a webhook for failed payments and the webhook is being successfully called. However, from the Laravel documentation it appears that a subscription would be automatically cancelled after 3 failed payments. In my case it is not being cancelled.
Is it because I have overridden the inbuilt behaviour? Or have I missed a trick?
Here is my webhook code:
public function handleChargeFailed(array $payload)
{
//first log the event in full
Log::info('Charge Failed - StripeWebhook - handleChargeFailed()', ['details' => json_encode($payload)]);
$tenant = Tenant::where('stripe_id', $payload['data']['object']['customer'])->first();
Mail::send(
//...//
});
return new Response('Webhook Handled', 200);
}

I have solved it myself - in case anyone else has the same issue. Once you handle a webhook yourself you need to completely handle it. In other words you have overridden completely the default inbuilt handling.

Related

Laravel cashier `$user->subscribed()` returning true when no subscription exists

My code is simple:
if($user->subscribed('main')){
$user->subscription('main')->cancel();
}
I get this response:
Stripe \ Error \ InvalidRequest
No such subscription: sub_EQTvxKjit2Ak6i
The subscription was in fact previously cancelled, so it shouldn't be giving a true response.
I tried returning $user->subscribed('main') and it came back as true.
This user has other subscriptions, but 'main' is not active.
Am I missing something?
Update Cashier v13.4
You can first sync the database entry with Stripe using:
optional($user->subscription('main'))->syncStripeStatus();
Then call
if ($user->subscribed('main')) { ... }
$user->subscribed() only checks the database for subscription status--it doesn't query the Stripe API at all. But when you try to cancel that subscription, then it queries the API.
So that could produce an error like this if your database is out of sync with your data in Stripe. Maybe your database has subscription from Stripe test mode, but you're querying the Stripe production API? Or vice versa?
If you read Billable trait then you will find this function there
public function subscribed($subscription = 'default', $plan = null)
{
$subscription = $this->subscription($subscription);
if (is_null($subscription)) {
return false;
}
if (is_null($plan)) {
return $subscription->valid();
}
return $subscription->valid() &&
$subscription->stripe_plan === $plan;
}
This function only checks subscription record exists in subscriptions table or not.
According to above function if subscription does not exist then it will return false.
Make sure you are not doing any mistake.

Database notifications don't show up after testing notify

I have a unit test with the following:
use \Illuminate\Notifications\DatabaseNotification;
public function testMailSentAndLogged()
{
Notification::fake();
$user = factory(User::class)->create();
$emailAddress = $user->emailAddress;
$emailAddress->notify(new UserCreated);
Notification::assertSentTo(
$emailAddress,
UserCreated::class
);
error_log('DatabaseNotification '.print_r(DatabaseNotification::get()->toArray(), 1));
$this->assertEquals(1, $emailAddress->notifications->count());
}
My Notification has this for the via():
public final function via($notifiable)
{
// complex logic...
error_log('mail, database');
return ['mail', 'database'];
}
The code fails on the $this->assertEquals code. the error_log produces the following:
[03-Jan-2018 01:23:01 UTC] mail, database
[03-Jan-2018 01:23:01 UTC] DatabaseNotification Array
(
)
WHY don't the $emailAddress->notifications pull up anything? Why doesn't DatabaseNotification::get() pull anything?;
In your test, you are calling the method
Notification::fake();
As stated in Laravel's documentation on Mocking,
You may use the Notification facade's fake method to prevent
notifications from being sent.
Actually, this bit of code is the assertion that the Notification would have been sent, under normal circumstances (ie in prod) :
Notification::assertSentTo();
If you remove the call to Notification::fake(), your notification should appear in your testing database.
So you kinda have two solutions. The first one is to remove the call to fake(), thus really sending the notification, which will appear in the database. the second is not to test if the notification was written successfully in the database : it's Laravel's responsibility, not your application's. I recommand the second solution :)

Laravel cashier 6 renew subscription after cancellation

I've just updated Laravel cashier package from 5 version to the latest 6 version. It supports multiple subscriptions and it's really cool. But I've got one problem with renewing subscription after subscription cancellation.
I'm removing subscription manually from stripe dashboard and customer.subscription.deleted event is firing.
Cashier method is catching this event:
\Laravel\Cashier\Http\Controllers\WebhookController#handleWebhook
And $subscription->markAsCancelled(); is firing.
From that moment subscription cannot be renewed. I've tried to use resume() function, but subscription can be resumed only(!) on grace period.
In previous version of cashier I was using swap() method to resume subscription. Now it returns:
Stripe\Error\InvalidRequest: Customer cus_*** does not have a subscription with ID sub_***** in /**/vendor/stripe/stripe-php/lib/ApiRequestor.php:103 from API request 'req_****'
Creating new customer and subscription is not very efficient way. What your thoughts about this issue?
My solution at this moment:
public function resume()
{
$user = Auth::user();
$subscription = $user->subscription(ServicePackageRepository::SUBSCRIPTION_NAME);
if ($subscription->cancelled() && $subscription->onGracePeriod()) {
//if it was cancelled by user in grace period
$subscription->resume();
return $this->respondWithSaved([]);
} else { //if cancelled by payment failure or smth else...
if($user->subscription(ServicePackageRepository::SUBSCRIPTION_NAME)) {
$user->newSubscription(ServicePackageRepository::SUBSCRIPTION_NAME,
$user->subscription(ServicePackageRepository::SUBSCRIPTION_NAME)->stripe_plan)
->create();
return $this->respondWithSaved([]);
} else {
return $this->respondWithError([]);
}
}
}

Laravel 4.1 + Push Queues + Error Queues

My goal is to somehow notify me if a push message fails after X attempts.
Iron.io push queues docs describe: Error Queues
http://dev.iron.io/mq/reference/push_queues/#error_queues
Following the docs, I have to define an error_queue option in order to failed messages trigger a message in the specified error_queue option.
How can I define an option if push method in IronQueue.php doesn't support option argument. I see that pushRaw does support option argument.
How can I transform the following push example into a pushRaw
Route::get('someroute', function()
{
Queue::push('SomeClass', array('time' => time()));
});
class SomeClass{
public function fire($job, $data)
{
// do something
$job->delete();
}
}
Other ways of detecting push queues fails are also welcome.
As #cmancre said, you can use HUD to set the error queue or you could use the API to set it: http://dev.iron.io/mq/reference/api/#update_a_message_queue
Iron guys just rolled out an UI allowing us to set a error_error via iron admin panel.
In case your error_queue is already firing, to complete the cycle, you need to know which message failed.
To grab the error message information, in the error_queue route just do:
// First we fetch the Request instance
$request = Request::instance();
// Now we can get the content from it
$content = $request->getContent();
Reference: http://www.codingswag.com/2013/07/get-raw-post-data-in-laravel/

HOW TO: Get real-time notifications in Laravel 4 using Iron.io MQ, Push Queues & AJAX

I've integrated ironMQ push queues in my Laravel 4 app for longer running processes. I have some views that perform a $.get that might take 30 seconds. I wanted to see what others are doing to easily get notified when ironMQ is done pushing back to an endpoint.
An example of what I will be doing with push queues mostly:
public function getCompletedTasks() {
$user = User::find(Auth::user()->id);
Queue::push(function($job) use ($user) {
$recent = new Recent;
$tasks = $recent->getCompletedTasks($user);
// append data from $tasks to DOM
// here's where I want to receive my notification
});
}
Here I am just getting tasks from an API that match data from user.
I know I can store the response data to a database and use AJAX long polling to constantly check for the data but it seems like too much work for most situations I will need to do this. I don't know much about websockets. What types of things have you guys done in these situations? And if you have any examples that would be very helpful. Thanks.
UPDATE: Solved the issue using Pusher. See my answer.
I was able to solve my problem with the help of Pusher. Here's what I did:
Setup my Iron MQ push queue as normal. In routes.php:
Route::post('queue/push', function() {
return Queue::marshal();
});
Installed pusher laravel package.
In my controller then I Queue::push my data. Inside the closure I trigger a new Pusher channel. This will obviously only trigger when the data has been returned from IronMQ.
public function getCompletedTasks() {
$user = User::find(Auth::user()->id);
Queue::push(function($job) use ($user) {
$recent = new Recent;
$tasks = $recent->getCompletedTasks($user);
$pusher = new Pusher('xxx', 'xxx', 'xxx');
$pusher->trigger('reports', 'get_completed_tasks', array('tasks' => $tasks));
$job->delete();
});
});
Next in my view I call my AJAX function with no callback since I won't be doing anything else just yet:
$.get('account/tasks/completed');
Next in my view I initialize Pusher, subscribe to the event and bind get_completed_tasks to the Pusher channel. Now we just wait for a response from Pusher which will then allow me to perform the latter part of my original AJAX request:
{{ HTML::script('//js.pusher.com/2.1/pusher.min.js') }}
<script>
var pusher = new Pusher('xxx');
var channel = pusher.subscribe('reports');
channel.bind('get_completed_tasks', function(data) {
// do something with the data returned
});
</script>
Once I used Pusher in my app, the rest was a breeze. Hope this helps someone!

Resources