Add loyalty points earning support, add points to successful subscription and FCM notification #799
This commit is contained in:
parent
857c573ae5
commit
b8666ff5e0
8 changed files with 216 additions and 31 deletions
|
|
@ -24,6 +24,7 @@ parameters:
|
|||
subscription_paymongo_secret_key: "%env(SUBSCRIPTION_PAYMONGO_SECRET_KEY)%"
|
||||
subscription_paymongo_webhook_id: "%env(SUBSCRIPTION_PAYMONGO_WEBHOOK_ID)%"
|
||||
subscription_months: "%env(SUBSCRIPTION_MONTHS)%"
|
||||
loyalty_php_point_multiplier: "%env(LOYALTY_PHP_POINT_MULTIPLIER)%"
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ 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 App\Service\FCMSender;
|
||||
use App\Service\LoyaltyConnector;
|
||||
use DateTime;
|
||||
|
||||
class SubscriptionController extends ApiController
|
||||
|
|
@ -43,8 +46,6 @@ class SubscriptionController extends ApiController
|
|||
$plan = $pm->getPlanByBatterySize($batts[0]->getSize());
|
||||
}
|
||||
|
||||
error_log("FOUND PLAN FOR $vid: " . print_r($plan, true));
|
||||
|
||||
// response
|
||||
return new ApiResponse(true, '', [
|
||||
'plan' => $plan,
|
||||
|
|
@ -76,6 +77,7 @@ class SubscriptionController extends ApiController
|
|||
'plan_id',
|
||||
'cv_id',
|
||||
'email',
|
||||
'remember_email',
|
||||
]);
|
||||
|
||||
if (!$validity['is_valid']) {
|
||||
|
|
@ -119,6 +121,13 @@ class SubscriptionController extends ApiController
|
|||
// 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)) {
|
||||
|
|
@ -127,11 +136,12 @@ class SubscriptionController extends ApiController
|
|||
|
||||
// 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'], $req->request->get('plan_id'));
|
||||
$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)) {
|
||||
if (empty($sub_pi) || empty($sub_invoice)) {
|
||||
return new ApiResponse(false, 'Error creating subscription. Please try again later.');
|
||||
}
|
||||
|
||||
|
|
@ -160,8 +170,29 @@ class SubscriptionController extends ApiController
|
|||
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();
|
||||
|
||||
|
|
@ -174,7 +205,7 @@ class SubscriptionController extends ApiController
|
|||
]);
|
||||
}
|
||||
|
||||
public function finalizeSubscription(Request $req, $id, PayMongoConnector $pm)
|
||||
public function finalizeSubscription(Request $req, $id, PayMongoConnector $pm, LoyaltyConnector $lc, FCMSender $fcmclient)
|
||||
{
|
||||
// check requirements
|
||||
$validity = $this->validateRequest($req);
|
||||
|
|
@ -186,11 +217,14 @@ class SubscriptionController extends ApiController
|
|||
// 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' => $this->session->getCustomer(),
|
||||
'customer' => $cust,
|
||||
]);
|
||||
|
||||
if (empty($sub_obj)) {
|
||||
|
|
@ -222,7 +256,9 @@ class SubscriptionController extends ApiController
|
|||
$sub_obj->getStatus() === SubscriptionStatus::PENDING &&
|
||||
$pi['response']['data']['attributes']['status'] === 'succeeded'
|
||||
) {
|
||||
$sub_start_date = new DateTime();
|
||||
$now = new DateTime();
|
||||
|
||||
$sub_start_date = $now;
|
||||
$sub_end_date = clone $sub_start_date;
|
||||
$sub_end_date->modify('+' . $this->getParameter('subscription_months') . ' month');
|
||||
|
||||
|
|
@ -230,13 +266,41 @@ class SubscriptionController extends ApiController
|
|||
->setDateStart($sub_start_date)
|
||||
->setDateEnd($sub_end_date);
|
||||
|
||||
// update the gateway transaction record as well
|
||||
$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($now);
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
// compute loyalty points to be added
|
||||
// TODO: get a proper matrix for this. right now we are using a flat multiplier for demo purposes
|
||||
$points_amount = ($gt->getAmount() / 100) * $this->getParameter('loyalty_php_point_multiplier');
|
||||
|
||||
error_log("PI STATUS: " . $pi['response']['data']['attributes']['status']);
|
||||
// add loyalty points
|
||||
$points_res = $lc->updatePoints($cust, $points_amount);
|
||||
if ($points_res['success']) {
|
||||
// notify the customer that points were added
|
||||
$fcmclient->sendLoyaltyEvent(
|
||||
$cust,
|
||||
"loyalty_fcm_title_add_points",
|
||||
"loyalty_fcm_body_add_points",
|
||||
[],
|
||||
[],
|
||||
['points' => $points_amount],
|
||||
);
|
||||
}
|
||||
|
||||
// response
|
||||
return new ApiResponse(true, '', [
|
||||
// response
|
||||
return new ApiResponse(true, '', [
|
||||
'payment_intent' => $pi['response']['data'],
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ namespace App\Controller;
|
|||
|
||||
use App\Entity\GatewayTransaction;
|
||||
use App\Ramcar\TransactionStatus;
|
||||
use App\Service\FCMSender;
|
||||
use App\Service\LoyaltyConnector;
|
||||
use App\Service\PayMongoConnector;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
|
|
@ -17,11 +19,15 @@ class PayMongoController extends Controller
|
|||
{
|
||||
protected $pm;
|
||||
protected $em;
|
||||
protected $lc;
|
||||
protected $fcmclient;
|
||||
|
||||
public function __construct(PayMongoConnector $pm, EntityManagerInterface $em)
|
||||
public function __construct(PayMongoConnector $pm, EntityManagerInterface $em, LoyaltyConnector $lc, FCMSender $fcmclient)
|
||||
{
|
||||
$this->pm = $pm;
|
||||
$this->em = $em;
|
||||
$this->lc = $lc;
|
||||
$this->fcmclient = $fcmclient;
|
||||
}
|
||||
|
||||
public function listen(Request $req)
|
||||
|
|
@ -63,14 +69,75 @@ class PayMongoController extends Controller
|
|||
|
||||
protected function handlePaymentPaid($event)
|
||||
{
|
||||
$metadata = $event['attributes']['metadata'];
|
||||
$obj = $this->getTransaction($metadata['transaction_id']);
|
||||
// TODO: work with paymongo to figure out a better way to standardize callbacks. For now we rely on the callback description
|
||||
$description = $event['attributes']['description'];
|
||||
|
||||
// define if we earn loyalty points or not
|
||||
$add_loyalty_points = false;
|
||||
|
||||
// set initial criteria
|
||||
$criteria = [
|
||||
'status' => TransactionStatus::PENDING,
|
||||
];
|
||||
|
||||
// figure out transaction type by ID
|
||||
switch (true) {
|
||||
// subscription payment
|
||||
case strpos($description, 'Payment for subs') !== false:
|
||||
// retrieve sub and invoice ID from description
|
||||
$desc_parts = explode(" - ", $description);
|
||||
|
||||
// add to criteria
|
||||
$criteria['ext_transaction_id'] = $desc_parts[1];
|
||||
|
||||
// we earn loyalty points
|
||||
$add_loyalty_points = true;
|
||||
|
||||
break;
|
||||
|
||||
// insurance premium
|
||||
// TODO: retest this later so we don't use a default clause
|
||||
default:
|
||||
$metadata = $event['attributes']['metadata'];
|
||||
|
||||
// add to criteria
|
||||
$criteria['id'] = $metadata['transaction_id'];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// get transaction
|
||||
$obj = $this->em->getRepository(GatewayTransaction::class)->findOneBy($criteria);
|
||||
|
||||
if (!empty($obj)) {
|
||||
// mark as paid
|
||||
$obj->setStatus(TransactionStatus::PAID);
|
||||
$obj->setDatePay(new DateTime());
|
||||
$this->em->flush();
|
||||
|
||||
// add loyalty points if applicable
|
||||
if ($add_loyalty_points) {
|
||||
// get the customer
|
||||
$cust = $obj->getCustomer();
|
||||
|
||||
// compute loyalty points to be added
|
||||
// TODO: get a proper matrix for this. right now we are using a flat multiplier for demo purposes
|
||||
$points_amount = ($obj->getAmount() / 100) * $this->getParameter('loyalty_php_point_multiplier');
|
||||
|
||||
// add loyalty points
|
||||
$points_res = $this->lc->updatePoints($cust, $points_amount);
|
||||
if ($points_res['success']) {
|
||||
// notify the customer that points were added
|
||||
$this->fcmclient->sendLoyaltyEvent(
|
||||
$cust,
|
||||
"loyalty_fcm_title_add_points",
|
||||
"loyalty_fcm_body_add_points",
|
||||
[],
|
||||
[],
|
||||
['points' => $points_amount],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json([
|
||||
|
|
@ -86,14 +153,6 @@ class PayMongoController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
protected function getTransaction($id)
|
||||
{
|
||||
//$class_name = 'App\\Entity\\' . $type;
|
||||
//$instance = new $class_name;
|
||||
|
||||
return $this->em->getRepository(GatewayTransaction::class)->find($id);
|
||||
}
|
||||
|
||||
public function paymentSuccess(Request $req)
|
||||
{
|
||||
return $this->render('paymongo/success.html.twig');
|
||||
|
|
|
|||
19
src/Ramcar/FirebaseNotificationType.php
Normal file
19
src/Ramcar/FirebaseNotificationType.php
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Ramcar;
|
||||
|
||||
class FirebaseNotificationType extends NameValue
|
||||
{
|
||||
const JOB_ORDER = 'job_order';
|
||||
const LOYALTY = 'loyalty';
|
||||
const SUBSCRIPTION = 'subscription';
|
||||
const INSURANCE = 'insurance';
|
||||
|
||||
|
||||
const COLLECTION = [
|
||||
'job_order' => 'Job Order',
|
||||
'loyalty' => 'Loyalty',
|
||||
'subscription' => 'Subscription',
|
||||
'insurance' => 'Insurance',
|
||||
];
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Service;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Ramcar\FirebaseNotificationType;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Fcm\FcmClient;
|
||||
use Fcm\Push\Notification;
|
||||
|
|
@ -52,7 +53,7 @@ class FCMSender
|
|||
return $this->client->send($notification);
|
||||
}
|
||||
|
||||
public function sendJoEvent(JobOrder $job_order, $title, $body, $data = [])
|
||||
public function sendJoEvent(JobOrder $job_order, $title, $body, $data = [], $title_params = [], $body_params = [])
|
||||
{
|
||||
// get customer object
|
||||
$cust = $job_order->getCustomer();
|
||||
|
|
@ -60,12 +61,22 @@ class FCMSender
|
|||
// attach jo info
|
||||
$data['jo_id'] = $job_order->getID();
|
||||
$data['jo_status'] = $job_order->getStatus();
|
||||
$data['notification_type'] = FirebaseNotificationType::JOB_ORDER;
|
||||
|
||||
// send the event
|
||||
return $this->sendEvent($cust, $title, $body, $data);
|
||||
return $this->sendEvent($cust, $title, $body, $data, $title_params, $body_params);
|
||||
}
|
||||
|
||||
public function sendEvent(Customer $cust, $title, $body, $data = [])
|
||||
public function sendLoyaltyEvent(Customer $cust, $title, $body, $data = [], $title_params = [], $body_params = [])
|
||||
{
|
||||
// attach type info
|
||||
$data['notification_type'] = FirebaseNotificationType::LOYALTY;
|
||||
|
||||
// send the event
|
||||
return $this->sendEvent($cust, $title, $body, $data, $title_params, $body_params);
|
||||
}
|
||||
|
||||
public function sendEvent(Customer $cust, $title, $body, $data = [], $title_params = [], $body_params = [])
|
||||
{
|
||||
// get all v2 devices
|
||||
$devices = $this->getDevices($cust);
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ class LoyaltyConnector
|
|||
]);
|
||||
}
|
||||
|
||||
public function updatePoints(Customer $cust, int $amount)
|
||||
{
|
||||
return $this->doRequest('/api/wallet/update', 'POST', [
|
||||
'external_id' => $cust->getPhoneMobile(),
|
||||
'currency' => 'points',
|
||||
'amount' => $amount,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function generateSignature(string $path, string $method, string $date_string)
|
||||
{
|
||||
$elements = [
|
||||
|
|
|
|||
|
|
@ -125,6 +125,11 @@ class PayMongoConnector
|
|||
return $found_plan;
|
||||
}
|
||||
|
||||
public function getPlan($plan_id)
|
||||
{
|
||||
return $this->doRequest('/v1/subscriptions/plans/'. $plan_id, 'GET');
|
||||
}
|
||||
|
||||
public function createPlan($plan_data)
|
||||
{
|
||||
$body = [
|
||||
|
|
@ -288,7 +293,19 @@ class PayMongoConnector
|
|||
],
|
||||
];
|
||||
|
||||
return $this->doRequest('/v1/subscriptions', 'POST', $body);
|
||||
$url = '/v1/subscriptions';
|
||||
$method = 'POST';
|
||||
$result = $this->doRequest($url, $method, $body);
|
||||
|
||||
// log if we don't get the expected response
|
||||
if (
|
||||
empty($result['response']['data']['attributes']['latest_invoice']['payment_intent']) ||
|
||||
empty($result['response']['data']['attributes']['latest_invoice'])
|
||||
) {
|
||||
$this->log($method . " " . $url, $body, $result, "error");
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getSubscription($sub_id)
|
||||
|
|
@ -358,7 +375,7 @@ class PayMongoConnector
|
|||
error_log(Psr7\Message::toString($e->getRequest()));
|
||||
|
||||
// log this error
|
||||
$this->log($url, Psr7\Message::toString($e->getRequest()), Psr7\Message::toString($e->getResponse()), 'error');
|
||||
$this->log($method . " " . $url, Psr7\Message::toString($e->getRequest()), Psr7\Message::toString($e->getResponse()), 'error');
|
||||
|
||||
if ($e->hasResponse()) {
|
||||
$error['response'] = Psr7\Message::toString($e->getResponse());
|
||||
|
|
@ -373,7 +390,7 @@ class PayMongoConnector
|
|||
$result_body = $response->getBody();
|
||||
|
||||
// log response
|
||||
$this->log($url, json_encode($request_body), $result_body);
|
||||
$this->log($method . " " . $url, json_encode($request_body), $result_body);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
|
|
@ -382,19 +399,20 @@ class PayMongoConnector
|
|||
}
|
||||
|
||||
// TODO: make this more elegant
|
||||
public function log($title, $request_body = "[]", $result_body = "[]", $type = 'api')
|
||||
public function log($title, $request_body = "[]", $result_body = "[]", $type = 'api', $custom_message = null)
|
||||
{
|
||||
$filename = '/../../var/log/paymongo_' . $type . '.log';
|
||||
$date = date("Y-m-d H:i:s");
|
||||
|
||||
// build log entry
|
||||
$entry = implode("\r\n", [
|
||||
$entry = implode("\r\n", array_filter([
|
||||
$date,
|
||||
$title,
|
||||
(!empty($custom_message) ? "MESSAGE: " . $custom_message : ""),
|
||||
"REQUEST:\r\n" . $request_body,
|
||||
"RESPONSE:\r\n" . $result_body,
|
||||
"\r\n----------------------------------------\r\n\r\n",
|
||||
]);
|
||||
]));
|
||||
|
||||
@file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,3 +180,7 @@ insurance_fcm_title_updated: 'Application updated'
|
|||
insurance_fcm_title_completed: 'Application completed'
|
||||
insurance_fcm_body_updated: 'Some details on your insurance application have been updated.'
|
||||
insurance_fcm_body_completed: 'Your insurance application has been processed!'
|
||||
|
||||
# fcm loyalty
|
||||
loyalty_fcm_title_add_points: 'Hooray!'
|
||||
loyalty_fcm_body_add_points: '{1} You have earned %points% point!|]1,Inf] You have earned %points% points!'
|
||||
Loading…
Reference in a new issue