Add source to invoice criteria. Modify invoice rules to get service fees from... #1701

Merged
arcticzero merged 217 commits from 746-resq-2-0-final into master 2023-11-22 08:54:48 +00:00
28 changed files with 1675 additions and 114 deletions
Showing only changes of commit 7c09dcac65 - Show all commits

View file

@ -53,6 +53,14 @@ security:
pattern: ^\/test_capi\/ pattern: ^\/test_capi\/
security: false security: false
insurance:
pattern: ^\/insurance\/
security: false
paymongo:
pattern: ^\/paymongo\/
security: false
cust_api_v2: cust_api_v2:
pattern: ^\/apiv2\/(?!register|register\/|number_confirm|number_confirm\/|code_validate|code_validate\/|resend_code|resend_code\/|version_check|version_check\/|account|account\/|account_code_validate|account_code_validate\/|account_resend_code|account_resend_code\/) pattern: ^\/apiv2\/(?!register|register\/|number_confirm|number_confirm\/|code_validate|code_validate\/|resend_code|resend_code\/|version_check|version_check\/|account|account\/|account_code_validate|account_code_validate\/|account_resend_code|account_resend_code\/)
provider: api_v2_provider provider: api_v2_provider

View file

@ -45,6 +45,11 @@ apiv2_cust_vehicle_add:
controller: App\Controller\CustomerAppAPI\VehicleController::addVehicle controller: App\Controller\CustomerAppAPI\VehicleController::addVehicle
methods: [POST] methods: [POST]
apiv2_cust_vehicle_info:
path: /apiv2/vehicles/{id}
controller: App\Controller\CustomerAppAPI\VehicleController::getVehicle
methods: [GET]
apiv2_cust_vehicle_update: apiv2_cust_vehicle_update:
path: /apiv2/vehicles/{id} path: /apiv2/vehicles/{id}
controller: App\Controller\CustomerAppAPI\VehicleController::updateVehicle controller: App\Controller\CustomerAppAPI\VehicleController::updateVehicle
@ -267,3 +272,34 @@ apiv2_cust_vehicle_trade_in_estimate:
path: /apiv2/vehicles/{id}/trade_in_estimate path: /apiv2/vehicles/{id}/trade_in_estimate
controller: App\Controller\CustomerAppAPI\VehicleController::getTradeInEstimate controller: App\Controller\CustomerAppAPI\VehicleController::getTradeInEstimate
methods: [GET] methods: [GET]
# insurance
apiv2_insurance_vehicle_maker_list:
path: /apiv2/insurance/vehicles/makers
controller: App\Controller\CustomerAppAPI\InsuranceController::getVehicleMakers
methods: [GET]
apiv2_insurance_vehicle_model_list:
path: /apiv2/insurance/vehicles/models/{maker_id}
controller: App\Controller\CustomerAppAPI\InsuranceController::getVehicleModels
methods: [GET]
apiv2_insurance_vehicle_trim_list:
path: /apiv2/insurance/vehicles/trims/{model_id}
controller: App\Controller\CustomerAppAPI\InsuranceController::getVehicleTrims
methods: [GET]
apiv2_insurance_vehicle_mv_type_list:
path: /apiv2/insurance/mvtypes
controller: App\Controller\CustomerAppAPI\InsuranceController::getMVTypes
methods: [GET]
apiv2_insurance_vehicle_client_type_list:
path: /apiv2/insurance/clienttypes
controller: App\Controller\CustomerAppAPI\InsuranceController::getClientTypes
methods: [GET]
apiv2_insurance_application_create:
path: /apiv2/insurance/application
controller: App\Controller\CustomerAppAPI\InsuranceController::createApplication
methods: [POST]

View file

@ -1,6 +1,6 @@
# insurance # insurance
insurance_listener: insurance_listener:
path: /api/insurance/listen path: /insurance/listen
controller: App\Controller\InsuranceController::listen controller: App\Controller\InsuranceController::listen
methods: [POST] methods: [POST]

View file

@ -0,0 +1,16 @@
# paymongo
paymongo_listener:
path: /paymongo/listen
controller: App\Controller\PayMongoController::listen
methods: [POST]
paymongo_payment_success:
path: /paymongo/success
controller: App\Controller\PayMongoController::paymentSuccess
methods: [GET]
paymongo_payment_cancelled:
path: /paymongo/cancelled
controller: App\Controller\PayMongoController::paymentCancelled
methods: [GET]

View file

@ -216,6 +216,23 @@ services:
$username: "%env(INSURANCE_USERNAME)%" $username: "%env(INSURANCE_USERNAME)%"
$password: "%env(INSURANCE_PASSWORD)%" $password: "%env(INSURANCE_PASSWORD)%"
# entity listener for gateway transactions
App\EntityListener\GatewayTransactionListener:
arguments:
$em: "@doctrine.orm.entity_manager"
$ic: "@App\\Service\\InsuranceConnector"
tags:
- name: doctrine.orm.entity_listener
event: 'postUpdate'
entity: 'App\Entity\GatewayTransaction'
# paymongo connector
App\Service\PayMongoConnector:
arguments:
$base_url: "%env(PAYMONGO_BASE_URL)%"
$public_key: "%env(PAYMONGO_PUBLIC_KEY)%"
$secret_key: "%env(PAYMONGO_SECRET_KEY)%"
# entity listener for customer vehicle warranty code history # entity listener for customer vehicle warranty code history
App\EntityListener\CustomerVehicleSerialListener: App\EntityListener\CustomerVehicleSerialListener:
arguments: arguments:

View file

