9

I'm using the following to override the default handleCustomerSubscriptionDeleted method by placing the following in app/Http/Controllers/WebHookController.php:

<?php namespace App\Http\Controllers;

use Laravel\Cashier\WebhookController;
use Symfony\Component\HttpFoundation\Response;

class StripeWebhooks extends WebhookController {

  /**
   * Handle stripe subscription webhook
   *
   * @param  array  $payload
   * @return Response
   */
  public function handleCustomerSubscriptionDeleted($payload)
  {
    $billable = $this->getBillable($payload['data']['object']['customer']);

    if ($billable && $billable->subscribed()) {
      $billable->subscription()->cancel();

    }

    return new Response('Webhook Handled', 200);
  }

}

In order for this to actually override the default, should I be updating my route to my extended controller, or still reference the default? I don't think that leaving it at the defaults would even let Laravel know my controller exists, but wanted to confirm as well.

Current:

Route::post('stripe/webhook', '\Laravel\Cashier\WebhookController@handleWebhook');

The other part of this is within handleCustomerSubscriptionDeleted I'd also like to be able to grab the current user this is referencing and perform other actions; in this case, automatically setting records to unpublished. What would be the best way to go about retrieving the user? It looks like $payload['data']['object']['customer'] could potentially hold and relate to the stripe_id column in the users table, but would like to confirm. Thanks for any help!

Update

I believe (based on the docs), it should be more like this:

<?php namespace App\Http\Controllers;

use App\Models\Dashboard\Listing;
use App\User;
use Symfony\Component\HttpFoundation\Response;
use Laravel\Cashier\WebhookController as BaseController;

class WebhookController extends BaseController
{
  /**
   * Handle stripe subscription webhook
   *
   * @param  array  $payload
   * @return Response
   */
  public function handleCustomerSubscriptionDeleted($payload)
  {
    $billable = $this->getBillable($payload['data']['object']['customer']);

    if ($billable && $billable->subscribed()) {
      $billable->subscription()->cancel();

      // Get current user
      $user = User::find($billable);

      // Set each listing to draft
      foreach ($user->listings as $listing) {
        $current_listing = Listing::find($listing->id);
        if ($current_listing->status == 'published') {
          $current_listing->status = 'draft';
          $current_listing->save();
        }
      }
    }

    return new Response('Webhook Handled', 200);
  }
}

I then updated my route to the following:

Route::post('stripe/webhook', 'WebhookController@handleWebhook');

But it's still not firing. BUT, what I'm also wondering is if handleCustomerSubscriptionDeleted is called "when they cancel" or after their grace period is over and the actual subscription is cancelled. Is there a more reliable way for me to test this than to play the waiting game locally?

Update #2

I have updated my override class to the following:

<?php namespace App\Http\Controllers;

use App\Models\Dashboard\Listing;
use App\User;
use Symfony\Component\HttpFoundation\Response;
use Laravel\Cashier\WebhookController as BaseController;

class WebhookController extends BaseController
{

  /**
   * Handle stripe subscription webhook
   *
   * @param  array  $payload
   * @return Response
   */
  public function handleCustomerSubscriptionDeleted(array $payload)
  {
    $billable = $this->getBillable($payload['data']['object']['customer']);

    if ($billable && $billable->subscribed()) {
      $billable->subscription()->cancel();

      // Get current user
      $user = User::where('stripe_id', $billable)->firstOrFail();

      // Set each listing to draft
      foreach ($user->listings as $listing) {
        $current_listing = Listing::find($listing->id);
        if ($current_listing->status == 'published') {
          $current_listing->status = 'draft';
          $current_listing->save();
        }
      }
    }

    return new Response('Webhook Handled', 200);
  }
}

The part I changed was changing $billable to search for the user by, as that's what the response returns -- not the user ID as I once thought. I did end up trying localtunnel.me as @Shaz mentioned which did allow me to send a request to it, BUT without being able to pass in the customer ID I'd like impact, I'm not sure how I can verify everything is actually working. I may try to dig in to see if I can manually run an event through Cashier, but still seems a bit odd.

Update #3

Tried doing something a little simpler as far as listening to customer.subscription.created (since that fires immediately):

<?php namespace App\Http\Controllers;

