Merge branch '761-add-paramount-insurance-and-paymongo-support-for-resq2-api' into '746-resq-2-0-final'

Resolve "Add Paramount insurance and PayMongo support for RESQ2 API"

See merge request jankstudio/resq!888
This commit is contained in:
Ramon Gutierrez 2023-11-17 20:28:02 +00:00
commit fb2ca0bbd8
24 changed files with 1667 additions and 107 deletions

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
@ -260,4 +265,35 @@ apiv2_account_delete_resend_code:
apiv2_account_delete_code_validate: apiv2_account_delete_code_validate:
path: /apiv2/account_delete_code_validate path: /apiv2/account_delete_code_validate
controller: App\Controller\CustomerAppAPI\AccountController::validateDeleteCode controller: App\Controller\CustomerAppAPI\AccountController::validateDeleteCode
methods: [POST]
# 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] 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

@ -8,7 +8,8 @@ use Catalyst\ApiBundle\Component\Response as ApiResponse;
use App\Entity\CustomerVehicle; use App\Entity\CustomerVehicle;
use App\Entity\VehicleManufacturer; use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle; use App\Entity\Vehicle;
use App\Ramcar\InsuranceApplicationStatus;
use App\Service\PayMongoConnector;
use DateTime; use DateTime;
class VehicleController extends ApiController class VehicleController extends ApiController
@ -107,6 +108,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
@ -141,7 +170,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);
@ -162,37 +191,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
@ -285,6 +284,98 @@ class VehicleController extends ApiController
return new ApiResponse(); return new ApiResponse();
} }
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

@ -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

@ -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

@ -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!'