<?php
namespace App\Controller;
use App\Constant\ShopifyApi;
use App\Constant\StripeSubscription;
use App\Constant\UserStatus;
use App\Entity\Account;
use App\Entity\Membership;
use App\Entity\Payment;
use App\Entity\Project;
use App\Entity\Role;
use App\Entity\Service;
use App\Entity\Subscription;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/webhooks")
*/
class WebhooksController extends AbstractController
{
private $doctrine;
public function __construct(ManagerRegistry $doctrine) {
$this->doctrine = $doctrine;
}
/**
* @Route("/stripe", name="webhook_stripe")
*/
public function webhookStripe(Request $request, StripeSubscription $stripeSubscription)
{
\Stripe\Stripe::setApiKey($this->getParameter('stripe_sk'));
$payload = @file_get_contents('php://input');
$event = null;
try {
$event = \Stripe\Event::constructFrom(
json_decode($payload, true)
);
} catch(\UnexpectedValueException $e) {
// Invalid payload
return new Response('', 400);
}
// Handle the event
switch ($event->type) {
case 'invoice.payment_succeeded':
$invoice = $event->data->object;
$this->handlePaymentIntentSucceeded($invoice->subscription);
break;
case 'invoice.payment_failed':
$invoice = $event->data->object;
if ($invoice->attempt_count == $stripeSubscription->getLastStripeInvoiceAttempt()){
// TODO - send email to user, subscription expired...
}
break;
case 'customer.subscription.deleted':
$subscription = $event->data->object;
$subscriptionId = $subscription->id;
$subscription = $this->doctrine->getRepository(Subscription::class)->findOneBy(['stripeSubscriptionId' => $subscriptionId]);
$subscription->setStripeCanceled(true);
$subscription->setCanceled(true);
$em = $this->doctrine->getManager();
$em->persist($subscription);
$em->flush();
break;
default:
echo 'Received unknown event type ' . $event->type;
}
return new Response('', 200);
}
private function handlePaymentIntentSucceeded($subscriptionId) :void {
$subscription = $this->doctrine->getRepository(Subscription::class)->findOneBy(['stripeSubscriptionId' => $subscriptionId]);
// init stripe
$stripe = new \Stripe\StripeClient($this->getParameter('stripe_sk'));
$stripeSubscription = $stripe->subscriptions->retrieve(
$subscriptionId,
[]
);
$periodEnd = $stripeSubscription->current_period_end;
$dateExpiration = date('Y-m-d', $periodEnd);
$dateTo = \DateTime::createFromFormat('Y-m-d', $dateExpiration);
$subscription->setExpiryDate($dateTo);
$em = $this->doctrine->getManager();
$em->persist($subscription);
$em->flush();
$this->registerPayment($subscription);
return;
}
/**
* @Route("/SHP", name="webhook_shopify")
*/
public function webhookShopify(Request $request)
{
$valid = true;
$message = null;
$data = json_decode(file_get_contents('php://input'), true);
$project = $this->doctrine->getRepository(Project::class)->findOneBy(['shopIdentity' => $data['shopID']]);
$subscriptionId = $data['subscriptionId'];
if (!$project){
$valid = false;
$message = 'Subscription not found';
} else {
$em = $this->doctrine->getManager();
$subscription = $project->getAccount()->getSubscription();
$pluginId = $this->getParameter('shopify_plugin_id');
$shopUrl = $project->getShopIdentity();
$shopData = $project->getShopData();
$token = $shopData["accessToken"];
$shopifyUrl = 'https://'.$shopUrl.'/admin/api/'.ShopifyApi::API_VERSION.'/recurring_application_charges.json';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $shopifyUrl);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD, "$pluginId:$token");
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
$resultShopify = curl_exec($ch);
curl_close($ch);
$responseShopify = json_decode($resultShopify, true);
if (isset($responseShopify['recurring_application_charges'][0])){
$charge = $responseShopify['recurring_application_charges'][0];
$expiryDate = new \DateTime($subscription->getExpiryDate()->format("Y-m-d"));
$trialEnds = new \DateTime($subscription->getCreatedAt()->modify('+14 days')->format("Y-m-d"));
if ($expiryDate != $trialEnds){
if ($charge['status'] == 'active' && str_contains($charge['return_url'], 'subscription')) {
$newExpiryDate = new \DateTime($charge['billing_on']);
$subscription->setExpiryDate($newExpiryDate);
$subscription->setStripeSubscriptionId($subscriptionId);
$em->persist($subscription);
$em->flush();
$em->persist($subscription);
}
$this->registerPayment($subscription);
}
$message = 'Subscription valid to '.$expiryDate->format('Y-m-d');
} else {
$valid = false;
$message = 'Cannot retrieve data from Shopify';
}
}
return new Response(json_encode([
'valid' => $valid,
'message' => $message
]));
}
/**
* @Route("/WC", name="webhook_woocommerce")
*/
public function webhookWoocommerce(Request $request, UserStatus $userStatus)
{
$em = $this->doctrine->getManager();
$data = json_decode(file_get_contents('php://input'), true);
$subscription = $this->doctrine->getRepository(Subscription::class)->findOneBy(['stripeSubscriptionId' => $data['id']]);
if ($data['status'] == 'active' && $subscription){
$shouldRegisterPayment = true;
$dateExpiration = date('Y-m-d', strtotime($data['next_payment_date']));
$dateTo = \DateTime::createFromFormat('Y-m-d', $dateExpiration);
$subscription->setExpiryDate($dateTo);
$account = $subscription->getAccount();
$account->setIsActive(true);
$em->persist($account);
$role = $this->doctrine->getRepository(Role::class)->find(1);
$membership = $this->doctrine->getRepository(Membership::class)->findOneBy(['account' => $account, 'role' => $role]);
$user = $membership->getUser();
$statusPending = $userStatus->getUserStatusId('Pending');
$statusActive = $userStatus->getUserStatusId('Active');
$currentStatus = $user->getStatus();
if ($currentStatus == $statusPending){
$user->setStatus($statusActive);
$shouldRegisterPayment = false;
}
$user->setToken(null);
$user->setTokenCreatedAt(null);
$em->persist($user);
// update service
$billingIntents = $data['billing_intents'];
$ids = array_column($billingIntents, 'id');
array_multisort($ids, SORT_DESC, $billingIntents);
foreach ($billingIntents as $billing){
if ($billing['status'] == 'completed' || $billing['status'] == 'pending'){
$serviceName = $billing['payload']['name'];
$unit = substr($billing['payload']['billing_period'],0, 1);
$service = $this->doctrine->getRepository(Service::class)->findOneBy([
'name' => $serviceName,
'unit' => $unit,
'enablePlugins' => true
]);
$subscription->setService($service);
$subscription->setCanceled(false);
break;
}
}
if ($shouldRegisterPayment){
$account = $subscription->getAccount();
foreach ($billingIntents as $billing){
if ($billing['status'] == 'completed'){
$now = new \DateTime('now');
$nowDate = $now->format('Y-m-d');
$payments = $account->getPayments();
$shouldRegister = true;
foreach ($payments as $payment){
$datePayment = $payment->getCreatedAt()->format('Y-m-d');
if ($nowDate == $datePayment){
$shouldRegister = false;
}
}
if ($shouldRegister){
$this->registerPayment($subscription);
}
}
}
}
$em->persist($subscription);
$em->flush();
}
if ($data['status'] == 'canceled' && $subscription){
// store to db
$em = $this->doctrine->getManager();
$subscription->setCanceled(true);
$em->persist($subscription);
$em->flush();
}
return new Response('', 200);
}
/**
* @param Subscription $subscription
*/
private function registerPayment (Subscription $subscription) :void
{
$payment = new Payment();
$payment->setAccount($subscription->getAccount());
$payment->setAmount($subscription->getService()->getPrice());
$payment->setUnit($subscription->getService()->getUnit());
$em = $this->doctrine->getManager();
$em->persist($payment);
$em->flush();
}
}