388 lines
14 KiB
PHP
388 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Controller\CustomerAppAPI;
|
|
|
|
use Symfony\Component\HttpFoundation\Request;
|
|
use Catalyst\ApiBundle\Component\Response as ApiResponse;
|
|
use App\Service\PayMongoConnector;
|
|
|
|
use App\Entity\Customer;
|
|
use App\Entity\Vehicle;
|
|
use App\Entity\Subscription;
|
|
use App\Entity\CustomerVehicle;
|
|
use App\Ramcar\SubscriptionStatus;
|
|
use App\Entity\GatewayTransaction;
|
|
use App\Ramcar\TransactionStatus;
|
|
use DateTime;
|
|
|
|
class SubscriptionController extends ApiController
|
|
{
|
|
public function getPlanDetails(Request $req, $vid, PayMongoConnector $pm)
|
|
{
|
|
// check requirements
|
|
$validity = $this->validateRequest($req);
|
|
|
|
if (!$validity['is_valid']) {
|
|
return new ApiResponse(false, $validity['error']);
|
|
}
|
|
|
|
// get vehicle
|
|
$vehicle = $this->em->getRepository(Vehicle::class)->find($vid);
|
|
if ($vehicle == null) {
|
|
return new ApiResponse(false, 'Invalid vehicle.');
|
|
}
|
|
|
|
$plan = null;
|
|
|
|
// get compatible batteries
|
|
$batts = $vehicle->getActiveBatteries();
|
|
|
|
if (!empty($batts)) {
|
|
// initialize paymongo connector
|
|
$this->initializeSubscriptionPayMongoConnector($pm);
|
|
|
|
$plan = $pm->getPlanByBatterySize($batts[0]->getSize());
|
|
}
|
|
|
|
// response
|
|
return new ApiResponse(true, '', [
|
|
'plan' => $plan,
|
|
]);
|
|
}
|
|
|
|
// NOTE: disabling this since we can just include the public key with the subscription creation endpoint
|
|
/*
|
|
public function getPayMongoPublicKey(Request $req)
|
|
{
|
|
// check requirements
|
|
$validity = $this->validateRequest($req);
|
|
|
|
if (!$validity['is_valid']) {
|
|
return new ApiResponse(false, $validity['error']);
|
|
}
|
|
|
|
// response
|
|
return new ApiResponse(true, '', [
|
|
'key' => $this->getParameter('subscription_paymongo_public_key'),
|
|
]);
|
|
}
|
|
*/
|
|
|
|
public function createSubscription(Request $req, PayMongoConnector $pm)
|
|
{
|
|
// check requirements
|
|
$validity = $this->validateRequest($req, [
|
|
'plan_id',
|
|
'cv_id',
|
|
'email',
|
|
'remember_email',
|
|
]);
|
|
|
|
if (!$validity['is_valid']) {
|
|
return new ApiResponse(false, $validity['error']);
|
|
}
|
|
|
|
// get customer
|
|
$cust = $this->session->getCustomer();
|
|
if ($cust == null) {
|
|
return new ApiResponse(false, 'No customer information found.');
|
|
}
|
|
|
|
// verify email does not belong to someone else
|
|
$email = $req->request->get('email');
|
|
$qb = $this->em->getRepository(Customer::class)
|
|
->createQueryBuilder('c')
|
|
->select('c')
|
|
->where('c.email = :email')
|
|
->andWhere('c.id != :cust_id')
|
|
->setParameter('email', $email)
|
|
->setParameter('cust_id', $cust->getID());
|
|
|
|
$email_exists = $qb->getQuery()->getOneOrNullResult();
|
|
if (!empty($email_exists)) {
|
|
return new ApiResponse(false, 'Email is already in use. Please use a different email address.');
|
|
}
|
|
|
|
// get customer vehicle
|
|
$cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id'));
|
|
|
|
// check if it exists
|
|
if ($cv == null) {
|
|
return new ApiResponse(false, 'Vehicle does not exist.');
|
|
}
|
|
|
|
// check if it's owned by customer
|
|
if ($cv->getCustomer()->getID() != $cust->getID()) {
|
|
return new ApiResponse(false, 'Invalid vehicle.');
|
|
}
|
|
|
|
// initialize paymongo connector
|
|
$this->initializeSubscriptionPayMongoConnector($pm);
|
|
|
|
// get the paymongo plan by ID
|
|
$plan_id = $req->request->get('plan_id');
|
|
$plan = $pm->getPlan($plan_id);
|
|
if (empty($plan['response']['data']['id'])) {
|
|
return new ApiResponse(false, 'No subscription plans found for this vehicle.');
|
|
}
|
|
|
|
// get paymongo customer
|
|
$pm_cust = $pm->findOrCreateCustomer($email, $cust);
|
|
if (empty($pm_cust)) {
|
|
return new ApiResponse(false, 'Error retrieving customer record. Please try again later.');
|
|
}
|
|
|
|
// create subscription
|
|
// NOTE: for now we save ourselves the extra API call and assume the plan_id is valid since this won't change often anyway
|
|
$pm_sub = $pm->createSubscription($pm_cust['id'], $plan_id);
|
|
$sub_pi = $pm_sub['response']['data']['attributes']['latest_invoice']['payment_intent'] ?? null;
|
|
$sub_invoice = $pm_sub['response']['data']['attributes']['latest_invoice'] ?? null;
|
|
|
|
// not the response we expected
|
|
if (empty($sub_pi) || empty($sub_invoice)) {
|
|
return new ApiResponse(false, 'Error creating subscription. Please try again later.');
|
|
}
|
|
|
|
// the payment intent must still be in a pending state
|
|
// TODO: log this somewhere
|
|
if ($sub_pi['status'] !== 'awaiting_payment_method') {
|
|
return new ApiResponse(false, 'Error creating subscription invoice. Please try again later.');
|
|
}
|
|
|
|
// fetch payment intent details for client key
|
|
$pi = $pm->getPaymentIntent($sub_pi['id']);
|
|
if (empty($pi['response']['data']['id'])) {
|
|
return new ApiResponse(false, 'Error retrieving payment intent. Please try again later.');
|
|
}
|
|
|
|
// create subscription entity
|
|
$obj = new Subscription();
|
|
$obj->setCustomer($cust)
|
|
->setCustomerVehicle($cv)
|
|
->setEmail($email)
|
|
->setStatus(SubscriptionStatus::PENDING)
|
|
->setExtApiId($pm_sub['response']['data']['id'])
|
|
->setMetadata($pm_sub['response']['data']);
|
|
|
|
// if requested to save email, save it
|
|
if (!empty($req->request->get('remember_email'))) {
|
|
$cust->setEmail($email);
|
|
}
|
|
|
|
// create new gateway transaction
|
|
$gt = new GatewayTransaction();
|
|
$gt->setCustomer($cust);
|
|
$gt->setDateCreate(new DateTime());
|
|
$gt->setAmount($plan['response']['data']['attributes']['amount']);
|
|
$gt->setStatus(TransactionStatus::PENDING);
|
|
$gt->setGateway('paymongo'); // TODO: define values elsewhere
|
|
$gt->setType('subscription'); // TODO: define values elsewhere
|
|
$gt->setExtTransactionId($sub_invoice['id']);
|
|
$gt->setMetadata([
|
|
'invoice_id' => $sub_invoice['id'],
|
|
'subscription_id' => $pm_sub['response']['data']['id'],
|
|
'payment_intent_id' => $sub_pi['id'],
|
|
]);
|
|
|
|
// if we set it to remember email, update customer email with this
|
|
if (!empty($req->request->get('remember_email'))) {
|
|
$cust->setEmail($email);
|
|
}
|
|
|
|
// save stuff to db
|
|
$this->em->persist($gt);
|
|
$this->em->persist($obj);
|
|
$this->em->flush();
|
|
|
|
// response
|
|
return new ApiResponse(true, '', [
|
|
'subscription_id' => $obj->getID(),
|
|
'payment_intent_id' => $pi['response']['data']['id'],
|
|
'payment_intent_client_key' => $pi['response']['data']['attributes']['client_key'],
|
|
'paymongo_public_key' => $this->getParameter('subscription_paymongo_public_key'),
|
|
]);
|
|
}
|
|
|
|
public function finalizeSubscription(Request $req, $id, PayMongoConnector $pm)
|
|
{
|
|
// check requirements
|
|
$validity = $this->validateRequest($req);
|
|
|
|
if (!$validity['is_valid']) {
|
|
return new ApiResponse(false, $validity['error']);
|
|
}
|
|
|
|
// initialize paymongo connector
|
|
$this->initializeSubscriptionPayMongoConnector($pm);
|
|
|
|
// get customer
|
|
$cust = $this->session->getCustomer();
|
|
|
|
// get subscription
|
|
$sub_obj = $this->em->getRepository(Subscription::class)->findOneBy([
|
|
'id' => $id,
|
|
'status' => SubscriptionStatus::PENDING,
|
|
'customer' => $cust,
|
|
]);
|
|
|
|
if (empty($sub_obj)) {
|
|
return new ApiResponse(false, 'Invalid subscription provided.');
|
|
}
|
|
|
|
// get paymongo subscription so we can verify if the latest invoice is paid or not
|
|
$pm_sub = $pm->getSubscription($sub_obj->getExtApiId());
|
|
if (empty($pm_sub['response']['data']['id'])) {
|
|
return new ApiResponse(false, 'Error retrieving subscription. Please try again later.');
|
|
}
|
|
|
|
// make sure the latest invoice has been paid
|
|
// NOTE: ignore this, this is unreliable due to race condition
|
|
/*
|
|
if ($pm_sub['response']['data']['attributes']['latest_invoice']['status'] !== 'paid') {
|
|
return new ApiResponse(false, 'Latest invoice for subscription is not yet paid.');
|
|
}
|
|
*/
|
|
|
|
// get payment intent
|
|
$pi = $pm->getPaymentIntent($pm_sub['response']['data']['attributes']['latest_invoice']['payment_intent']['id']);
|
|
if (empty($pi['response']['data']['id'])) {
|
|
return new ApiResponse(false, 'Error retrieving payment intent. Please try again later.');
|
|
}
|
|
|
|
// if the paymongo sub is active, and the payment was successful, update the gateway transaction record, which will also activate the sub via listener
|
|
if (
|
|
$sub_obj->getStatus() === SubscriptionStatus::PENDING &&
|
|
$pi['response']['data']['attributes']['status'] === 'succeeded'
|
|
) {
|
|
$gt = $this->em->getRepository(GatewayTransaction::class)->findOneBy([
|
|
'status' => TransactionStatus::PENDING,
|
|
'ext_transaction_id' => $pm_sub['response']['data']['attributes']['latest_invoice']['id'],
|
|
]);
|
|
if (empty($gt)) {
|
|
return new ApiResponse(false, 'Error retrieving transaction. Please try again later.');
|
|
}
|
|
|
|
$gt->setStatus(TransactionStatus::PAID)
|
|
->setDatePay(new DateTime());
|
|
|
|
$this->em->flush();
|
|
}
|
|
|
|
// response
|
|
return new ApiResponse(true, '', [
|
|
'payment_intent' => $pi['response']['data'],
|
|
]);
|
|
}
|
|
|
|
public function getUnfulfilledSubs(Request $req)
|
|
{
|
|
// check requirements
|
|
$validity = $this->validateRequest($req);
|
|
|
|
if (!$validity['is_valid']) {
|
|
return new ApiResponse(false, $validity['error']);
|
|
}
|
|
|
|
// get customer
|
|
$cust = $this->session->getCustomer();
|
|
if ($cust == null) {
|
|
return new ApiResponse(false, 'No customer information found.');
|
|
}
|
|
|
|
// NOTE: this functions like an outer join as far as DQL is concerned
|
|
// get all customer vehicles for the current customer that do not have a JO
|
|
$sql = 'SELECT cv.id, cv.name, cv.model_year, cv.plate_number, v.id AS make_id, v.make AS make, vm.name AS manufacturer
|
|
FROM App\Entity\CustomerVehicle cv
|
|
JOIN App\Entity\Vehicle v WITH cv.vehicle = v
|
|
JOIN App\Entity\VehicleManufacturer vm WITH v.manufacturer = vm
|
|
JOIN App\Entity\Subscription s WITH s.customer_vehicle = cv AND s.status = :status
|
|
LEFT JOIN App\Entity\JobOrder jo WITH jo.subscription = s
|
|
WHERE jo.id IS NULL
|
|
AND cv.customer = :customer';
|
|
|
|
$query = $this->em->createQuery($sql)
|
|
->setParameters([
|
|
'status' => SubscriptionStatus::ACTIVE,
|
|
'customer' => $cust,
|
|
]);
|
|
|
|
$vehicles = $query->getResult();
|
|
|
|
// response
|
|
return new ApiResponse(true, '', [
|
|
'vehicles' => $vehicles,
|
|
]);
|
|
}
|
|
|
|
/*
|
|
public function getPaymentIntent(Request $req, $pid, PayMongoConnector $pm)
|
|
{
|
|
// check requirements
|
|
$validity = $this->validateRequest($req);
|
|
|
|
if (!$validity['is_valid']) {
|
|
return new ApiResponse(false, $validity['error']);
|
|
}
|
|
|
|
// initialize paymongo connector
|
|
$this->initializeSubscriptionPayMongoConnector($pm);
|
|
|
|
// get payment intent
|
|
$pi = $pm->getPaymentIntent($pid);
|
|
if (empty($pi['response']['data']['id'])) {
|
|
return new ApiResponse(false, 'Error retrieving payment intent. Please try again later.');
|
|
}
|
|
|
|
// response
|
|
return new ApiResponse(true, '', [
|
|
'payment_intent' => $pi['response']['data'],
|
|
]);
|
|
}
|
|
|
|
public function activateSubscription(Request $req, $id, PayMongoConnector $pm)
|
|
{
|
|
// check requirements
|
|
$validity = $this->validateRequest($req);
|
|
|
|
if (!$validity['is_valid']) {
|
|
return new ApiResponse(false, $validity['error']);
|
|
}
|
|
|
|
// initialize paymongo connector
|
|
$this->initializeSubscriptionPayMongoConnector($pm);
|
|
|
|
// get subscription
|
|
$obj = $this->em->getRepository(Subscription::class)->findOneBy([
|
|
'id' => $id,
|
|
'status' => SubscriptionStatus::PENDING,
|
|
'customer' => $this->session->getCustomer(),
|
|
]);
|
|
|
|
if (empty($obj)) {
|
|
return new ApiResponse(false, 'Invalid subscription provided.');
|
|
}
|
|
|
|
// get paymongo subscription so we can verify if the latest invoice is paid or not
|
|
$pm_sub = $pm->getSubscription($obj->getExtApiId());
|
|
if (empty($pm_sub['response']['data']['id'])) {
|
|
return new ApiResponse(false, 'Error retrieving subscription. Please try again later.');
|
|
}
|
|
|
|
// make sure the latest invoice has been paid
|
|
if ($pm_sub['response']['data']['attributes']['latest_invoice']['status'] !== 'succeeded') {
|
|
return new ApiResponse(false, 'Latest invoice for subscription is not yet paid.');
|
|
}
|
|
|
|
// mark subscription as paid
|
|
$obj->setStatus(SubscriptionStatus::ACTIVE)
|
|
->setDateStart(new DateTime());
|
|
|
|
$this->em->flush();
|
|
|
|
// response
|
|
return new ApiResponse(true, '', [
|
|
'success' => true,
|
|
]);
|
|
}
|
|
*/
|
|
}
|