@ -0,0 +1,324 @@
<?php
namespace App\Controller\CustomerAppAPI;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Catalyst\ApiBundle\Component\Response as ApiResponse;
use App\Service\InsuranceConnector;
use App\Service\PayMongoConnector;
use App\Entity\InsuranceApplication;
use App\Entity\GatewayTransaction;
use App\Entity\CustomerVehicle;
use App\Ramcar\InsuranceApplicationStatus;
use App\Ramcar\InsuranceMVType;
use App\Ramcar\InsuranceClientType;
use App\Ramcar\TransactionStatus;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use DateTime;
class InsuranceController extends ApiController
{
protected $client;
public function __construct(EntityManagerInterface $em, KernelInterface $kernel, InsuranceConnector $client)
{
parent::__construct($em, $kernel);
$this->client = $client;
}
public function createApplication(Request $req, PayMongoConnector $paymongo, UrlGeneratorInterface $router)
{
// validate params
$validity = $this->validateRequest($req, [
// internal
'customer_vehicle_id',
// client info
'client_type',
'first_name',
//'middle_name', // not required
'surname',
'corporate_name',
// client contact info
'address_number',
//'address_street', // not required
//'address_building', // not required
'address_barangay',
'address_city',
'address_province',
'zipcode',
'mobile_number',
'email_address',
// car info
'make',
'model',
'series',
'color',
//'plate_number', // NOTE: we get this from the internal cv record instead
'mv_file_number',
'motor_number',
'serial_chasis', // NOTE: this is how it's spelled on their API
'year_model',
'mv_type_id',
'body_type',
//'is_public', // not required, boolean, only show field if mv_type_id in [4, 13]
//'orcr_file', // this is a file
// mv_type_id specific fields
//'vehicle_use_type', // not required, only show field if mv_type_id is not in [4, 13]. accepted values are: 'commercial', 'private'
]);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// conditionally require is_public or vehicle_use_type
switch ($req->request->get('mv_type_id')) {
case 4:
case 13:
if (empty($req->request->get('is_public'))) {
return new ApiResponse(false, 'Missing required parameter(s): is_public is required when mv_type_id is in [4, 13]');
}
break;
default:
if (empty($req->request->get('vehicle_use_type'))) {
return new ApiResponse(false, 'Missing required parameter(s): vehicle_use_type is required when mv_type_id is not in [4, 13]');
}
break;
}
// require the orcr file
if ($req->files->get('orcr_file') === null) {
return new ApiResponse(false, 'Missing required file: orcr_file');
}
// get our listener url
$notif_url = $router->generate('insurance_listener', [], UrlGeneratorInterface::ABSOLUTE_URL);
// get customer and cv info
$cust = $this->session->getCustomer();
$cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle_id'));
if ($cv == null) {
return new ApiResponse(false, 'Invalid customer vehicle id.');
}
// confirm that customer vehicle belongs to customer
if ($cv->getCustomer()->getID() != $cust->getID()) {
return new ApiResponse(false, 'Vehicle does not belong to customer.');
}
// process all our inputs first
$input = $req->request->all();
if (!isset($input['is_public'])) {
$input['is_public'] = false;
}
$input['line'] = $this->getLineType($input['mv_type_id'], $input['vehicle_use_type'], $input['is_public']);
// submit insurance application
$result = $this->client->createApplication(
$cv,
$notif_url,
$input,
$req->files->get('orcr_file')
);
if (!$result['success']) {
return new ApiResponse(false, $result['error']['message']);
}
$premium_amount_int = (int)bcmul($result['response']['premium'], 100);
// build checkout item and metadata
$items = [
[
'name' => "Insurance Premium",
'description' => "Premium fee for vehicle insurance",
'quantity' => 1,
'amount' => $premium_amount_int,
'currency' => 'PHP',
],
];
$now = new DateTime();
// create gateway transaction
$gt = new GatewayTransaction();
$gt->setCustomer($cust);
$gt->setDateCreate($now);
$gt->setAmount($premium_amount_int);
$gt->setStatus(TransactionStatus::PENDING);
$gt->setGateway('paymongo'); // TODO: define values elsewhere
$gt->setType('insurance_premium'); // TODO: define values elsewhere
$this->em->persist($gt);
$this->em->flush();
// create paymongo checkout resource
$checkout = $paymongo->createCheckout(
$cust,
$items,
$gt->getID(),
"Motolite RES-Q Vehicle Insurance",
$router->generate('paymongo_payment_success', [], UrlGeneratorInterface::ABSOLUTE_URL),
$router->generate('paymongo_payment_cancelled', [], UrlGeneratorInterface::ABSOLUTE_URL),
['transaction_id' => $gt->getID()], // NOTE: passing this here too for payment resource metadata
);
if (!$checkout['success']) {
return new ApiResponse(false, $checkout['error']['message']);
}
$checkout_url = $checkout['response']['data']['attributes']['checkout_url'];
// add checkout url and id to transaction metadata
$gt->setExtTransactionId($checkout['response']['data']['id']);
$gt->setMetadata([
'checkout_url' => $checkout_url,
]);
// store application in db
$app = new InsuranceApplication();
$app->setDateSubmit($now);
$app->setCustomer($cust);
$app->setCustomerVehicle($cv);
$app->setGatewayTransaction($gt);
$app->setStatus(InsuranceApplicationStatus::CREATED);
$app->setExtTransactionId($result['response']['id']);
$app->setMetadata($input);
$this->em->persist($app);
// save everything
$this->em->flush();
// return
return new ApiResponse(true, '', [
'app_id' => $app->getID(),
'checkout_url' => $checkout_url,
'premium_amount' => (string)$result['response']['premium'],
]);
}
public function getVehicleMakers(Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get maker list
$result = $this->client->getVehicleMakers();
if (!$result['success']) {
return new ApiResponse(false, $result['error']['message']);
}
return new ApiResponse(true, '', [
'makers' => $result['response']['data']['vehicleMakers'],
]);
}
public function getVehicleModels($maker_id, Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get maker list
$result = $this->client->getVehicleModels($maker_id);
if (!$result['success']) {
return new ApiResponse(false, $result['error']['message']);
}
return new ApiResponse(true, '', [
'models' => $result['response']['data']['vehicleModels'],
]);
}
public function getVehicleTrims($model_id, Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get maker list
$result = $this->client->getVehicleTrims($model_id);
if (!$result['success']) {
return new ApiResponse(false, $result['error']['message']);
}
return new ApiResponse(true, '', [
'trims' => $result['response']['data']['vehicleTrims'],
]);
}
public function getMVTypes(Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
return new ApiResponse(true, '', [
'mv_types' => InsuranceMVType::getCollection(),
]);
}
public function getClientTypes(Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
return new ApiResponse(true, '', [
'mv_types' => InsuranceClientType::getCollection(),
]);
}
protected function getLineType($mv_type_id, $vehicle_use_type, $is_public = false)
{
$line = '';
// NOTE: this is a bit of a hack since we're hardcoding values, but this is fine for now
switch ($mv_type_id) {
case '3':
$line = 'mcoc';
break;
case '4':
case '13':
if ($is_public) {
$line = 'lcoc';
} else {
$line = 'mcoc';
}
break;
default:
if ($vehicle_use_type === 'commercial') {
$line = 'ccoc';
} else {
$line = 'pcoc';
}
break;
}
return $line;
}
}

View file

