From 20d6ca47dc26de3ebbdc0337d7e089b5170da157 Mon Sep 17 00:00:00 2001 From: Dillon Date: Tue, 30 May 2023 22:02:09 +0800 Subject: [PATCH] Vourcherify --- .env.example | 7 ++ .../Eloquent/Filters/TransactionId.php | 20 +++++ app/Classes/General/VoucherifyHelper.php | 14 +++ .../CreateBookingPaymentLogic.php | 59 ++++++++++++- .../FetchBookingPaymentQuotationLogic.php | 7 +- .../DataTransferObjects/CalculationObject.php | 45 +++++++++- .../Services/FetchesBookingQuotation.php | 30 ++++++- .../Services/GeneratesBookingQuotation.php | 6 +- .../SuspendTransactionLogic.php | 24 +++++- .../ControllersLogic/RedeemVoucherLogic.php | 48 +++++++++++ .../ControllersLogic/ValidateVoucherLogic.php | 48 +++++++++++ .../ValidatedVoucherObject.php | 82 ++++++++++++++++++ .../DataTransferObjects/VoucherObject.php | 70 +++++++++++++++ .../Services/CheckIfVoucherExists.php | 27 ++++++ .../Vouchers/Services/CreatesVoucher.php | 40 +++++++++ .../Services/CreatesVoucherRedemption.php | 28 ++++++ .../Vouchers/Services/FetchesVoucher.php | 34 ++++++++ .../Services/FetchesVoucherRedemption.php | 34 ++++++++ .../Vouchers/Services/RedeemsVoucher.php | 48 +++++++++++ .../Vouchers/Services/RollbacksRedemption.php | 39 +++++++++ .../Vouchers/Services/ValidatesVoucher.php | 64 ++++++++++++++ .../Vouchers/RedeemVoucherController.php | 20 +++++ .../Vouchers/ValidateVoucherController.php | 20 +++++ app/Http/Resources/TransactionResource.php | 3 +- .../Resources/VoucherRedemptionResource.php | 25 ++++++ app/Models/Transaction.php | 8 ++ app/Models/User.php | 9 ++ app/Models/Voucher.php | 19 ++++ app/Models/VoucherRedemption.php | 36 ++++++++ composer.json | 4 + config/voucherify.php | 8 ++ ...023_05_27_050522_create_vouchers_table.php | 35 ++++++++ ...50523_create_voucher_redemptions_table.php | 41 +++++++++ .../elements/BookingConfirmationComponent.vue | 22 ++--- .../elements/PaymentHistoryComponent.vue | 13 ++- .../BookingPaymentQuotationComponent.vue | 86 +++++++++++++++++-- .../forms/ConfirmQuotationFormComponent.vue | 4 +- routes/api.php | 2 + routes/voucher.php | 8 ++ 39 files changed, 1102 insertions(+), 35 deletions(-) create mode 100644 app/Classes/General/Eloquent/Filters/TransactionId.php create mode 100644 app/Classes/General/VoucherifyHelper.php create mode 100644 app/Classes/Modules/Vouchers/ControllersLogic/RedeemVoucherLogic.php create mode 100644 app/Classes/Modules/Vouchers/ControllersLogic/ValidateVoucherLogic.php create mode 100644 app/Classes/Modules/Vouchers/DataTransferObjects/ValidatedVoucherObject.php create mode 100644 app/Classes/Modules/Vouchers/DataTransferObjects/VoucherObject.php create mode 100644 app/Classes/Modules/Vouchers/Services/CheckIfVoucherExists.php create mode 100644 app/Classes/Modules/Vouchers/Services/CreatesVoucher.php create mode 100644 app/Classes/Modules/Vouchers/Services/CreatesVoucherRedemption.php create mode 100644 app/Classes/Modules/Vouchers/Services/FetchesVoucher.php create mode 100644 app/Classes/Modules/Vouchers/Services/FetchesVoucherRedemption.php create mode 100644 app/Classes/Modules/Vouchers/Services/RedeemsVoucher.php create mode 100644 app/Classes/Modules/Vouchers/Services/RollbacksRedemption.php create mode 100644 app/Classes/Modules/Vouchers/Services/ValidatesVoucher.php create mode 100644 app/Http/Controllers/Vouchers/RedeemVoucherController.php create mode 100644 app/Http/Controllers/Vouchers/ValidateVoucherController.php create mode 100644 app/Http/Resources/VoucherRedemptionResource.php create mode 100644 app/Models/Voucher.php create mode 100644 app/Models/VoucherRedemption.php create mode 100644 config/voucherify.php create mode 100644 database/migrations/2023_05_27_050522_create_vouchers_table.php create mode 100644 database/migrations/2023_05_27_050523_create_voucher_redemptions_table.php create mode 100644 routes/voucher.php diff --git a/.env.example b/.env.example index 91c8e15c..428d747b 100644 --- a/.env.example +++ b/.env.example @@ -58,6 +58,13 @@ BILLPLZ_X_SIGNATURE_KEY="S-HdAU6QDubUrMErxJ-PCpgw" BILLPLZ_COLLECTION_ID="t0cggbdd" BILLPLZ_WALLET_COLLECTION_ID="t0cggbdd" + PERFEXCRM_BASE_URL="" PERFEXCRM_API_KEY="" PERFEXCRM_IS_ENABLED="false" + + +VOUCHERIFY_APPLICATION_ID="" +VOUCHERIFY_CLIENT_SECRET_KEY="" +VOUCHERIFY_VERSION="v2018-08-01" +VOUCHERIFY_URL="https://as1.api.voucherify.io" diff --git a/app/Classes/General/Eloquent/Filters/TransactionId.php b/app/Classes/General/Eloquent/Filters/TransactionId.php new file mode 100644 index 00000000..b764c8fb --- /dev/null +++ b/app/Classes/General/Eloquent/Filters/TransactionId.php @@ -0,0 +1,20 @@ +where('transaction_id', $value); + } + +} diff --git a/app/Classes/General/VoucherifyHelper.php b/app/Classes/General/VoucherifyHelper.php new file mode 100644 index 00000000..83e38482 --- /dev/null +++ b/app/Classes/General/VoucherifyHelper.php @@ -0,0 +1,14 @@ +fetchBookingQuotation = $fetchBookingQuotation; $this->fetchesCompanyPaymentAttemptLimit = $fetchesCompanyPaymentAttemptLimit; @@ -99,6 +118,10 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic $this->updatesTransactionStatus = $updatesTransactionStatus; $this->createCashBackTransactionProcessor = $createCashBackTransactionProcessor; $this->recalculatesWalletBalance = $recalculatesWalletBalance; + $this->fetchesVoucher = $fetchesVoucher; + $this->createsVoucher = $createsVoucher; + $this->createsVoucherRedemption = $createsVoucherRedemption; + $this->redeemsVoucher = $redeemsVoucher; } /** @@ -108,6 +131,12 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic */ public function logic(Request $request) : JsonResponse { + $voucherCode = $request->input('voucher_code'); //cief to, json_encode and json_decode ??? + // if($voucherReqInput){ + // $voucherObject = json_decode($voucherReqInput); + // $validatedVoucherObject = new ValidatedVoucherObject($voucherObject->metadata->name, $voucherObject->code, $voucherObject->discount->type, $voucherObject->discount->amount_off); + // } + $booking = Booking::find($request->route('id')); $conversionObject = new CurrencyConversionObject(floatval(str_replace(',', '', $request->input('amount'))), $booking->convertible_currency_id, $booking->service_id, $booking->fix_currency_id === 1 ? 0:1, PaymentMethodType::PAYMENT_METHODS[$request->input('payment_method')]); @@ -116,7 +145,7 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic if($conversionObject->getAmount() > round($outstanding, 2)) throw new MalformedRequestException('Your payment must not be greater than '. $outstanding .'.'); - $configurations = $this->fetchBookingQuotation->execute($booking->company, $conversionObject); + $configurations = $this->fetchBookingQuotation->execute($booking->company, $conversionObject, $voucherCode); $paymentAttemptLimit = $this->fetchesCompanyPaymentAttemptLimit->execute($booking->company); @@ -164,6 +193,30 @@ class CreateBookingPaymentLogic extends AbstractControllerLogic $this->updatesTransactionStatus->execute($transaction, ApprovalStatus::APPROVED); } + //Voucherify + if($voucherCode){ + $employee = $booking->company->employees()->first(); + + $result = $this->redeemsVoucher->execute($voucherCode, $configurations->getSubTotal(), $employee); + $voucher = $result->voucher; + $redemptionId = $result->id; + //Update Database + $voucherValue = 0.00; + if(isset($voucher->discount->amount_off)){ + $voucherValue =$voucher->discount->amount_off; + } + if($voucherValue == 0.00){ + if(isset($voucher->discount->percent_off)){ + $voucherValue =$voucher->discount->percent_off; + } + } + + $voucherObject= new VoucherObject($voucher->code, isset($voucher->metadata->name) ? $voucher->metadata->name : "", $voucher->discount->type, $voucherValue); + $voucher = $this->createsVoucher->execute($voucherObject); + if(!$voucher) $voucher = $this->fetchesVoucher->execute(['code' => $voucherObject->getCode()]); + $this->createsVoucherRedemption->execute($transaction, $voucher, $redemptionId, $configurations->getVoucherDiscountAmount()); + } + return $this->resourceResponse(new TransactionResource($transaction)); } diff --git a/app/Classes/Modules/Bookings/ControllersLogic/FetchBookingPaymentQuotationLogic.php b/app/Classes/Modules/Bookings/ControllersLogic/FetchBookingPaymentQuotationLogic.php index 3a981946..8effdb2f 100644 --- a/app/Classes/Modules/Bookings/ControllersLogic/FetchBookingPaymentQuotationLogic.php +++ b/app/Classes/Modules/Bookings/ControllersLogic/FetchBookingPaymentQuotationLogic.php @@ -69,8 +69,11 @@ class FetchBookingPaymentQuotationLogic extends AbstractControllerLogic $outstanding = $this->calculatesBookingOutstanding->execute($booking); if($conversionObject->getAmount() > round($outstanding, 2)) throw new MalformedRequestException('Your payment must not be greater than '.$booking->fixedCurrency->short_code.' '. number_format((float)$outstanding, 2, '.', ',')); + //Voucherify + $voucherCode = $request->input('voucherCode'); + return $this->response(['data' => $this->generatesBookingQuotation->execute( - $this->fetchBookingQuotation->execute($booking->company, $conversionObject), + $this->fetchBookingQuotation->execute($booking->company, $conversionObject, $voucherCode), $this->fetchesCompanyPaymentAttemptLimit->execute($booking->company), $conversionObject )]); @@ -78,4 +81,4 @@ class FetchBookingPaymentQuotationLogic extends AbstractControllerLogic } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Bookings/DataTransferObjects/CalculationObject.php b/app/Classes/Modules/Bookings/DataTransferObjects/CalculationObject.php index aeeba8f7..db79848d 100644 --- a/app/Classes/Modules/Bookings/DataTransferObjects/CalculationObject.php +++ b/app/Classes/Modules/Bookings/DataTransferObjects/CalculationObject.php @@ -5,6 +5,7 @@ namespace App\Classes\Modules\Bookings\DataTransferObjects; use App\Classes\General\Interfaces\DataTransferObject; use App\Classes\Modules\Companies\DataTransferObjects\CompanyServiceConfigurationsObject; use App\Classes\Modules\Currencies\DataTransferObjects\CurrencyConversionObject; +use App\Classes\Modules\Vouchers\DataTransferObjects\ValidatedVoucherObject; class CalculationObject implements DataTransferObject { @@ -15,15 +16,20 @@ class CalculationObject implements DataTransferObject /** @var CompanyServiceConfigurationsObject */ private $configurations; + /** @var ValidatedVoucherObject */ + private $voucherObject; + /** * CalculationObject constructor. * @param CurrencyConversionObject $conversionObject * @param CompanyServiceConfigurationsObject $configurations + * @param ValidatedVoucherObject $voucherObject */ - public function __construct(CurrencyConversionObject $conversionObject, CompanyServiceConfigurationsObject $configurations) + public function __construct(CurrencyConversionObject $conversionObject, CompanyServiceConfigurationsObject $configurations, ?ValidatedVoucherObject $voucherObject = null) { $this->conversionObject = $conversionObject; $this->configurations = $configurations; + $this->voucherObject = $voucherObject; } /** @@ -43,6 +49,15 @@ class CalculationObject implements DataTransferObject return $this->configurations; } + + /** + * @return ValidatedVoucherObject + */ + public function getValidatedVoucherObject(): ?ValidatedVoucherObject + { + return $this->voucherObject; + } + public function getConvertibleTotal(){ return $this->getConversionObject()->getType() === 1 ? $this->getConversionObject()->getAmount() : $this->getConversionTotal(); } @@ -87,9 +102,31 @@ class CalculationObject implements DataTransferObject return ($this->getSubTotal()/100) * $this->getConfigurations()->getTax(); } - public function getTotal(): float { - return $this->getSubTotal() + $this->getTax(); + public function getVoucherDiscountAmount(): float { + if($this->getValidatedVoucherObject()){ + return $this->getValidatedVoucherObject()->getTotalDiscountAmount(); + } + else{ + return 0.00; + } } + public function getVoucherCode(): string { + if($this->getValidatedVoucherObject()){ + return $this->getValidatedVoucherObject()->getCode(); + } + else{ + return ""; + } + } -} \ No newline at end of file + public function getTotal(): float { + if($this->getValidatedVoucherObject()){ + return $this->getValidatedVoucherObject()->getTotalAmount() + $this->getTax(); + } + else{ + return $this->getSubTotal() + $this->getTax(); + } + } + +} diff --git a/app/Classes/Modules/Bookings/Services/FetchesBookingQuotation.php b/app/Classes/Modules/Bookings/Services/FetchesBookingQuotation.php index 17d776fd..c81f7bba 100644 --- a/app/Classes/Modules/Bookings/Services/FetchesBookingQuotation.php +++ b/app/Classes/Modules/Bookings/Services/FetchesBookingQuotation.php @@ -7,7 +7,9 @@ use App\Classes\Exceptions\MalformedRequestException; use App\Classes\Modules\Bookings\DataTransferObjects\CalculationObject; use App\Classes\Modules\Companies\Services\FetchesCompanyServiceSettings; use App\Classes\Modules\Currencies\DataTransferObjects\CurrencyConversionObject; +use App\Classes\Modules\Vouchers\DataTransferObjects\ValidatedVoucherObject; use App\Classes\Modules\Currencies\Services\FetchesCurrency; +use App\Classes\Modules\Vouchers\Services\ValidatesVoucher; use App\Models\Company; use App\Models\Currency; @@ -20,25 +22,30 @@ class FetchesBookingQuotation /** @var FetchesCurrency */ private $fetchesCurrency; + /** @var ValidatesVoucher */ + private $validatesVoucher; + /** * FetchesBookingQuotation constructor. * @param FetchesCompanyServiceSettings $fetchesCompanyServiceSettings * @param FetchesCurrency $fetchesCurrency */ - public function __construct(FetchesCompanyServiceSettings $fetchesCompanyServiceSettings, FetchesCurrency $fetchesCurrency) + public function __construct(FetchesCompanyServiceSettings $fetchesCompanyServiceSettings, FetchesCurrency $fetchesCurrency, ValidatesVoucher $validatesVoucher) { $this->fetchesCompanyServiceSettings = $fetchesCompanyServiceSettings; $this->fetchesCurrency = $fetchesCurrency; + $this->validatesVoucher = $validatesVoucher; } /** * @param Company $company * @param CurrencyConversionObject $conversionObject + * @param string $voucherCode * @return CalculationObject * @throws MalformedRequestException */ - public function execute(Company $company, CurrencyConversionObject $conversionObject){ + public function execute(Company $company, CurrencyConversionObject $conversionObject, ?string $voucherCode = null){ if($conversionObject->getAmount() <= 0) throw new MalformedRequestException('Your transfer must be greater than zero.'); $configurations = $this->fetchesCompanyServiceSettings->execute($company, $conversionObject); @@ -47,9 +54,24 @@ class FetchesBookingQuotation $currency = $this->fetchesCurrency->execute(['id' => $conversionObject->getCurrencyId()]); if($conversionObject->getAmount() > $configurations->getMaxLimit()) throw new MalformedRequestException('Your transfer can\'t be greater than '.number_format( floatval(str_replace(',', '', $configurations->getMaxLimit())), 2, '.', ',').' '.$currency->short_code); - $calculationObject = new CalculationObject($conversionObject, $configurations); + $calculationObject = new CalculationObject($conversionObject, $configurations, null); + + //Voucherify + if($voucherCode){ + $employee = $company->employees()->first(); + + $result = $this->validatesVoucher->execute($voucherCode, $calculationObject->getSubTotal(), $employee); + $voucher = [ + "code" => $result->code, + "discount" => $result->discount, + "metadata" => $result->metadata, + "order" => $result->order, + ]; + $validatedVoucherObject = new ValidatedVoucherObject(isset($voucher['metadata']->name) ? $voucher['metadata']->name: "", $voucher['code'], $voucher['discount']->type, $voucher['order']->total_discount_amount, $voucher['order']->total_amount); + $calculationObject = new CalculationObject($conversionObject, $configurations, $validatedVoucherObject); + } return $calculationObject; } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Bookings/Services/GeneratesBookingQuotation.php b/app/Classes/Modules/Bookings/Services/GeneratesBookingQuotation.php index 6fe93c97..8ea1b88d 100644 --- a/app/Classes/Modules/Bookings/Services/GeneratesBookingQuotation.php +++ b/app/Classes/Modules/Bookings/Services/GeneratesBookingQuotation.php @@ -34,7 +34,9 @@ class GeneratesBookingQuotation 'total' => $calculationObject->getTotal(), 'date' => Carbon::now()->timezone('Asia/Singapore')->format('h:i a, jS M, Y \G\M\T T'), 'receive_date' => $receive_date, - 'payment_attempt_limit' => CarbonInterval::days($days)->hours($hours)->minutes($minutes)->forHumans() + 'payment_attempt_limit' => CarbonInterval::days($days)->hours($hours)->minutes($minutes)->forHumans(), + 'voucher_discount_amount' => $calculationObject->getVoucherDiscountAmount(), + 'voucher_code' => $calculationObject->getVoucherCode(), ]; } -} \ No newline at end of file +} diff --git a/app/Classes/Modules/Transactions/ControllersLogic/SuspendTransactionLogic.php b/app/Classes/Modules/Transactions/ControllersLogic/SuspendTransactionLogic.php index e824547e..0500d6ae 100644 --- a/app/Classes/Modules/Transactions/ControllersLogic/SuspendTransactionLogic.php +++ b/app/Classes/Modules/Transactions/ControllersLogic/SuspendTransactionLogic.php @@ -7,6 +7,9 @@ use App\Classes\General\Abstracts\AbstractControllerLogic; use App\Classes\Modules\Transactions\Services\FetchesTransaction; use App\Classes\Modules\Transactions\Services\ListsTransactions; use App\Classes\Modules\Transactions\Services\UpdatesTransactionStatus; +use App\Classes\Modules\Vouchers\Services\FetchesVoucherRedemption; +use App\Classes\Modules\Vouchers\Services\CreatesVoucherRedemption; +use App\Classes\Modules\Vouchers\Services\RollbacksRedemption; use App\Classes\ValueObjects\Constants\ApprovalStatus; use App\Http\Resources\BookingResource; use App\Http\Resources\TransactionResource; @@ -32,16 +35,27 @@ class SuspendTransactionLogic extends AbstractControllerLogic /** @var UpdatesTransactionStatus */ private $updatesTransactionStatus; + /** @var RollbacksRedemption */ + private $rollbacksRedemption; + + /** @var FetchesVoucherRedemption */ + private $fetchesVoucherRedemption; + + /** @var CreatesVoucherRedemption */ + private $createsVoucherRedemption; /** * SuspendTransactionLogic constructor. * @param FetchesTransaction $fetchesTransaction * @param UpdatesTransactionStatus $updatesTransactionStatus */ - public function __construct(FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus) + public function __construct(FetchesTransaction $fetchesTransaction, UpdatesTransactionStatus $updatesTransactionStatus, RollbacksRedemption $rollbacksRedemption, FetchesVoucherRedemption $fetchesVoucherRedemption, CreatesVoucherRedemption $createsVoucherRedemption) { $this->fetchesTransaction = $fetchesTransaction; $this->updatesTransactionStatus = $updatesTransactionStatus; + $this->rollbacksRedemption = $rollbacksRedemption; + $this->fetchesVoucherRedemption = $fetchesVoucherRedemption; + $this->createsVoucherRedemption = $createsVoucherRedemption; } @@ -52,6 +66,14 @@ class SuspendTransactionLogic extends AbstractControllerLogic $this->updatesTransactionStatus->execute($transaction, ApprovalStatus::SUSPENDED); + if($transaction->voucherRedemption) { + $result = $this->rollbacksRedemption->execute($transaction->voucherRedemption->redemption_id); + if($result){ + $redemptionId = $result->id; + $this->createsVoucherRedemption->execute($transaction, $transaction->voucherRedemption->voucher, $redemptionId, $transaction->voucherRedemption->value); + } + } + return $this->response([]); } diff --git a/app/Classes/Modules/Vouchers/ControllersLogic/RedeemVoucherLogic.php b/app/Classes/Modules/Vouchers/ControllersLogic/RedeemVoucherLogic.php new file mode 100644 index 00000000..58980b64 --- /dev/null +++ b/app/Classes/Modules/Vouchers/ControllersLogic/RedeemVoucherLogic.php @@ -0,0 +1,48 @@ + 'Redeem Voucher', + 'message' => 'You have successfully redeemed a voucher' + ]; + } + + /** @var RedeemsVoucher */ + private $redeemsVoucher; + + /** + * RedeemVoucherLogic constructor. + */ + public function __construct(RedeemsVoucher $redeemVoucher) + { + $this->redeemsVoucher = $redeemVoucher; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws MalformedRequestException + */ + public function logic(Request $request) : JsonResponse + { + $result = $this->redeemsVoucher->execute($request->input('promoCode'), $request->input('amount')); + return $this->response(['data' => $result]); + } + + +} diff --git a/app/Classes/Modules/Vouchers/ControllersLogic/ValidateVoucherLogic.php b/app/Classes/Modules/Vouchers/ControllersLogic/ValidateVoucherLogic.php new file mode 100644 index 00000000..b3e1ff7d --- /dev/null +++ b/app/Classes/Modules/Vouchers/ControllersLogic/ValidateVoucherLogic.php @@ -0,0 +1,48 @@ + 'Fetch Voucher', + 'message' => 'You have successfully fetched a voucher' + ]; + } + + /** @var ValidatesVoucher */ + private $validatesVoucher; + + /** + * ValidateVoucherLogic constructor. + */ + public function __construct(ValidatesVoucher $validatesVoucher) + { + $this->validatesVoucher = $validatesVoucher; + } + + /** + * @param Request $request + * @return JsonResponse + * @throws MalformedRequestException + */ + public function logic(Request $request) : JsonResponse + { + $result = $this->validatesVoucher->execute($request->input('voucherCode'), null, null); + return $this->response(['data' => $result]); + } + + +} diff --git a/app/Classes/Modules/Vouchers/DataTransferObjects/ValidatedVoucherObject.php b/app/Classes/Modules/Vouchers/DataTransferObjects/ValidatedVoucherObject.php new file mode 100644 index 00000000..c3e766c2 --- /dev/null +++ b/app/Classes/Modules/Vouchers/DataTransferObjects/ValidatedVoucherObject.php @@ -0,0 +1,82 @@ +voucher_name = $voucher_name; + $this->code = $code; + $this->discount_type = $discount_type; + $this->total_discount_amount = $total_discount_amount; + $this->total_amount = $total_amount; + } + + /** + * @return string + */ + public function getVoucherName(): string + { + return $this->voucher_name; + } + + /** + * @return string + */ + public function getCode(): string + { + return $this->code; + } + + /** + * @return string + */ + public function getDiscountType(): string + { + return $this->discount_type; + } + + /** + * @return float + */ + public function getTotalDiscountAmount(): float + { + return $this->total_discount_amount / 100; + } + + /** + * @return float + */ + public function getTotalAmount(): float + { + return $this->total_amount / 100; + } + +} diff --git a/app/Classes/Modules/Vouchers/DataTransferObjects/VoucherObject.php b/app/Classes/Modules/Vouchers/DataTransferObjects/VoucherObject.php new file mode 100644 index 00000000..24b01612 --- /dev/null +++ b/app/Classes/Modules/Vouchers/DataTransferObjects/VoucherObject.php @@ -0,0 +1,70 @@ +code = $code; + $this->name = $name; + $this->type = $type; + $this->value = $value; + } + + /** + * @return string + */ + public function getCode(): string + { + return $this->code; + } + + /** + * @return string + */ + public function getName(): ?string + { + return $this->name; + } + + + /** + * @return string + */ + public function getType(): ?string + { + return $this->type; + } + + + /** + * @return float + */ + public function getValue(): ?float + { + return $this->value; + } + +} diff --git a/app/Classes/Modules/Vouchers/Services/CheckIfVoucherExists.php b/app/Classes/Modules/Vouchers/Services/CheckIfVoucherExists.php new file mode 100644 index 00000000..9bd38dfd --- /dev/null +++ b/app/Classes/Modules/Vouchers/Services/CheckIfVoucherExists.php @@ -0,0 +1,27 @@ +repository = $repository; + } + + public function execute(string $code): bool { + return $this->repository->where('code', $code)->exists(); + } + +} diff --git a/app/Classes/Modules/Vouchers/Services/CreatesVoucher.php b/app/Classes/Modules/Vouchers/Services/CreatesVoucher.php new file mode 100644 index 00000000..26fb8bfa --- /dev/null +++ b/app/Classes/Modules/Vouchers/Services/CreatesVoucher.php @@ -0,0 +1,40 @@ +voucherExists = $voucherExists; + } + + /** + * @param VoucherObject $object + * @return \Illuminate\Database\Eloquent\Model + * @throws \App\Classes\Exceptions\MalformedRequestException + */ + public function execute(VoucherObject $object) { + if(!$this->voucherExists->execute($object->getCode())) + { + $model = new Voucher(); + $model->code = $object->getCode(); + $model->name = $object->getName(); + $model->type = $object->getType(); + $model->value = $object->getValue(); + return $this->handler($model); + } + return null; + } +} diff --git a/app/Classes/Modules/Vouchers/Services/CreatesVoucherRedemption.php b/app/Classes/Modules/Vouchers/Services/CreatesVoucherRedemption.php new file mode 100644 index 00000000..c032031e --- /dev/null +++ b/app/Classes/Modules/Vouchers/Services/CreatesVoucherRedemption.php @@ -0,0 +1,28 @@ +voucher_id = $voucher->id; + $model->redemption_id = $redemptionId; + $model->value = $value; + + return $this->handler($transaction->voucherRedemption(), $model); + } +} diff --git a/app/Classes/Modules/Vouchers/Services/FetchesVoucher.php b/app/Classes/Modules/Vouchers/Services/FetchesVoucher.php new file mode 100644 index 00000000..4abc873a --- /dev/null +++ b/app/Classes/Modules/Vouchers/Services/FetchesVoucher.php @@ -0,0 +1,34 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Vouchers/Services/FetchesVoucherRedemption.php b/app/Classes/Modules/Vouchers/Services/FetchesVoucherRedemption.php new file mode 100644 index 00000000..0efc163d --- /dev/null +++ b/app/Classes/Modules/Vouchers/Services/FetchesVoucherRedemption.php @@ -0,0 +1,34 @@ +repository = $repository; + } + + + /** + * @return Builder + */ + public function getRepository(): Builder + { + return $this->repository->newQuery(); + } +} diff --git a/app/Classes/Modules/Vouchers/Services/RedeemsVoucher.php b/app/Classes/Modules/Vouchers/Services/RedeemsVoucher.php new file mode 100644 index 00000000..aaf4d39a --- /dev/null +++ b/app/Classes/Modules/Vouchers/Services/RedeemsVoucher.php @@ -0,0 +1,48 @@ +voucherifyClient = createVoucherifyClient(); + } + + /** + * @param string $promoCode + * @param string $amount + * @param object $employee + * @return null|object + * @throws ErrorException + */ + public function execute(string $promoCode, string $amount, object $employee) + { + try { + $result = $this->voucherifyClient->redemptions->redeem($promoCode, [ + "customer" => [ + // "id" => $employee->id, + "name" => $employee->name, + "email" => $employee->email, + ], + "order" => [ + "amount" => $amount * 100 //converting it to cents + ] + ]); + return $result; + } catch (\Voucherify\ClientException $e) { + throw $e; + } + } +} diff --git a/app/Classes/Modules/Vouchers/Services/RollbacksRedemption.php b/app/Classes/Modules/Vouchers/Services/RollbacksRedemption.php new file mode 100644 index 00000000..bd8f6bb2 --- /dev/null +++ b/app/Classes/Modules/Vouchers/Services/RollbacksRedemption.php @@ -0,0 +1,39 @@ +voucherifyClient = createVoucherifyClient(); + } + + /** + * @param string $redemptionId + * @return null|object + * @throws ErrorException + */ + public function execute(string $redemptionId) + { + try { + $result = $this->voucherifyClient->redemptions->rollback($redemptionId); + return $result; + } catch (\Voucherify\ClientException $e) { + Log::error('RollbacksRedemption error:' . $e); + return null; + } + } +} diff --git a/app/Classes/Modules/Vouchers/Services/ValidatesVoucher.php b/app/Classes/Modules/Vouchers/Services/ValidatesVoucher.php new file mode 100644 index 00000000..7705e22e --- /dev/null +++ b/app/Classes/Modules/Vouchers/Services/ValidatesVoucher.php @@ -0,0 +1,64 @@ +voucherifyClient = createVoucherifyClient(); + } + + /** + * @param string $voucherCode + * @param float $amount + * @param object $employee + * @return null|object + * @throws ErrorException + */ + public function execute(string $voucherCode, ?float $amount, ?object $employee) + { + try { + if($amount){ + $result = $this->voucherifyClient->validations->validateVoucher($voucherCode, [ + "customer" => [ + // "id" => $employee->id, + "name" => $employee->name, + "email" => $employee->email, + ], + "order" => [ + "amount" => $amount * 100 //converting it to cents + ] + ]); + } + else{ + $res = $this->voucherifyClient->validations->validateVoucher($voucherCode); + $result = [ + "valid" => $res->valid, + "code" => $res->code + ]; + if (isset($res->reason)) { + $result["reason"] = $res->reason; + } + } + + return $result; + } catch (\Voucherify\ClientException $e) { + // throw $e; + Log::error('ValidatesVoucher error:' . $e); + return null; + } + } +} diff --git a/app/Http/Controllers/Vouchers/RedeemVoucherController.php b/app/Http/Controllers/Vouchers/RedeemVoucherController.php new file mode 100644 index 00000000..8657846f --- /dev/null +++ b/app/Http/Controllers/Vouchers/RedeemVoucherController.php @@ -0,0 +1,20 @@ +execute($request); + } + +} diff --git a/app/Http/Controllers/Vouchers/ValidateVoucherController.php b/app/Http/Controllers/Vouchers/ValidateVoucherController.php new file mode 100644 index 00000000..86c4c4e1 --- /dev/null +++ b/app/Http/Controllers/Vouchers/ValidateVoucherController.php @@ -0,0 +1,20 @@ +execute($request); + } + +} diff --git a/app/Http/Resources/TransactionResource.php b/app/Http/Resources/TransactionResource.php index e35dcc36..27fd17ab 100644 --- a/app/Http/Resources/TransactionResource.php +++ b/app/Http/Resources/TransactionResource.php @@ -48,7 +48,8 @@ class TransactionResource extends JsonResource 'interval' => [ 'value' => $days->gt(Carbon::now()) ? '+' : '-', 'duration' => $days->diff(Carbon::now())->format('%d'), - ] + ], + 'redemption' => new VoucherRedemptionResource($this->voucherRedemption) ]; } } diff --git a/app/Http/Resources/VoucherRedemptionResource.php b/app/Http/Resources/VoucherRedemptionResource.php new file mode 100644 index 00000000..16ae693f --- /dev/null +++ b/app/Http/Resources/VoucherRedemptionResource.php @@ -0,0 +1,25 @@ + $this->id, + 'voucher_id' => $this->voucher_id, + 'transaction_id' => $this->transaction_id, + 'redemption_id' => $this->redemption_id, + 'value' => (float) $this->value + ]; + } +} diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 0df23186..b61d30c7 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -112,6 +112,14 @@ class Transaction extends AbstractModel implements Documentable, Transactionable return $this->HasOne(GroupTransaction::class, 'transaction_id'); } + /** + * @return HasOne + */ + public function voucherRedemption(): HasOne + { + return $this->HasOne(VoucherRedemption::class, 'transaction_id', 'id'); + } + public function convert_original_amount() { if($this->booking()->first()->fix_currency_id !== 1) { diff --git a/app/Models/User.php b/app/Models/User.php index 6fd8b6b0..6f33fac5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\SoftDeletes; use Spatie\Permission\Traits\HasRoles; @@ -65,4 +66,12 @@ class User extends AbstractModel implements { return $this->belongsToMany(Company::class, (new Employee())->getTable(), 'user_id', 'company_id'); } + + /** + * @return HasMany + */ + public function redemptions(): HasMany + { + return $this->HasMany(VoucherRedemption::class, 'user_id', 'id'); + } } diff --git a/app/Models/Voucher.php b/app/Models/Voucher.php new file mode 100644 index 00000000..b8df7dca --- /dev/null +++ b/app/Models/Voucher.php @@ -0,0 +1,19 @@ +HasMany(VoucherRedemption::class, 'voucher_id', 'id'); + } +} diff --git a/app/Models/VoucherRedemption.php b/app/Models/VoucherRedemption.php new file mode 100644 index 00000000..aa37487b --- /dev/null +++ b/app/Models/VoucherRedemption.php @@ -0,0 +1,36 @@ +BelongsTo(Voucher::class, 'voucher_id', 'id'); + } + + /** + * @return BelongsTo + */ + public function transaction(): BelongsTo + { + return $this->BelongsTo(Transaction::class, 'transaction_id', 'id'); + } + + /** + * @return BelongsTo + */ + public function user(): BelongsTo + { + return $this->BelongsTo(User::class, 'user_id', 'id'); + } +} diff --git a/composer.json b/composer.json index 349234b1..47acc131 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "maatwebsite/excel": "^3.1", "mpdf/mpdf": "^8.1", "rinvex/countries": "^6.1", + "rspective/voucherify": " v2.0.*", "smalot/pdfparser": "^2.2", "spatie/laravel-activitylog": "^3.14", "spatie/laravel-permission": "^3.17", @@ -57,6 +58,9 @@ "classmap": [ "database/seeds", "database/factories" + ], + "files": [ + "app/Classes/General/VoucherifyHelper.php" ] }, "autoload-dev": { diff --git a/config/voucherify.php b/config/voucherify.php new file mode 100644 index 00000000..c97fab5b --- /dev/null +++ b/config/voucherify.php @@ -0,0 +1,8 @@ + env('VOUCHERIFY_APPLICATION_ID', ''), + 'client_secret_key' => env('VOUCHERIFY_CLIENT_SECRET_KEY', ''), + 'version' => env('VOUCHERIFY_VERSION', ''), + 'url' => env('VOUCHERIFY_URL', ''), +]; diff --git a/database/migrations/2023_05_27_050522_create_vouchers_table.php b/database/migrations/2023_05_27_050522_create_vouchers_table.php new file mode 100644 index 00000000..a769a699 --- /dev/null +++ b/database/migrations/2023_05_27_050522_create_vouchers_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('name')->nullable(); + $table->string('code'); + $table->string('type')->nullable(); + $table->decimal('value', 8, 2)->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('vouchers'); + } +} diff --git a/database/migrations/2023_05_27_050523_create_voucher_redemptions_table.php b/database/migrations/2023_05_27_050523_create_voucher_redemptions_table.php new file mode 100644 index 00000000..7e46bc98 --- /dev/null +++ b/database/migrations/2023_05_27_050523_create_voucher_redemptions_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('voucher_id'); + $table->unsignedBigInteger('transaction_id'); + $table->string('redemption_id'); + $table->decimal('value', 8, 2)->nullable(); + // $table->unsignedBigInteger('user_id'); + $table->timestamps(); + + // Define foreign key constraints + $table->foreign('voucher_id')->references('id')->on('vouchers'); + $table->foreign('transaction_id')->references('id')->on('transactions'); + // $table->foreign('user_id')->references('id')->on('users'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('voucher_redemptions'); + } +} diff --git a/resources/assets/vue/components/bookings/elements/BookingConfirmationComponent.vue b/resources/assets/vue/components/bookings/elements/BookingConfirmationComponent.vue index d0bd12f3..68db519b 100644 --- a/resources/assets/vue/components/bookings/elements/BookingConfirmationComponent.vue +++ b/resources/assets/vue/components/bookings/elements/BookingConfirmationComponent.vue @@ -269,12 +269,12 @@
{{(Math.round((this.parameters.calculation.total + Number.EPSILON) * 100) / 100).toFixed(2)}}
-
+
@@ -320,7 +320,7 @@ account_no: '', step:1, promoCode:"" - + } }, computed: { @@ -350,7 +350,8 @@ this.createBank = false; this.account_no = ''; this.parameters.bankAccount = {}; - }, + } + /* applyPromocoe(){ let promoRequestObj = { category : 'New cat 4', @@ -370,17 +371,17 @@ } }; this.isLoading = true; - + let url = `https://as1.api.voucherify.io/v1/vouchers/${this.promoCode}/redemption`; return fetch(url, { method: 'POST', responseType: 'json', - body: promoRequestObj, + body: JSON.stringify(promoRequestObj), headers: { 'content-type': 'application/json', - 'X-App-Token': '09a22aa9-f287-4813-9c65-6baa43091a6e', - 'X-App-Id': 'e12347c6-b072-4318-9a2b-179a7a3b1414' + 'X-App-Token': 'b839bfa1-d8c1-4846-a367-05d466f48cfd', + 'X-App-Id': 'a180e3cb-ee34-469b-876f-d4a1139b82c6' } }).then(response => { @@ -394,6 +395,7 @@ }) } + */ }, mixins: [FormHandler] } diff --git a/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue b/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue index 702745c7..6a7aefcb 100644 --- a/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue +++ b/resources/assets/vue/components/bookings/elements/PaymentHistoryComponent.vue @@ -150,6 +150,17 @@
MYR {{(Math.round((item.service_charge + Number.EPSILON) * 100) / 100).toFixed(2)}}
+
+
+
Voucher
+
+
+
- MYR {{(Math.round((item.redemption.value + Number.EPSILON) * 100) / 100).toFixed(2)}}
+
+
+
- MYR 0.00
+
+
Tax
@@ -285,7 +296,7 @@ this.data.transaction_refunds.forEach(function(refunds) { TotalRequestedRefund += refunds.status === 1 ? refunds.original_amount : 0; }); - + return TotalRequestedRefund; }, totalRequestedConvertRefund() { diff --git a/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationComponent.vue b/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationComponent.vue index 37a51a43..fcaf8390 100644 --- a/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationComponent.vue +++ b/resources/assets/vue/components/bookings/forms/BookingPaymentQuotationComponent.vue @@ -224,7 +224,29 @@
-
+
+
+ + + + +
+
+
+
+ +
+
+
+
+
+ + {{ voucherCodeFailedReason }} + Voucher applied +
+
@@ -406,7 +428,7 @@
- +
@@ -468,6 +490,17 @@
MYR {{(Math.round((calculation.sub_total + Number.EPSILON) * 100) / 100).toFixed(2)}}
+
+
+
Voucher
+
+
+
- MYR {{(Math.round((calculation.voucher_discount_amount + Number.EPSILON) * 100) / 100).toFixed(2)}}
+
+
+
- MYR 0.00
+
+
Tax {{(Math.round((calculation.tax + Number.EPSILON) * 10) / 10).toFixed(1)}}%
@@ -540,12 +573,16 @@ id: '', status: false }, - onlinePayment: { + onlinePayment: { id: '', status: false }, amount: (Math.round((this.data.outstanding_amount + Number.EPSILON) * 100) / 100).toFixed(2), - calculation: null + calculation: null, + voucherCode: '', + voucherCodeFailedReason: '', + voucherValidated: false, + voucherIsChecking: false } }, validations () { @@ -580,6 +617,7 @@ submitForm(){ this.parameters = { payment_method: this.paymentMethod.id, + voucherCode: this.voucherCode, amount: this.amount }; @@ -587,7 +625,22 @@ this.calculation = null; }, successHandler(response){ - this.calculation = response.payload.data; + if(!response.payload.data){ + this.voucherValidated = false; + this.voucherIsChecking = false; + this.voucherCodeFailedReason = 'Something went wrong. Please contact customer service.' + } + else if(response.payload.data.valid !== undefined && response.payload.data.code){ //applyVoucherCode() + this.voucherValidated = true; + if(response.payload.data.reason){ + this.voucherCodeFailedReason = response.payload.data.reason; + this.voucherValidated = false; + } + this.voucherIsChecking = false; + } + else { //submitForm() + this.calculation = response.payload.data; + } }, errorHandler(error) { this.error = error.message; @@ -600,9 +653,30 @@ cancelQuotation(){ this.calculation = null; this.expandPayment = false; + }, + applyVoucherCode(){ + this.voucherIsChecking = true; + this.voucherCodeFailedReason = ''; + this.voucherValidated = false; + this.parameters = { + voucherCode: this.voucherCode, + amount: this.amount + }; + if(this.voucherCode.trim() !== ''){ + this.submit(route('api.voucher.validate'), 'post', this.section, false, false); + } + else{ + this.voucherValidated = true; + this.voucherIsChecking = false; + } + }, + updateVoucherFlag(event){ + this.voucherCodeFailedReason = ''; + this.voucherValidated = false; + this.voucherCode = event.target.value; } }, mixins: [componentHandler], directives: {money: VMoney} } - \ No newline at end of file + diff --git a/resources/assets/vue/components/bookings/forms/ConfirmQuotationFormComponent.vue b/resources/assets/vue/components/bookings/forms/ConfirmQuotationFormComponent.vue index db136e28..975c4592 100644 --- a/resources/assets/vue/components/bookings/forms/ConfirmQuotationFormComponent.vue +++ b/resources/assets/vue/components/bookings/forms/ConfirmQuotationFormComponent.vue @@ -120,6 +120,8 @@ payment_method: this.payment_method, amount: this.amount, bank_code: this.bank_code, + voucher_code: this.calculation.voucher_code, + voucher_discount_amount: this.calculation.voucher_discount_amount } } }, @@ -143,4 +145,4 @@ \ No newline at end of file + diff --git a/routes/api.php b/routes/api.php index b3e0002c..b3054bd2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -55,6 +55,8 @@ Route::group(['middleware' => 'api', 'prefix' => 'v1', 'as' => 'api.'], function require __DIR__ . '/wallet.php'; + require __DIR__ . '/voucher.php'; + // require __DIR__ . '/rate.php'; // require __DIR__ . '/receipt.php'; diff --git a/routes/voucher.php b/routes/voucher.php new file mode 100644 index 00000000..5b0f3b29 --- /dev/null +++ b/routes/voucher.php @@ -0,0 +1,8 @@ + 'voucher', 'as' => 'voucher.', 'namespace' => 'Vouchers'], function () { + Route::post('fetch', 'ValidateVoucherController@validate')->name('validate'); + Route::post('redeem', 'RedeemVoucherController@redeem')->name('redeem'); +});