Skip to content

Commit

Permalink
Merge pull request #19 from xendit/TPI-2694/standardize-error
Browse files Browse the repository at this point in the history
Tpi 2694/standardize error
  • Loading branch information
candrasaputra authored Jan 19, 2021
2 parents ec7ffae + 8597500 commit 75485e1
Show file tree
Hide file tree
Showing 17 changed files with 467 additions and 63 deletions.
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# CHANGELOG

## 2020-12-18
## 1.3.0 (2021-01-19)
- Error message standardization

## 1.2.2 (2020-12-18)
- Do not show API key on admin page

## 2020-12-10
## 1.2.1 (2020-12-10)
- Improve callback endpoint security to check order number from source of truth

## 2020-07-02
## 1.2.0 (2020-07-02)
- Refactor xendit_order table for all versions
- Ensure all Xendit orders are recorded in DB
21 changes: 18 additions & 3 deletions opencart1.5.x/upload/catalog/controller/payment/xendit.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

class ControllerPaymentXendit extends Controller {
const EXT_ID_PREFIX = 'opencart-xendit-';
const MINIMUM_AMOUNT = 10000;

public function index() {
$this->load->language('payment/xendit');
Expand Down Expand Up @@ -32,9 +33,18 @@ public function process_payment() {
Xendit::set_public_key($api_key['public_key']);

$store_name = $this->config->get('config_name');
$amount = (int)$order['total'];

if ($amount < self::MINIMUM_AMOUNT) {
$json['error'] = 'The minimum amount for using this payment is IDR ' . self::MINIMUM_AMOUNT . '. Please put more item(s) to reach the minimum amount. Code: 100001';

$this->response->addHeader('Content-Type: application/json');
return $this->response->setOutput(json_encode($json));
}

$request_payload = array(
'external_id' => self::EXT_ID_PREFIX . $order_id,
'amount' => (int)$order['total'],
'amount' => $amount,
'payer_email' => $order['email'],
'description' => 'Payment for order #' . $order_id . ' at ' . $store_name,
'client_type' => 'INTEGRATION',
Expand All @@ -49,9 +59,14 @@ public function process_payment() {

try {
$response = Xendit::request($request_url, Xendit::METHOD_POST, $request_payload, $request_options);

if (isset($response['error_code'])) {
$json['error'] = $response['message'];
$message = $response['message'];

if (isset($response['code'])) {
$message .= " Code: " . $response['code'];
}
$json['error'] = $message;
}
else {
$this->model_payment_xendit->addOrder($order, $response, $this->config->get('payment_xendit_environment'), 'invoice');
Expand Down
83 changes: 74 additions & 9 deletions opencart1.5.x/upload/catalog/controller/payment/xenditcc.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

class ControllerPaymentXenditCC extends Controller {
const EXT_ID_PREFIX = 'opencart-xendit-';
const MINIMUM_AMOUNT = 5000;

public function index() {
$this->load->language('payment/xenditcc');
Expand Down Expand Up @@ -32,10 +33,19 @@ public function process_payment() {
);

$store_name = $this->config->get('config_name');
$amount = (int)$order['total'];

if ($amount < self::MINIMUM_AMOUNT) {
$json['error'] = 'The minimum amount for using this payment is IDR ' . self::MINIMUM_AMOUNT . '. Please put more item(s) to reach the minimum amount. Code: 100001';

$this->response->addHeader('Content-Type: application/json');
return $this->response->setOutput(json_encode($json));
}

$request_payload = array(
'external_id' => self::EXT_ID_PREFIX . $order_id,
'token_id' => $this->request->post['token_id'],
'amount' => (int)$order['total'],
'amount' => $amount,
'return_url' => $this->url->link('payment/xenditcc/process_3ds')
);
$request_url = '/payment/xendit/credit-card/hosted-3ds';
Expand All @@ -52,7 +62,12 @@ public function process_payment() {
$response = Xendit::request($request_url, Xendit::METHOD_POST, $request_payload, $request_options);

if (isset($response['error_code'])) {
$json['error'] = $response['message'];
$message = $response['message'];

if (isset($response['code'])) {
$message .= " Code: " . $response['code'];
}
$json['error'] = $message;
}
else {
$response['external_id'] = $request_payload['external_id']; //original response doesn't return external_id
Expand Down Expand Up @@ -90,8 +105,12 @@ public function process_3ds() {
Xendit::set_public_key($api_key['public_key']);

if (!isset($this->request->get['hosted_3ds_id'])) {
$message = 'Empty authentication. Cancelling order.';
$message = $this->map_failure_reason('AUTHENTICATION_FAILED');
$this->cancel_order($order_id, $message);

$redir_url = $this->url->link('extension/payment/xenditcc/failure', 'message=' . urlencode($message), 'SSL');
$this->response->redirect($redir_url);
return;
}

$hosted_3ds_id = $this->request->get['hosted_3ds_id'];
Expand All @@ -105,16 +124,23 @@ public function process_3ds() {
'should_use_public_key' => true
)
);

if (isset($hosted_3ds['error_code'])) {
$redir_url = $this->url->link('payment/xenditcc/failure');
$message = $this->map_failure_reason('AUTHENTICATION_FAILED');
$this->cancel_order($order_id, $message);

$redir_url = $this->url->link('extension/payment/xenditcc/failure', 'message=' . urlencode($message), 'SSL');
$this->response->redirect($redir_url);
return;
}

if ('VERIFIED' !== $hosted_3ds['status']) {
$message = 'Authentication failed. Cancelling order.';
$message = $this->map_failure_reason('AUTHENTICATION_FAILED');
$this->cancel_order($order_id, $message);

$redir_url = $this->url->link('extension/payment/xenditcc/failure', 'message=' . urlencode($message), 'SSL');
$this->response->redirect($redir_url);
return;
}

$token_id = $hosted_3ds['token_id'];
Expand All @@ -138,6 +164,19 @@ public function process_3ds() {
)
);

if (isset($charge['error_code'])) {
$message = $charge['message'];

if (isset($charge['code'])) {
$message .= " Code: " . $charge['code'];
}
$this->cancel_order($order_id, $message);

$redir_url = $this->url->link('extension/payment/xenditcc/failure', 'message=' . urlencode($message), 'SSL');
$this->response->redirect($redir_url);
return;
}

$this->process_order($charge, $order_id, $charge_data);
} catch (Exception $e) {
$redir_url = $this->url->link('payment/xenditcc/failure');
Expand All @@ -152,6 +191,7 @@ public function failure() {
$this->document->setTitle($this->language->get('heading_title'));
$this->data['heading_title'] = $this->language->get('heading_title');
$this->data['text_failure'] = $this->language->get('text_failure');
$this->data['message'] = isset($this->request->get['message']) ? $this->request->get['message'] : 'We encountered an issue while processing the checkout. Please contact us. Code: 100007';

$this->data['column_left'] = $this->getChild('common/column_left');
$this->data['column_right'] = $this->getChild('common/column_right');
Expand All @@ -167,10 +207,10 @@ public function failure() {

private function process_order($charge, $order_id, $charge_data) {
if ($charge['status'] !== 'CAPTURED') {
$message = 'Charge failed. Cancelling order. Charge ID: ' . $charge['id'];
$message = $this->map_failure_reason($charge['failure_reason']);
$this->cancel_order($order_id, $message);

$redir_url = $this->url->link('payment/xenditcc/failure');
$redir_url = $this->url->link('extension/payment/xenditcc/failure', 'message=' . urlencode($message), 'SSL');
$this->response->redirect($redir_url);
return;
}
Expand Down Expand Up @@ -218,4 +258,29 @@ private function get_api_key() {
);
}
}

private function map_failure_reason($failure_reason) {
$card_declined_reason = 'Card declined by the issuer bank. Please try with another card or contact the bank directly.';
switch ($failure_reason) {
case 'CARD_DECLINED':
return $card_declined_reason . ' Code: 200011';
case 'STOLEN_CARD':
return $card_declined_reason . ' Code: 200013';
case 'INSUFFICIENT_BALANCE':
return 'Card declined due to insufficient balance. Ensure sufficient balance is available, or try another card. Code: 200012';
case 'INVALID_CVN':
return 'Card declined due to incorrect card details. Please try again. Code: 200015';
case 'INACTIVE_CARD':
return $card_declined_reason . ' Code: 200014';
case 'EXPIRED_CARD':
return 'Card declined due to expiration. Please try again with another card. Code: 200010';
case 'PROCESSOR_ERROR':
return 'We encountered an issue while processing the checkout. Please try again. Code: 200009';
case 'AUTHENTICATION_FAILED':
return 'The authentication process failed. Please try again. Code: 200001';
case 'UNEXPECTED_PLUGIN_ISSUE':
return 'We encountered an issue processing your checkout, please contact us. Code: 100007';
default: return $failure_reason;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,31 @@
is_multiple_use: true
};
// Validation
if (data.card_number == '') {
alert('Please fill in Credit Card Number.');
if (!data.card_number || !data.card_cvn || !data.card_exp_month || !data.card_exp_year) {
buttonConfirm.attr('disabled', false);
alert('Card information is incomplete. Please complete it and try again. Code: 200034');
return;
}
if (!Xendit.card.validateCardNumber(data.card_number)) {
buttonConfirm.attr('disabled', false);
alert('Invalid Card Number. Please make sure the card is Visa / Mastercard / JCB. Code: 200030');
return;
}
if (expMonth == '' || expYear == '') {
alert('Please fill in Card Expiry Month & Year.');
if (!Xendit.card.validateCvnForCardType(data.card_cvn, data.card_number)) {
buttonConfirm.attr('disabled', false);
alert('The CVC/CVN that you entered is less than 3 digits. Please enter the correct value and try again. Code: 200032');
return;
}
if (data.card_cvn == '') {
alert('Please fill in CVN.');
if (!Xendit.card.validateExpiry(data.card_exp_month, data.card_exp_year)) {
buttonConfirm.attr('disabled', false);
alert('The card expiry that you entered does not meet the expected format. Please try again by entering the 2 digits of the month (MM) and the last 2 digits of the year (YY). Code: 200031');
return;
}
Expand All @@ -107,7 +121,7 @@
if (err) {
buttonConfirm.attr('disabled', false);
alert('Tokenization error. Error code:' + err.error_code);
alert('We encountered an issue while processing the checkout. Please contact us. Code: 200035');
return;
}
Expand Down
21 changes: 18 additions & 3 deletions opencart2.0.x-2.2.x/upload/catalog/controller/payment/xendit.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
class Controllerpaymentxendit extends Controller
{
const EXT_ID_PREFIX = 'opencart-xendit-';
const MINIMUM_AMOUNT = 10000;

public function index()
{
Expand Down Expand Up @@ -41,9 +42,18 @@ public function process_payment()
Xendit::set_public_key($api_key['public_key']);

$store_name = $this->config->get('config_name');
$amount = (int)$order['total'];

if ($amount < self::MINIMUM_AMOUNT) {
$json['error'] = 'The minimum amount for using this payment is IDR ' . self::MINIMUM_AMOUNT . '. Please put more item(s) to reach the minimum amount. Code: 100001';

$this->response->addHeader('Content-Type: application/json');
return $this->response->setOutput(json_encode($json));
}

$request_payload = array(
'external_id' => self::EXT_ID_PREFIX . $order_id,
'amount' => (int) $order['total'],
'amount' => $amount,
'payer_email' => $order['email'],
'description' => 'Payment for order #' . $order_id . ' at ' . $store_name,
'client_type' => 'INTEGRATION',
Expand All @@ -60,8 +70,13 @@ public function process_payment()
$response = Xendit::request($request_url, Xendit::METHOD_POST, $request_payload, $request_options);

if (isset($response['error_code'])) {
$json['error'] = $response['message'];
}
$message = $response['message'];

if (isset($response['code'])) {
$message .= " Code: " . $response['code'];
}
$json['error'] = $message;
}
else {
$this->model_payment_xendit->addOrder($order, $response, $this->config->get('xendit_environment'), 'invoice');
$message = 'Invoice ID: ' . $response['id'] . '. Redirecting..';
Expand Down
Loading

0 comments on commit 75485e1

Please sign in to comment.