@ -12,6 +12,8 @@ use App\Entity\Vehicle;
use App\Ramcar\JOStatus; use App\Ramcar\JOStatus;
use App\Ramcar\ServiceType; use App\Ramcar\ServiceType;
use App\Ramcar\TradeInType; use App\Ramcar\TradeInType;
use App\Ramcar\InsuranceApplicationStatus;
use App\Service\PayMongoConnector;
use DateTime; use DateTime;
class VehicleController extends ApiController class VehicleController extends ApiController
@ -110,6 +112,34 @@ class VehicleController extends ApiController
} }
public function getVehicle(Request $req, $id, PayMongoConnector $paymongo)
{
// check requirements
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get customer vehicle
$cv = $this->em->getRepository(CustomerVehicle::class)->find($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() != $this->session->getCustomer()->getID()) {
return new ApiResponse(false, 'Invalid vehicle.');
}
// response
return new ApiResponse(true, '', [
'vehicle' => $this->generateVehicleInfo($cv, true, $paymongo),
]);
}
public function updateVehicle(Request $req, $id) public function updateVehicle(Request $req, $id)
{ {
// check requirements // check requirements
@ -177,7 +207,7 @@ class VehicleController extends ApiController
]); ]);
} }
public function listVehicles(Request $req) public function listVehicles(Request $req, PayMongoConnector $paymongo)
{ {
// validate params // validate params
$validity = $this->validateRequest($req); $validity = $this->validateRequest($req);
@ -198,37 +228,7 @@ class VehicleController extends ApiController
// only get the customer's vehicles whose flag_active is true // only get the customer's vehicles whose flag_active is true
$cvs = $this->em->getRepository(CustomerVehicle::class)->findBy(['flag_active' => true, 'customer' => $cust]); $cvs = $this->em->getRepository(CustomerVehicle::class)->findBy(['flag_active' => true, 'customer' => $cust]);
foreach ($cvs as $cv) { foreach ($cvs as $cv) {
$battery_id = null; $cv_list[] = $this->generateVehicleInfo($cv, true, $paymongo);
if ($cv->getCurrentBattery() != null)
$battery_id = $cv->getCurrentBattery()->getID();
$wty_ex = null;
if ($cv->getWarrantyExpiration() != null)
$wty_ex = $cv->getWarrantyExpiration()->format('Y-m-d');
$warranty = $this->findWarranty($cv->getPlateNumber());
$cv_name = '';
if ($cv->getName() != null)
$cv_name = $cv->getName();
$cv_list[] = [
'cv_id' => $cv->getID(),
'mfg_id' => $cv->getVehicle()->getManufacturer()->getID(),
'make_id' => $cv->getVehicle()->getID(),
'name' => $cv_name,
'plate_num' => $cv->getPlateNumber(),
'model_year' => $cv->getModelYear(),
'color' => $cv->getColor(),
'condition' => $cv->getStatusCondition(),
'fuel_type' => $cv->getFuelType(),
'wty_code' => $cv->getWarrantyCode(),
'wty_expire' => $wty_ex,
'curr_batt_id' => $battery_id,
'is_motolite' => $cv->hasMotoliteBattery() ? 1 : 0,
'is_active' => $cv->isActive() ? 1 : 0,
'warranty' => $warranty,
];
} }
// response // response
@ -320,7 +320,7 @@ class VehicleController extends ApiController
// response // response
return new ApiResponse(); return new ApiResponse();
} }
protected function getTIEstimateByCV($cv) protected function getTIEstimateByCV($cv)
{ {
// compute for trade in value // compute for trade in value
@ -382,6 +382,98 @@ class VehicleController extends ApiController
]; ];
} }
protected function generateVehicleInfo(CustomerVehicle $cv, $include_insurance = false, PayMongoConnector $paymongo)
{
$battery_id = null;
if ($cv->getCurrentBattery() != null)
$battery_id = $cv->getCurrentBattery()->getID();
$wty_ex = null;
if ($cv->getWarrantyExpiration() != null)
$wty_ex = $cv->getWarrantyExpiration()->format('Y-m-d');
$warranty = $this->findWarranty($cv->getPlateNumber());
$cv_name = '';
if ($cv->getName() != null)
$cv_name = $cv->getName();
$row = [
'cv_id' => $cv->getID(),
'mfg_id' => $cv->getVehicle()->getManufacturer()->getID(),
'make_id' => $cv->getVehicle()->getID(),
'name' => $cv_name,
'plate_num' => $cv->getPlateNumber(),
'model_year' => $cv->getModelYear(),
'color' => $cv->getColor(),
'condition' => $cv->getStatusCondition(),
'fuel_type' => $cv->getFuelType(),
'wty_code' => $cv->getWarrantyCode(),
'wty_expire' => $wty_ex,
'curr_batt_id' => $battery_id,
'is_motolite' => $cv->hasMotoliteBattery() ? 1 : 0,
'is_active' => $cv->isActive() ? 1 : 0,
'warranty' => $warranty,
];
// get latest insurance row
if ($include_insurance) {
$insurance = null;
$iobj = $cv->getLatestInsuranceApplication();
if (!empty($iobj)) {
$gt = $iobj->getGatewayTransaction();
$date_complete = $iobj->getDateComplete();
$date_expire = $iobj->getDateExpire();
$status = $iobj->getStatus();
error_log("\r\nTHIS IS THE CURRENT STATUS: " . $status . "\r\n");
// handle the very transient state between a payment being made and receiving the paymongo webhook
// TODO: maybe handle this more elegantly. issue is not sure it is a good idea to update the db for this very transient status as the webhook listener also updates this status right away
switch ($status) {
case InsuranceApplicationStatus::CREATED:
// get latest status on this checkout from paymongo
$checkout = $paymongo->getCheckout($gt->getExtTransactionId());
if ($checkout['success']) {
$payment_intent = $checkout['response']['data']['attributes']['payment_intent'] ?? null;
if (!empty($payment_intent)) {
$intent_status = $payment_intent['attributes']['status'] ?? null;
// TODO: define these paymongo payment intent statuses elsewhere
if ($intent_status === 'processing' || $intent_status === 'succeeded') {
$status = InsuranceApplicationStatus::PAID;
}
}
}
break;
default:
break;
}
$insurance = [
'id' => $iobj->getID(),
'ext_transaction_id' => $iobj->getExtTransactionId(),
'status' => $status,
'coc_url' => $iobj->getCOC(),
'checkout_url' => $gt->getMetadata()['checkout_url'],
'transaction_status' => $gt->getStatus(),
'premium_amount' => (string)bcdiv($gt->getAmount(), 100), // NOTE: hard expressing as string so it's consistent
'date_submit' => $iobj->getDateSubmit()->format('Y-m-d H:i:s'),
'date_complete' => $date_complete ? $date_complete->format('Y-m-d H:i:s') : null,
'date_expire' => $date_expire ? $date_expire->format('Y-m-d H:i:s') : null,
];
// get information changelog
$insurance['changelog'] = $iobj->getMetadata()['changes'] ?? [];
}
$row['latest_insurance'] = $insurance;
}
return $row;
}
protected function checkVehicleRequirements(Request $req) protected function checkVehicleRequirements(Request $req)
{ {
// validate params // validate params

View file

@ -2,22 +2,134 @@
namespace App\Controller; namespace App\Controller;
use App\Ramcar\InsuranceApplicationStatus;
use App\Service\FCMSender;
use App\Entity\InsuranceApplication;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use DateTime;
class InsuranceController extends Controller class InsuranceController extends Controller
{ {
public function listen(Request $req, EntityManagerInterface $em) protected $em;
protected $fcmclient;
public function __construct(EntityManagerInterface $em, FCMSender $fcmclient)
{
$this->em = $em;
$this->fcmclient = $fcmclient;
}
public function listen(Request $req)
{ {
$payload = $req->request->all(); $payload = $req->request->all();
// DEBUG
@file_put_contents(__DIR__ . '/../../var/log/insurance.log', print_r($payload, true) . "\r\n----------------------------------------\r\n\r\n", FILE_APPEND);
error_log(print_r($payload, true)); error_log(print_r($payload, true));
/*
return $this->json([
'success' => true,
]);
*/
// END DEBUG
// if no transaction code given, silently fail
if (empty($payload['transaction_code'])) {
error_log("Invalid insurance callback received: " . print_r($payload, true));
return $this->json([
'success' => true,
]);
}
// get event type and process accordingly
$event_name = $payload['transaction_code'];
switch ($event_name) {
case 'GR002':
return $this->handleAuthenticated($payload);
break;
case 'GR003':
return $this->handleUpdateMade($payload);
break;
default:
break;
}
return $this->json([ return $this->json([
'success' => true, 'success' => true,
'payload' => $payload, 'payload' => $payload,
]); ]);
} }
protected function handleAuthenticated($payload)
{
$obj = $this->getApplication($payload['id']);
$now = new DateTime();
$expiry = DateTime::createFromFormat("Y-m-d", $payload['expiry_date']);
if (!empty($obj)) {
// mark as completed
$obj->setStatus(InsuranceApplicationStatus::COMPLETED);
$obj->setDateComplete($now);
$obj->setDateExpire($expiry);
$obj->setCOC($payload['coc_url']);
$this->em->flush();
// send notification
$this->fcmclient->sendEvent($obj->getCustomer(), "insurance_fcm_title_completed", "insurance_fcm_body_completed", [
'cv_id' => $obj->getCustomerVehicle()->getID(),
]);
}
return $this->json([
'success' => true,
]);
}
protected function handleUpdateMade($payload)
{
$obj = $this->getApplication($payload['id']);
if (!empty($obj)) {
$metadata = $obj->getMetadata();
// initialize change list if not present
if (empty($metadata['changes'])) {
$metadata['changes'] = [];
}
$now = new DateTime;
$metadata['changes'][$now->format('Y-m-d H:i:s')] = $payload['data'];
// update metadata to record change
$obj->setMetadata($metadata);
$this->em->flush();
// send notification
$this->fcmclient->sendEvent($obj->getCustomer(), "insurance_fcm_title_updated", "insurance_fcm_body_updated", [
'cv_id' => $obj->getCustomerVehicle()->getID(),
]);
}
return $this->json([
'success' => true,
]);
}
protected function getApplication($transaction_id)
{
$result = $this->em->getRepository(InsuranceApplication::class)->findBy([
'ext_transaction_id' => $transaction_id,
], [], 1);
return !empty($result) ? $result[0] : false;
}
} }

View file

@ -0,0 +1,110 @@
<?php
namespace App\Controller;
use App\Entity\GatewayTransaction;
use App\Ramcar\TransactionStatus;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class PayMongoController extends Controller
{
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function listen(Request $req)
{
$payload = json_decode($req->getContent(), true);
// DEBUG
@file_put_contents(__DIR__ . '/../../var/log/paymongo.log', print_r($payload, true) . "\r\n----------------------------------------\r\n\r\n", FILE_APPEND);
/*
return $this->json([
'success' => true,
]);
*/
// END DEBUG
// if no event type given, silently fail
if (empty($payload['data'])) {
error_log("Invalid paymongo callback received: " . print_r($payload, true));
return $this->json([
'success' => true,
]);
}
// get event type and process accordingly
$attr = $payload['data']['attributes'];
$event = $attr['data'];
$event_name = $attr['type'];
switch ($event_name) {
case "payment.paid":
return $this->handlePaymentPaid($event);
break;
case "payment.failed":
return $this->handlePaymentPaid($event);
break;
case "payment.refunded": // TODO: handle refunds
case "payment.refund.updated":
case "checkout_session.payment.paid":
default:
break;
}
return $this->json([
'success' => true,
]);
}
protected function handlePaymentPaid($event)
{
$metadata = $event['attributes']['metadata'];
$obj = $this->getTransaction($metadata['transaction_id']);
if (!empty($obj)) {
// mark as paid
$obj->setStatus(TransactionStatus::PAID);
$this->em->flush();
}
return $this->json([
'success' => true,
]);
}
protected function handlePaymentFailed(Request $req)
{
// TODO: do something about failed payments?
return $this->json([
'success' => true,
]);
}
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');
}
public function paymentCancelled(Request $req)
{
return $this->render('paymongo/cancelled.html.twig');
}
}

View file

@ -536,7 +536,7 @@ class ReportController extends Controller
'Mobile Number', 'Mobile Number',
'Landline Number', 'Landline Number',
'Office Number', 'Office Number',
'Fax Number', 'Alternative Phone Number',
'Plate Number', 'Plate Number',
'Date Mobile App Downloaded', 'Date Mobile App Downloaded',
'Mobile Number Using Mobile App', 'Mobile Number Using Mobile App',
@ -706,7 +706,7 @@ class ReportController extends Controller
'Customer Mobile Phone', 'Customer Mobile Phone',
'Customer Landline Phone', 'Customer Landline Phone',
'Customer Office Phone', 'Customer Office Phone',
'Customer Fax Phone', 'Customer Alternative Phone',
'Customer Email Address', 'Customer Email Address',
'Customer Notes', 'Customer Notes',
'Customer Has Third Party Privacy Policy?', 'Customer Has Third Party Privacy Policy?',
@ -1142,7 +1142,7 @@ class ReportController extends Controller
'Mobile Phone', 'Mobile Phone',
'Landline', 'Landline',
'Office Phone', 'Office Phone',
'Fax', 'Alternative Phone',
'Email Address', 'Email Address',
'Vehicle Manufacturer', 'Vehicle Manufacturer',
'Vehicle Make', 'Vehicle Make',

View file

@ -2,11 +2,13 @@
namespace App\Entity; namespace App\Entity;
use App\Ramcar\InsuranceApplicationStatus;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
use DateTime; use DateTime;
use Doctrine\Common\Collections\Criteria;
/** /**
* @ORM\Entity * @ORM\Entity
@ -114,10 +116,15 @@ class CustomerVehicle
*/ */
protected $flag_active; protected $flag_active;
// link to insurance
/**
* @ORM\OneToMany(targetEntity="InsuranceApplication", mappedBy="customer_vehicle")
*/
protected $insurance_applications;
public function __construct() public function __construct()
{ {
$this->flag_active = true; $this->flag_active = true;
$this->job_orders = new ArrayCollection(); $this->job_orders = new ArrayCollection();
} }
@ -282,4 +289,21 @@ class CustomerVehicle
{ {
return $this->flag_active; return $this->flag_active;
} }
public function getInsuranceApplications()
{
return $this->insurance_applications;
}
public function getLatestInsuranceApplication()
{
$criteria = Criteria::create()
->where(Criteria::expr()->notIn('status', [InsuranceApplicationStatus::CANCELLED]))
->orderBy(['date_submit' => Criteria::DESC])
->setMaxResults(1);
$result = $this->insurance_applications->matching($criteria);
return !empty($result) ? $result[0] : null;
}
} }

