Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webhook verification #99

Merged
merged 2 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ JWT_SECRET=Jrsweag3Mf0srOqDizRkhjWm5CEFcrBy
PADDLE_VENDOR_ID=
PADDLE_VENDOR_AUTH_CODE=
PADDLE_ENV=sandbox
PADDLE_PUBLIC_KEY=

WAVE_DOCS=true
WAVE_DEMO=false
Expand Down
3 changes: 2 additions & 1 deletion config/wave.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
'paddle' => [
'vendor' => env('PADDLE_VENDOR_ID', ''),
'auth_code' => env('PADDLE_VENDOR_AUTH_CODE', ''),
'env' => env('PADDLE_ENV', 'sandbox')
'env' => env('PADDLE_ENV', 'sandbox'),
'public_key' => env('PADDLE_PUBLIC_KEY', ''),
]

];
2 changes: 1 addition & 1 deletion wave/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
Route::view('pricing', 'theme::pricing')->name('wave.pricing');

/***** Billing Routes *****/
Route::post('paddle/webhook', '\Wave\Http\Controllers\SubscriptionController@webhook');
Route::post('paddle/webhook', '\Wave\Http\Controllers\WebhookController');
Route::post('checkout', '\Wave\Http\Controllers\SubscriptionController@checkout')->name('checkout');

Route::get('test', '\Wave\Http\Controllers\SubscriptionController@test');
Expand Down
30 changes: 0 additions & 30 deletions wave/src/Http/Controllers/SubscriptionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,36 +31,6 @@ public function __construct(){
$this->paddle_vendors_url = (config('wave.paddle.env') == 'sandbox') ? 'https://sandbox-vendors.paddle.com/api' : 'https://vendors.paddle.com/api';
}


public function webhook(Request $request){

// Which alert/event is this request for?
$alert_name = $request->alert_name;
$subscription_id = $request->subscription_id;
$status = $request->status;


// Respond appropriately to this request.
switch($alert_name) {

case 'subscription_created':
break;
case 'subscription_updated':
break;
case 'subscription_cancelled':
$this->cancelSubscription($subscription_id);
return response()->json(['status' => 1]);
break;
case 'subscription_payment_succeeded':
break;
case 'subscription_payment_failed':
$this->cancelSubscription($subscription_id);
return response()->json(['status' => 1]);
break;
}

}

public function cancel(Request $request){
$this->cancelSubscription($request->id);
return response()->json(['status' => 1]);
Expand Down
51 changes: 51 additions & 0 deletions wave/src/Http/Controllers/WebhookController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace Wave\Http\Controllers;

use Illuminate\Http\Request;
use TCG\Voyager\Models\Role;
use Wave\PaddleSubscription;
use Illuminate\Support\Carbon;
use App\Http\Controllers\Controller;
use Wave\Http\Middleware\VerifyWebhook;

class WebhookController extends Controller
{
public function __construct()
{
if (config('wave.paddle.public_key')) {
$this->middleware(VerifyWebhook::class);
}
}

public function __invoke(Request $request)
{
$method = match ($request->get('alert_name', null)) {
'subscription_cancelled',
'subscription_payment_failed' => 'subscriptionCancelled',
default => null,
};

if (method_exists($this, $method)) {
try {
$this->{$method}($request);
} catch (\Exception $e) {
return response('Webhook failed');
}
}

return response('Webhook handled');
}

protected function subscriptionCancelled(Request $request)
{
$subscription = PaddleSubscription::where('subscription_id', $request->subscription_id)->firstOrFail();
$subscription->cancelled_at = Carbon::now();
$subscription->status = 'cancelled';
$subscription->save();
$user = config('wave.user_model')::find($subscription->user_id);
$cancelledRole = Role::where('name', '=', 'cancelled')->first();
$user->role_id = $cancelledRole->id;
$user->save();
}
}
43 changes: 43 additions & 0 deletions wave/src/Http/Middleware/VerifyWebhook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace Wave\Http\Middleware;

use Closure;
use InvalidArgumentException;

class VerifyWebhook
{
/**
* Handle an incoming webhook request.
*
* @see https://developer.paddle.com/webhook-reference/ZG9jOjI1MzUzOTg2-verifying-webhooks
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$signature = $request->get('p_signature');
$fields = $request->except('p_signature');

ksort($fields);

foreach ($fields as $k => $v) {
if (!in_array(gettype($v), array('object', 'array'))) {
$fields[$k] = "$v";
}
}

if (openssl_verify(
serialize($fields),
base64_decode($signature),
openssl_get_publickey(config('wave.paddle.public_key')),
OPENSSL_ALGO_SHA1
) !== 1) {
throw new InvalidArgumentException('Webhook signature is invalid.');
}

return $next($request);
}
}