use App\Models\Dashboard\Listing;
use App\User;
use Symfony\Component\HttpFoundation\Response;
use Laravel\Cashier\WebhookController as BaseController;

class WebhookController extends BaseController
{

  /**
   * @param array $payload
   */
  public function handleCustomerSubscriptionCreated(array $payload) {
    $billable = $this->getBillable($payload['data']['object']['customer']);

    if ($billable) {
      // Get current user
      $user = User::where('stripe_id', $billable)->firstOrFail();

      $user->first_name = 'Helloooooo';
      $user->save();
    }
  }

  /**
   * Handle stripe subscription webhook
   *
   * @param  array  $payload
   * @return Response
   */
  public function handleCustomerSubscriptionDeleted(array $payload)
  {
    $billable = $this->getBillable($payload['data']['object']['customer']);

    if ($billable && $billable->subscribed()) {
      $billable->subscription()->cancel();

      // Get current user
      $user = User::where('stripe_id', $billable)->firstOrFail();

      // Set each listing to draft
      foreach ($user->listings as $listing) {
        $current_listing = Listing::find($listing->id);
        if ($current_listing->status == 'published') {
          $current_listing->status = 'draft';
          $current_listing->save();
        }
      }
    }

    return new Response('Webhook Handled', 200);
  }
}

I used localtunnel.me to setup the webhook, but it doesn't appear that it is correctly responding to the webhook, as I see 500 errors in the Stripe response logs even though my "Test Webhooks" event fired from the Stripe dashboard (with no customer ID set obviously) is fine. The response I'm getting about the 500 error is unfortunately lost/cut-off in the jumbled mess of source code that Laravel is spitting out:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta name="robots" content="noindex,nofollow" />
        <style>
            /* Copyright (c) 2010, Yahoo! Inc. All rights reserved. Code licensed under the BSD License: http://developer.yahoo.com/yui/license.html */
            html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:text-top;}sub{vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}

            html { backgr...
4

3 回答 3

6

阅读此问题的答案- 事件在订阅实际结束的日期/时间触发,而不是在客户发起取消时触发。

基于Laravel Cashier Docs - 您的路线实际上应该是:

Route::post('stripe/webhook', 'Laravel\Cashier\WebhookController@handleWebhook');

确保您在条带中的 webhook 设置实际上指向yourapp.com/stripe/webhook

基于代码handleWebhook中的方法Laravel\Cashier——的,任何适当命名的方法(以句柄开头,驼峰化到带事件)都将覆盖现有的方法。


如果你想在路由中使用你的扩展控制器;记得在类的开头添加一个构造函数:

public function __construct(){
  parent::__construct();
}

所以handleWebhook当你这样做的时候方法是可用的

Route::post('stripe/webhook', 'WebhookController@handleWebhook');

Laravel\Cashier\WebhookController而不是在路由中调用。

于 2015-06-26T03:34:47.950 回答
4

我有这个工作,我将路线指向我的控制器。

Route::post(
    'stripe/webhook',
    'WebhookController@handleWebhook'
);

由于我们正在扩展基本控制器,因此基本控制器中的任何方法都已经可用。该控制器的重点是可以访问所有基本控制器方法。通过这种方式,您可以扩展任何新方法或覆盖现有方法。

我在获取代码以捕获 webhook 事件时也遇到了问题。需要注意的是,如果您处于条带测试环境并触发 webhook 事件,请确保添加 env 变量。

CASHIER_ENV=testing

在基本 webhook 控制器中,它首先检查以确保事件存在,如果 webhook 是在测试环境中生成的,则事件不会存在。

public function handleWebhook(Request $request)
{
    $payload = json_decode($request->getContent(), true);

    if (! $this->isInTestingEnvironment() && ! $this->eventExistsOnStripe($payload['id'])) {
        return;
    }

    $method = 'handle'.studly_case(str_replace('.', '_', $payload['type']));

    if (method_exists($this, $method)) {
        return $this->{$method}($payload);
    } else {
        return $this->missingMethod();
    }
}
于 2016-08-08T19:02:50.317 回答
1
$billable = $this->getBillable($payload['data']['object']['customer']);
$user = $billable; // billable is User object
于 2016-01-25T13:42:13.343 回答