View file

@ -0,0 +1,193 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Ramcar\TransactionStatus;
use Symfony\Component\Validator\Constraints as Assert;
use DateTime;
/**
* @ORM\Entity
* @ORM\Table(name="gateway_transaction")
*/
class GatewayTransaction
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity="Customer", inversedBy="transactions")
* @ORM\JoinColumn(name="customer_id", referencedColumnName="id")
*/
protected $customer;
// date ticket was created
/**
* @ORM\Column(type="datetime")
*/
protected $date_create;
// date ticket was paid
/**
* @ORM\Column(type="datetime", nullable=true)
*/
protected $date_pay;
// amount
/**
* @ORM\Column(type="bigint")
* @Assert\NotBlank()
*/
protected $amount;
// status of the transaction
/**
* @ORM\Column(type="string", length=50)
* @Assert\NotBlank()
*/
protected $status;
// type of transaction
/**
* @ORM\Column(type="string", length=50)
* @Assert\NotBlank()
*/
protected $type;
// gateway used for transaction
/**
* @ORM\Column(type="string", length=50)
* @Assert\NotBlank()
*/
protected $gateway;
// external transaction id
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $ext_transaction_id;
// other data related to the transaction
/**
* @ORM\Column(type="json")
*/
protected $metadata;
public function __construct()
{
$this->date_create = new DateTime();
$this->status = TransactionStatus::PENDING;
$this->metadata = [];
}
public function getID()
{
return $this->id;
}
public function setCustomer(Customer $customer)
{
$this->customer = $customer;
return $this;
}
public function getCustomer()
{
return $this->customer;
}
public function setDateCreate(DateTime $date)
{
$this->date_create = $date;
return $this;
}
public function getDateCreate()
{
return $this->date_create;
}
public function setDatePay(DateTime $date)
{
$this->date_pay = $date;
return $this;
}
public function getDatePay()
{
return $this->date_pay;
}
public function setAmount($amount)
{
$this->amount = $amount;
return $this;
}
public function getAmount()
{
return $this->amount;
}
public function setStatus($status)
{
$this->status = $status;
return $this;
}
public function getStatus()
{
return $this->status;
}
public function setType($type)
{
$this->type = $type;
return $this;
}
public function getType()
{
return $this->type;
}
public function setGateway($gateway)
{
$this->gateway = $gateway;
return $this;
}
public function getGateway()
{
return $this->gateway;
}
public function setExtTransactionId($transaction_id)
{
$this->ext_transaction_id = $transaction_id;
return $this;
}
public function getExtTransactionId()
{
return $this->ext_transaction_id;
}
public function setMetadata($metadata)
{
$this->metadata = $metadata;
return $this;
}
public function getMetadata()
{
return $this->metadata;
}
}

