Skip to content

Commit

Permalink
Merge pull request #67 from xendit/TPI-2484/fix-callback
Browse files Browse the repository at this point in the history
improve callback to check order number from source of truth
  • Loading branch information
IreneGohtami authored Dec 11, 2020
2 parents c93ff45 + 2197a39 commit 70be897
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 90 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## 2.4.3 (2020-12-10)

Improvements:

- Improve callback endpoint security to check order number from source of truth

## 2.4.2 (2020-11-23)

Improvements:
Expand Down
96 changes: 64 additions & 32 deletions Xendit/M2Invoice/Controller/Checkout/CCCallback.m22.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,35 @@
use Magento\Sales\Model\Order;
use Xendit\M2Invoice\Enum\LogDNALevel;

/**
* This callback is only for order in multishipping flow. For order
* created in onepage checkout is handled in ProcessHosted.php
*/
class CCCallback extends ProcessHosted
{
public function execute()
{
try {
$orderIds = explode('-', $this->getRequest()->getParam('order_ids'));
$post = $this->getRequest()->getContent();
$callbackPayload = json_decode($post, true);

if (
!isset($callbackPayload['id']) ||
!isset($callbackPayload['hp_token']) ||
!isset($callbackPayload['order_number'])
) {
$result = $this->getJsonResultFactory()->create();
$result->setHttpResponseCode(\Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST);
$result->setData([
'status' => __('ERROR'),
'message' => 'Callback body is invalid'
]);

return $result;
}
$orderIds = explode('-', $callbackPayload['order_number']);
$hostedPaymentId = $callbackPayload['id'];
$hostedPaymentToken = $callbackPayload['hp_token'];

$shouldRedirect = false;
$isError = false;
Expand All @@ -23,40 +46,49 @@ public function execute()

$payment = $order->getPayment();

if ($payment->getAdditionalInformation('xendit_hosted_payment_id') !== null) {
$requestData = [
'id' => $payment->getAdditionalInformation('xendit_hosted_payment_id'),
'hp_token' => $payment->getAdditionalInformation('xendit_hosted_payment_token')
];

if ($flag) { // complete hosted payment only once as status will be changed to USED
$hostedPayment = $this->getCompletedHostedPayment($requestData);
$flag = false;
}

if (isset($hostedPayment['error_code'])) {
$isError = true;
$requestData = [
'id' => $hostedPaymentId,
'hp_token' => $hostedPaymentToken
];

if ($flag) { // complete hosted payment only once as status will be changed to USED
$hostedPayment = $this->getCompletedHostedPayment($requestData);
$flag = false;
}

if (isset($hostedPayment['error_code'])) {
$isError = true;
}
else {
if ($hostedPayment['order_number'] !== $callbackPayload['order_number']) {
$result = $this->getJsonResultFactory()->create();
$result->setHttpResponseCode(\Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST);
$result->setData([
'status' => __('ERROR'),
'message' => 'Hosted payment is not for this order'
]);

return $result;
}
else {
if ($hostedPayment['paid_amount'] != $hostedPayment['amount']) {
$order->setBaseDiscountAmount($hostedPayment['paid_amount'] - $hostedPayment['amount']);
$order->setDiscountAmount($hostedPayment['paid_amount'] - $hostedPayment['amount']);
$order->save();

$order->setBaseGrandTotal($order->getBaseGrandTotal() + $order->getBaseDiscountAmount());
$order->setGrandTotal($order->getGrandTotal() + $order->getDiscountAmount());
$order->save();
}
$payment->setAdditionalInformation('token_id', $hostedPayment['token_id']);
$payment->setAdditionalInformation('xendit_installment', $hostedPayment['installment']);

if ($hostedPayment['paid_amount'] != $hostedPayment['amount']) {
$order->setBaseDiscountAmount($hostedPayment['paid_amount'] - $hostedPayment['amount']);
$order->setDiscountAmount($hostedPayment['paid_amount'] - $hostedPayment['amount']);
$order->save();

$this->processSuccessfulTransaction(
$order,
$payment,
'Xendit Credit Card payment completed. Transaction ID: ',
$hostedPayment['charge_id']
);
$order->setBaseGrandTotal($order->getBaseGrandTotal() + $order->getBaseDiscountAmount());
$order->setGrandTotal($order->getGrandTotal() + $order->getDiscountAmount());
$order->save();
}
$payment->setAdditionalInformation('token_id', $hostedPayment['token_id']);
$payment->setAdditionalInformation('xendit_installment', $hostedPayment['installment']);

$this->processSuccessfulTransaction(
$order,
$payment,
'Xendit Credit Card payment completed. Transaction ID: ',
$hostedPayment['charge_id']
);
}
}

Expand Down
96 changes: 64 additions & 32 deletions Xendit/M2Invoice/Controller/Checkout/CCCallback.m23.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,35 @@
use Magento\Sales\Model\Order;
use Xendit\M2Invoice\Enum\LogDNALevel;

/**
* This callback is only for order in multishipping flow. For order
* created in onepage checkout is handled in ProcessHosted.php
*/
class CCCallback extends ProcessHosted implements CsrfAwareActionInterface
{
public function execute()
{
try {
$orderIds = explode('-', $this->getRequest()->getParam('order_ids'));
$post = $this->getRequest()->getContent();
$callbackPayload = json_decode($post, true);

if (
!isset($callbackPayload['id']) ||
!isset($callbackPayload['hp_token']) ||
!isset($callbackPayload['order_number'])
) {
$result = $this->getJsonResultFactory()->create();
$result->setHttpResponseCode(\Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST);
$result->setData([
'status' => __('ERROR'),
'message' => 'Callback body is invalid'
]);

return $result;
}
$orderIds = explode('-', $callbackPayload['order_number']);
$hostedPaymentId = $callbackPayload['id'];
$hostedPaymentToken = $callbackPayload['hp_token'];

$shouldRedirect = false;
$isError = false;
Expand All @@ -27,40 +50,49 @@ public function execute()

$payment = $order->getPayment();

if ($payment->getAdditionalInformation('xendit_hosted_payment_id') !== null) {
$requestData = [
'id' => $payment->getAdditionalInformation('xendit_hosted_payment_id'),
'hp_token' => $payment->getAdditionalInformation('xendit_hosted_payment_token')
];

if ($flag) { // complete hosted payment only once as status will be changed to USED
$hostedPayment = $this->getCompletedHostedPayment($requestData);
$flag = false;
}

if (isset($hostedPayment['error_code'])) {
$isError = true;
$requestData = [
'id' => $hostedPaymentId,
'hp_token' => $hostedPaymentToken
];

if ($flag) { // complete hosted payment only once as status will be changed to USED
$hostedPayment = $this->getCompletedHostedPayment($requestData);
$flag = false;
}

if (isset($hostedPayment['error_code'])) {
$isError = true;
}
else {
if ($hostedPayment['order_number'] !== $callbackPayload['order_number']) {
$result = $this->getJsonResultFactory()->create();
$result->setHttpResponseCode(\Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST);
$result->setData([
'status' => __('ERROR'),
'message' => 'Hosted payment is not for this order'
]);

return $result;
}
else {
if ($hostedPayment['paid_amount'] != $hostedPayment['amount']) {
$order->setBaseDiscountAmount($hostedPayment['paid_amount'] - $hostedPayment['amount']);
$order->setDiscountAmount($hostedPayment['paid_amount'] - $hostedPayment['amount']);
$order->save();

$order->setBaseGrandTotal($order->getBaseGrandTotal() + $order->getBaseDiscountAmount());
$order->setGrandTotal($order->getGrandTotal() + $order->getDiscountAmount());
$order->save();
}
$payment->setAdditionalInformation('token_id', $hostedPayment['token_id']);
$payment->setAdditionalInformation('xendit_installment', $hostedPayment['installment']);

if ($hostedPayment['paid_amount'] != $hostedPayment['amount']) {
$order->setBaseDiscountAmount($hostedPayment['paid_amount'] - $hostedPayment['amount']);
$order->setDiscountAmount($hostedPayment['paid_amount'] - $hostedPayment['amount']);
$order->save();

$this->processSuccessfulTransaction(
$order,
$payment,
'Xendit Credit Card payment completed. Transaction ID: ',
$hostedPayment['charge_id']
);
$order->setBaseGrandTotal($order->getBaseGrandTotal() + $order->getBaseDiscountAmount());
$order->setGrandTotal($order->getGrandTotal() + $order->getDiscountAmount());
$order->save();
}
$payment->setAdditionalInformation('token_id', $hostedPayment['token_id']);
$payment->setAdditionalInformation('xendit_installment', $hostedPayment['installment']);

$this->processSuccessfulTransaction(
$order,
$payment,
'Xendit Credit Card payment completed. Transaction ID: ',
$hostedPayment['charge_id']
);
}
}

Expand Down
35 changes: 24 additions & 11 deletions Xendit/M2Invoice/Controller/Checkout/Notification.m22.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,11 @@ public function handleEwalletCallback($callbackPayload) {
if (isset($callbackPayload['failure_code'])) {
$failureCode = $callbackPayload['failure_code'];
}
$prefix = $this->dataHelper->getExternalIdPrefix();
$trimmedExternalId = str_replace($prefix . "-", "", $callbackPayload['external_id']);
$order = $this->getOrderById($trimmedExternalId);

$temp = explode('-', $callbackPayload['external_id']);
$orderId = end($temp);
$order = $this->getOrderById($orderId);

return $this->checkOrder($order, true, $callbackPayload, null, $orderId);
return $this->checkOrder($order, true, $callbackPayload, null, $trimmedExternalId);
}

private function checkOrder($order, $isEwallet, $callbackPayload, $invoice, $callbackDescription) {
Expand Down Expand Up @@ -184,7 +183,20 @@ private function checkOrder($order, $isEwallet, $callbackPayload, $invoice, $cal
}

if ($isEwallet) {
$paymentStatus = $this->getEwalletStatus($callbackPayload['ewallet_type'], $callbackPayload['external_id']);
$ewallet = $this->getEwallet($callbackPayload['ewallet_type'], $callbackPayload['external_id']);
$paymentStatus = $ewallet['status'];

if ($ewallet['external_id'] !== $callbackPayload['external_id']) {
$result = $this->jsonResultFactory->create();
/** You may introduce your own constants for this custom REST API */
$result->setHttpResponseCode(\Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST);
$result->setData([
'status' => __('ERROR'),
'message' => 'Ewallet is not for this order'
]);

return $result;
}
} else {
$paymentStatus = $invoice['status'];

Expand Down Expand Up @@ -272,25 +284,26 @@ private function getXenditInvoice($invoiceId)
return $invoice;
}

private function getEwalletStatus($ewalletType, $externalId)
private function getEwallet($ewalletType, $externalId)
{
$ewalletUrl = $this->dataHelper->getCheckoutUrl() . "/payment/xendit/ewallets?ewallet_type=".$ewalletType."&external_id=".$externalId;
$ewalletMethod = \Zend\Http\Request::METHOD_GET;

try {
$response = $this->apiHelper->request($ewalletUrl, $ewalletMethod);
} catch (\Magento\Framework\Exception\LocalizedException $e) {
throw new \Magento\Framework\Exception\LocalizedException(
throw new LocalizedException(
new Phrase($e->getMessage())
);
}

$status = $response['status'];
$statusList = array("COMPLETED", "PAID", "SUCCESS_COMPLETED"); //OVO, DANA, LINKAJA
if (in_array($response['status'], $statusList)) {
return "COMPLETED";
if (in_array($status, $statusList)) {
$response['status'] = "COMPLETED";
}

return $response['status'];
return $response;
}

private function invoiceOrder($order, $transactionId)
Expand Down
33 changes: 23 additions & 10 deletions Xendit/M2Invoice/Controller/Checkout/Notification.m23.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,11 @@ public function handleEwalletCallback($callbackPayload) {
if (isset($callbackPayload['failure_code'])) {
$failureCode = $callbackPayload['failure_code'];
}
$prefix = $this->dataHelper->getExternalIdPrefix();
$trimmedExternalId = str_replace($prefix . "-", "", $callbackPayload['external_id']);
$order = $this->getOrderById($trimmedExternalId);

$temp = explode('-', $callbackPayload['external_id']);
$orderId = end($temp);
$order = $this->getOrderById($orderId);

return $this->checkOrder($order, true, $callbackPayload, null, $orderId);
return $this->checkOrder($order, true, $callbackPayload, null, $trimmedExternalId);
}

private function checkOrder($order, $isEwallet, $callbackPayload, $invoice, $callbackDescription) {
Expand Down Expand Up @@ -195,7 +194,20 @@ private function checkOrder($order, $isEwallet, $callbackPayload, $invoice, $cal
}

if ($isEwallet) {
$paymentStatus = $this->getEwalletStatus($callbackPayload['ewallet_type'], $callbackPayload['external_id']);
$ewallet = $this->getEwallet($callbackPayload['ewallet_type'], $callbackPayload['external_id']);
$paymentStatus = $ewallet['status'];

if ($ewallet['external_id'] !== $callbackPayload['external_id']) {
$result = $this->jsonResultFactory->create();
/** You may introduce your own constants for this custom REST API */
$result->setHttpResponseCode(\Magento\Framework\Webapi\Exception::HTTP_BAD_REQUEST);
$result->setData([
'status' => __('ERROR'),
'message' => 'Ewallet is not for this order'
]);

return $result;
}
} else {
$paymentStatus = $invoice['status'];

Expand Down Expand Up @@ -283,7 +295,7 @@ private function getXenditInvoice($invoiceId)
return $invoice;
}

private function getEwalletStatus($ewalletType, $externalId)
private function getEwallet($ewalletType, $externalId)
{
$ewalletUrl = $this->dataHelper->getCheckoutUrl() . "/payment/xendit/ewallets?ewallet_type=".$ewalletType."&external_id=".$externalId;
$ewalletMethod = \Zend\Http\Request::METHOD_GET;
Expand All @@ -296,12 +308,13 @@ private function getEwalletStatus($ewalletType, $externalId)
);
}

$status = $response['status'];
$statusList = array("COMPLETED", "PAID", "SUCCESS_COMPLETED"); //OVO, DANA, LINKAJA
if (in_array($response['status'], $statusList)) {
return "COMPLETED";
if (in_array($status, $statusList)) {
$response['status'] = "COMPLETED";
}

return $response['status'];
return $response;
}

private function invoiceOrder($order, $transactionId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function execute()
$invoiceId = $payload['id'];
$chargeId = $payload['credit_card_charge_id'];

//verify callback
// verify callback to ensure payment exist in xendit side
$callback = $this->getCallbackByInvoiceId($invoiceId);
if (isset($callback['error_code']) || !isset($callback['status'])) {
$result->setData([
Expand Down
Loading

0 comments on commit 70be897

Please sign in to comment.