View file

@ -0,0 +1,226 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use DateTime;
/**
* @ORM\Entity
* @ORM\Table(name="insurance_application")
*/
class InsuranceApplication
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// link to customer
/**
* @ORM\ManyToOne(targetEntity="Customer")
* @ORM\JoinColumn(name="customer_id", referencedColumnName="id")
*/
protected $customer;
// link to customer vehicle
/**
* @ORM\ManyToOne(targetEntity="CustomerVehicle", inversedBy="insurance")
* @ORM\JoinColumn(name="customer_vehicle_id", referencedColumnName="id")
* @Assert\NotBlank()
*/
protected $customer_vehicle;
// gateway transaction
/**
* @ORM\OneToOne(targetEntity="GatewayTransaction")
* @ORM\JoinColumn(name="gateway_transaction_id", referencedColumnName="id")
*/
protected $gateway_transaction;
// status
/**
* @ORM\Column(type="string", length=32)
*/
protected $status;
// URL of COC
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $coc_url;
// date the application was submitted
/**
* @ORM\Column(type="datetime")
*/
protected $date_submit;
// date the application was paid
/**
* @ORM\Column(type="datetime", nullable=true)
*/
protected $date_pay;
// date the application was marked as completed by the insurance api
/**
* @ORM\Column(type="datetime", nullable=true)
*/
protected $date_complete;
// date the application is set to expire
/**
* @ORM\Column(type="datetime", nullable=true)
*/
protected $date_expire;
// external transaction id
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $ext_transaction_id;
// form data when submitting the application
/**
* @ORM\Column(type="json")
*/
protected $metadata;
public function __construct()
{
$this->date_submit = new DateTime();
$this->date_pay = null;
$this->date_complete = null;
$this->date_expire = null;
$this->metadata = [];
}
public function getID()
{
return $this->id;
}
public function setCustomer(Customer $cust = null)
{
$this->customer = $cust;
return $this;
}
public function getCustomer()
{
return $this->customer;
}
public function setCustomerVehicle(CustomerVehicle $cv = null)
{
$this->customer_vehicle = $cv;
return $this;
}
public function getCustomerVehicle()
{
return $this->customer_vehicle;
}
public function setDateSubmit(DateTime $date)
{
$this->date_submit = $date;
return $this;
}
public function getDateSubmit()
{
return $this->date_submit;
}
public function setGatewayTransaction(GatewayTransaction $transaction)
{
$this->gateway_transaction = $transaction;
return $this;
}
public function getGatewayTransaction()
{
return $this->gateway_transaction;
}
public function setStatus($status)
{
return $this->status = $status;
}
public function getStatus()
{
return $this->status;
}
public function setCOC($url)
{
return $this->coc_url = $url;
}
public function getCOC()
{
return $this->coc_url;
}
public function setDatePay(DateTime $date)
{
$this->date_pay = $date;
return $this;
}
public function getDatePay()
{
return $this->date_pay;
}
public function setDateComplete(DateTime $date)
{
$this->date_complete = $date;
return $this;
}
public function getDateComplete()
{
return $this->date_complete;
}
public function setDateExpire(DateTime $date)
{
$this->date_expire = $date;
return $this;
}
public function getDateExpire()
{
return $this->date_expire;
}
public function setExtTransactionId($transaction_id)
{
$this->ext_transaction_id = $transaction_id;
return $this;
}
public function getExtTransactionId()
{
return $this->ext_transaction_id;
}
public function setMetadata($metadata)
{
return $this->metadata = $metadata;
}
public function getMetadata()
{
return $this->metadata;
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace App\EntityListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\GatewayTransaction;
use App\Entity\InsuranceApplication;
use App\Service\InsuranceConnector;
use App\Ramcar\InsuranceApplicationStatus;
use App\Ramcar\TransactionStatus;
use DateTime;
class GatewayTransactionListener
{
protected $ic;
protected $em;
public function __construct(EntityManagerInterface $em, InsuranceConnector $ic)
{
$this->em = $em;
$this->ic = $ic;
}
public function postUpdate(GatewayTransaction $gt_obj, LifecycleEventArgs $args)
{
// get transaction changes
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$changeset = $uow->getEntityChangeSet($gt_obj);
if (array_key_exists('status', $changeset)) {
$field_changes = $changeset['status'];
$prev_value = $field_changes[0] ?? null;
$new_value = $field_changes[1] ?? null;
// only do something if the status has changed to paid
if ($prev_value !== $new_value && $new_value === TransactionStatus::PAID) {
// handle based on type
// TODO: add types here as we go. there's probably a better way to do this.
switch ($gt_obj->getType()) {
case 'insurance_premium':
return $this->handleInsurancePremium($gt_obj);
break;
default:
break;
}
}
}
}
protected function handleInsurancePremium($gt_obj)
{
// get insurance application object
$obj = $this->em->getRepository(InsuranceApplication::class)->findOneBy([
'gateway_transaction' => $gt_obj,
]);
if (!empty($obj)) {
// mark as paid
$obj->setDatePay(new DateTime());
$obj->setStatus(InsuranceApplicationStatus::PAID);
$this->em->flush();
}
// flag on api as paid
$result = $this->ic->tagApplicationPaid($obj->getID());
if (!$result['success'] || $result['response']['transaction_code'] !== 'GR004') {
error_log("INSURANCE MARK AS PAID FAILED FOR " . $obj->getID() . ": " . $result['error']['message']);
}
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Ramcar;
class InsuranceApplicationStatus extends NameValue
{
const CREATED = 'created';
const PAID = 'paid';
const COMPLETED = 'completed';
const CANCELLED = 'cancelled';
const COLLECTION = [
'created' => 'Created',
'paid' => 'Paid',
'completed' => 'Completed',
'cancelled' => 'Cancelled',
];
}

View file

@ -0,0 +1,14 @@
<?php
namespace App\Ramcar;
class InsuranceClientType extends NameValue
{
const INDIVIDUAL = 'i';
const CORPORATE = 'c';
const COLLECTION = [
'i' => 'Individual',
'c' => 'Corporate',
];
}

View file

@ -0,0 +1,32 @@
<?php
namespace App\Ramcar;
class InsuranceMVType extends NameValue
{
const CAR = '1';
const SHUTTLE_BUS = '2';
const MOTORCYCLE = '3';
const MC_WITH_SIDECAR = '4';
const NON_CONVENTIONAL = '5';
const SUV = '8';
const TRUCK = '9';
const TRAILER = '10';
const UV_PRIVATE = '11';
const UV_COMMERCIAL = '12';
const TRICYCLE = '13';
const COLLECTION = [
'1' => "Car",
'2' => "Shuttle Bus",
'3' => "Motorcycle",
'4' => "Motorcycle with Sidecar",
'5' => "Non-Conventional MV",
'8' => "Sports Utility Vehicle",
'9' => "Truck",
'10' => "Trailer",
'11' => "UV Private",
'12' => "UV Commercial",
'13' => "Tricycle",
];
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Ramcar;
class InsuranceVehicleLine extends NameValue
{
const PCOC = 'pcoc';
const MCOC = 'mcoc';
const CCOC = 'ccoc';
const LCOC = 'lcoc';
const COLLECTION = [
'pcoc' => 'Private Car',
'mcoc' => 'Motorcycle / Motorcycle with Sidecar / Tricycle (Private)',
'ccoc' => 'Commercial Vehicle',
'lcoc' => 'Motorcycle / Motorcycle with Sidecar / Tricycle (Public)',
];
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Ramcar;
class TransactionStatus extends NameValue
{
const PENDING = 'pending';
const PAID = 'paid';
const CANCELLED = 'cancelled';
const REFUNDED = 'refunded';
const COLLECTION = [
'pending' => 'Pending',
'paid' => 'Paid',
'cancelled' => 'Cancelled',
'refunded' => 'Refunded',
];
}

View file

@ -2,6 +2,7 @@
namespace App\Service; namespace App\Service;
use App\Entity\Customer;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
use Fcm\FcmClient; use Fcm\FcmClient;
use Fcm\Push\Notification; use Fcm\Push\Notification;
@ -53,42 +54,62 @@ class FCMSender
public function sendJoEvent(JobOrder $job_order, $title, $body, $data = []) public function sendJoEvent(JobOrder $job_order, $title, $body, $data = [])
{ {
// get all v2 sessions // get customer object
$cust = $job_order->getCustomer();
// attach jo info
$data['jo_id'] = $job_order->getID();
$data['jo_status'] = $job_order->getStatus();
// send the event
return $this->sendEvent($cust, $title, $body, $data);
}
public function sendEvent(Customer $cust, $title, $body, $data = [])
{
// get all v2 devices
$devices = $this->getDevices($cust);
if (empty($devices)) {
return false;
}
// send fcm notification
$result = $this->send(array_keys($devices), $this->translator->trans($title), $this->translator->trans($body), $data);
return $result;
}
protected function getDevices(Customer $cust)
{
$sessions = []; $sessions = [];
$cust_user = $job_order->getCustomer()->getCustomerUser(); $device_ids = [];
$cust_user = $cust->getCustomerUser();
if (!empty($cust_user)) { if (!empty($cust_user)) {
$sessions = $cust_user->getMobileSessions(); $sessions = $cust_user->getMobileSessions();
} }
if (empty($sessions)) { if (empty($sessions)) {
error_log("no sessions to send fcm notification to"); error_log("no sessions to send fcm notification to");
return; return false;
} }
$device_ids = [];
// send to every customer session // send to every customer session
foreach ($sessions as $sess) { foreach ($sessions as $sess) {
$device_id = $sess->getDevicePushID(); $device_id = $sess->getDevicePushID();
if (!empty($device_id) && !isset($device_ids[$device_id])) { if (!empty($device_id) && !isset($device_ids[$device_id])) {
// send fcm notification // send to this device
$device_ids[$device_id] = true; $device_ids[$device_id] = true;
} }
} }
if (empty($device_ids)) { if (empty($device_ids)) {
error_log("no devices to send fcm notification to"); error_log("no devices to send fcm notification to");
return; return false;
} }
// attach jo info return $device_ids;
$data['jo_id'] = $job_order->getID();
$data['jo_status'] = $job_order->getStatus();
// send fcm notification
$result = $this->send(array_keys($device_ids), $this->translator->trans($title), $this->translator->trans($body), $data);
return $result;
} }
} }

View file

@ -2,6 +2,11 @@
namespace App\Service; namespace App\Service;
use App\Entity\CustomerVehicle;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\RequestException;
class InsuranceConnector class InsuranceConnector
{ {
protected $base_url; protected $base_url;
@ -17,37 +22,68 @@ class InsuranceConnector
$this->hash = $this->generateHash(); $this->hash = $this->generateHash();
} }
public function createApplication($notif_url, $client_info, $client_contact_info, $car_info) public function createApplication(CustomerVehicle $cv, $notif_url, $data, $orcr_file)
{ {
$body = [ $body = [
'notif_url' => $notif_url, 'notif_url' => $notif_url,
'client_info' => $client_info, 'client_info' => [
'client_contact_info' => $client_contact_info, 'client_type' => $data['client_type'],
'car_info' => $car_info, 'first_name' => $data['first_name'],
'middle_name' => $data['middle_name'] ?? null,
'surname' => $data['surname'],
'corporate_name' => $data['corporate_name'],
],
'client_contact_info' => [
'address_number' => $data['address_number'],
'address_street' => $data['address_street'] ?? null,
'address_building' => $data['address_building'] ?? null,
'address_barangay' => $data['address_barangay'],
'address_city' => $data['address_city'],
'address_province' => $data['address_province'],
'zipcode' => (int)$data['zipcode'],
'mobile_number' => $data['mobile_number'],
'email_address' => $data['email_address'],
],
'car_info' => [
'make' => $data['make'],
'model' => $data['model'],
'series' => $data['series'],
'color' => $data['color'],
'plate_number' => $cv->getPlateNumber(),
'mv_file_number' => $data['mv_file_number'],
'motor_number' => $data['motor_number'],
'serial_chasis' => $data['serial_chasis'],
'year_model' => (int)$data['year_model'],
'mv_type_id' => (int)$data['mv_type_id'],
'body_type' => $data['body_type'],
'is_public' => (bool)$data['is_public'],
'line' => $data['line'],
'orcr_file' => base64_encode(file_get_contents($orcr_file->getPathname())),
],
]; ];
return $this->doRequest('/api/v1/ctpl/applications', true, $body); return $this->doRequest('/api/v1/ctpl/applications', 'POST', $body);
} }
public function tagApplicationPaid($application_id) public function tagApplicationPaid($application_id)
{ {
$url = '/api/v1/ctpl/application/' . $application_id . '/paid'; $url = '/api/v1/ctpl/application/' . $application_id . '/paid';
return $this->doRequest($url, true); return $this->doRequest($url, 'POST');
} }
public function getVehicleMakers() public function getVehicleMakers()
{ {
return $this->doRequest('/api/v1/ctpl/vehicle-makers'); return $this->doRequest('/api/v1/ctpl/vehicle-makers', 'GET');
} }
public function getVehicleModels() public function getVehicleModels($maker_id)
{ {
return $this->doRequest('/api/v1/ctpl/vehicle-models'); return $this->doRequest('/api/v1/ctpl/vehicle-models?maker_id='. $maker_id, 'GET');
} }
public function getVehicleTrims() public function getVehicleTrims($model_id)
{ {
return $this->doRequest('/api/v1/ctpl/vehicle-trims'); return $this->doRequest('/api/v1/ctpl/vehicle-trims?model_id='. $model_id, 'GET');
} }
protected function generateHash() protected function generateHash()
@ -55,46 +91,42 @@ class InsuranceConnector
return base64_encode($this->username . ":" . $this->password); return base64_encode($this->username . ":" . $this->password);
} }
protected function doRequest($url, $is_post = false, $body = []) protected function doRequest($url, $method, $body = [])
{ {
$curl = curl_init(); $client = new Client();
$headers = [
$options = [ 'Content-Type' => 'application/json',
CURLOPT_URL => $this->base_url . '/' . $url, 'accept' => 'application/json',
CURLOPT_POST => $is_post, 'authorization' => 'Basic '. $this->hash,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Basic ' . $this->hash,
],
]; ];
// add post body if present try {
if (!empty($body)) { $response = $client->request($method, $this->base_url . '/' . $url, [
$options[CURLOPT_POSTFIELDS] = json_encode($body); 'json' => $body,
'headers' => $headers,
]);
} catch (RequestException $e) {
$error = ['message' => $e->getMessage()];
error_log("Insurance API Error: " . $error['message']);
error_log(Psr7\Message::toString($e->getRequest()));
error_log($e->getResponse()->getBody()->getContents());
if ($e->hasResponse()) {
$error['response'] = Psr7\Message::toString($e->getResponse());
}
return [
'success' => false,
'error' => $error,
];
} }
curl_setopt_array($curl, $options); error_log(print_r(json_decode($response->getBody(), true), true));
$res = curl_exec($curl);
curl_close($curl); return [
'success' => true,
error_log('Insurance API connector'); 'response' => json_decode($response->getBody(), true)
error_log(print_r($options, true)); ];
error_log($res);
// response
return $this->handleResponse($res);
}
protected function handleResponse($res)
{
$inv_res = json_decode($res, true);
// make sure result is always an array
if ($inv_res == null)
return [];
return $inv_res;
} }
} }

View file

@ -0,0 +1,125 @@
<?php
namespace App\Service;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use GuzzleHttp\Exception\RequestException;
use App\Entity\Customer;
class PayMongoConnector
{
protected $base_url;
protected $public_key;
protected $secret_key;
protected $hash;
public function __construct($base_url, $public_key, $secret_key)
{
$this->base_url = $base_url;
$this->public_key = $public_key;
$this->secret_key = $secret_key;
$this->hash = $this->generateHash();
}
public function createCheckout(Customer $cust, $items, $ref_no = null, $description = null, $success_url = null, $cancel_url = null, $metadata = [])
{
// build billing info
$billing = [
'name' => implode(" ", [$cust->getFirstName(), $cust->getLastName()]),
'phone' => $cust->getPhoneMobile(),
];
if ($cust->getEmail()) {
$billing['email'] = $cust->getEmail();
}
// build the request body
$body = [
'data' => [
'attributes' => [
'description' => $description,
'billing' => $billing,
// NOTE: this may be variable later, hardcoding for now
'payment_method_types' => [
'card',
'paymaya',
'gcash',
],
/* NOTE: format for line items:
* ['name', 'description', 'quantity', 'amount', 'currency']
*/
'line_items' => $items,
'reference_number' => (string)$ref_no,
'cancel_url' => $cancel_url,
'success_url' => $success_url,
'statement_descriptor' => $description,
'send_email_receipt' => true,
'show_description' => true,
'show_line_items' => false,
],
],
];
if (!empty($metadata)) {
$body['data']['attributes']['metadata'] = $metadata;
}
return $this->doRequest('/v1/checkout_sessions', 'POST', $body);
}
public function getCheckout($checkout_id)
{
return $this->doRequest('/v1/checkout_sessions/' . $checkout_id, 'GET');
}
protected function generateHash()
{
return base64_encode($this->secret_key);
}
protected function doRequest($url, $method, $body = [])
{
$client = new Client();
$headers = [
'Content-Type' => 'application/json',
'accept' => 'application/json',
'authorization' => 'Basic '. $this->hash,
];
try {
$response = $client->request($method, $this->base_url . '/' . $url, [
'json' => $body,
'headers' => $headers,
]);
} catch (RequestException $e) {
$error = ['message' => $e->getMessage()];
ob_start();
var_dump($body);
$varres = ob_get_clean();
error_log($varres);
error_log("--------------------------------------");
error_log($e->getResponse()->getBody()->getContents());
error_log("PayMongo API Error: " . $error['message']);
error_log(Psr7\Message::toString($e->getRequest()));
if ($e->hasResponse()) {
$error['response'] = Psr7\Message::toString($e->getResponse());
}
return [
'success' => false,
'error' => $error,
];
}
return [
'success' => true,
'response' => json_decode($response->getBody(), true)
];
}
}

View file

@ -219,7 +219,7 @@
</div> </div>
<div class="col-lg-4"> <div class="col-lg-4">
<label data-field="phone_fax"> <label data-field="phone_fax">
Fax Alternative Phone
</label> </label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>

View file

@ -138,7 +138,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="customer_phone_fax">Fax</label> <label data-field="customer_phone_fax">Alternative Phone</label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_fax" id="customer-phone-fax" class="form-control m-input" value="{{ obj.getCustomer.getPhoneFax|default('') }}" data-vehicle-field="1" disabled> <input type="text" name="customer_phone_fax" id="customer-phone-fax" class="form-control m-input" value="{{ obj.getCustomer.getPhoneFax|default('') }}" data-vehicle-field="1" disabled>

View file

@ -138,7 +138,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="customer_phone_fax">Fax</label> <label data-field="customer_phone_fax">Alternative Phone</label>
<div class="input-group m-input-group"> <div class="input-group m-input-group">
<span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span> <span class="input-group-addon">{% trans %}country_code_prefix{% endtrans %}</span>
<input type="text" name="customer_phone_fax" id="customer-phone-fax" class="form-control m-input" value="{{ obj.getCustomer.getPhoneFax|default('') }}" data-vehicle-field="1" disabled> <input type="text" name="customer_phone_fax" id="customer-phone-fax" class="form-control m-input" value="{{ obj.getCustomer.getPhoneFax|default('') }}" data-vehicle-field="1" disabled>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Cancelled</title>
<style>
body {
background-color: #333;
}
</style>
</head>
<body>
<script>
window.addEventListener('load', (e) => {
if (typeof toApp !== 'undefined') {
toApp.postMessage("paymentCancelled");
}
});
</script>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment Successful</title>
<style>
body {
background-color: #333;
}
</style>
</head>
<body>
<script>
window.addEventListener('load', (e) => {
if (typeof toApp !== 'undefined') {
toApp.postMessage("paymentSuccess");
}
});
</script>
</body>
</html>

View file

@ -39,7 +39,7 @@ label.pdf.mobile_phone: 'Mobile Phone:'
label.pdf.delivery_date: 'Delivery Date:' label.pdf.delivery_date: 'Delivery Date:'
label.pdf.landline: 'Landline:' label.pdf.landline: 'Landline:'
label.pdf.office_phone: 'Office Phone:' label.pdf.office_phone: 'Office Phone:'
label.pdf.fax: 'Fax:' label.pdf.fax: 'Alternative Phone:'
label.pdf.vehicle_details: 'Vehicle Details' label.pdf.vehicle_details: 'Vehicle Details'
label.pdf.plate_number: 'Plate Number:' label.pdf.plate_number: 'Plate Number:'
label.pdf.vehicle_color: 'Vehicle Color:' label.pdf.vehicle_color: 'Vehicle Color:'
@ -161,13 +161,19 @@ menu.database.ownershiptypes: 'Ownership Types'
menu.database.serviceofferings: 'Service Offerings' menu.database.serviceofferings: 'Service Offerings'
# fcm jo status updates # fcm jo status updates
jo_fcm_title_outlet_assign: Looking for riders jo_fcm_title_outlet_assign: 'Looking for riders'
jo_fcm_title_driver_assigned: Rider found jo_fcm_title_driver_assigned: 'Rider found'
jo_fcm_title_driver_arrived: Rider nearby jo_fcm_title_driver_arrived: 'Rider nearby'
jo_fcm_title_cancelled: Order cancelled jo_fcm_title_cancelled: 'Order cancelled'
jo_fcm_title_fulfilled: Thank you! jo_fcm_title_fulfilled: 'Thank you!'
jo_fcm_body_outlet_assign: We're assigning a rider for your order, please wait. jo_fcm_body_outlet_assign: 'We`re assigning a rider for your order, please wait.'
jo_fcm_body_driver_assigned: A rider is on their way. jo_fcm_body_driver_assigned: 'A rider is on their way.'
jo_fcm_body_driver_arrived: Your order is almost there! jo_fcm_body_driver_arrived: 'Your order is almost there!'
jo_fcm_body_cancelled: Your order has been cancelled. jo_fcm_body_cancelled: 'Your order has been cancelled.'
jo_fcm_body_fulfilled: Order complete! Your receipt is ready. jo_fcm_body_fulfilled: 'Order complete! Your receipt is ready.'
# fcm insurance
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!'