From b240bbdff67a0c1a3eeca9c5084ce2da258c54b8 Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Sun, 16 Aug 2026 18:34:57 +0200 Subject: [PATCH 1/5] Add dnqr (DuitNow QR) payment method support --- PluginChip.php | 52 ++++++++++++++++++++++++++++++++++++++++++++-- class.chip.api.php | 4 ++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/PluginChip.php b/PluginChip.php index 6ab4645..b043304 100644 --- a/PluginChip.php +++ b/PluginChip.php @@ -8,6 +8,8 @@ */ class PluginChip extends GatewayPlugin { + const DUITNOW_GROUP = array('duitnow_qr', 'dnqr'); + public function getVariables() { $variables = array( @@ -178,7 +180,13 @@ public function autopayment($params) $payment_method_whitelist = explode(',', $payment_method_whitelist); $diff = array_diff($payment_method_whitelist, ['fpx', 'fpx_b2b1', 'mastercard', 'maestro', 'visa', 'razer_atome', 'razer_grabpay', 'razer_maybankqr', 'razer_shopeepay', 'razer_tng', 'duitnow_qr']); if (empty($diff)) { - $purchase_params['payment_method_whitelist'] = $payment_method_whitelist; + $purchase_params['payment_method_whitelist'] = $this->resolve_duitnow_methods( + $payment_method_whitelist, + $params['currencytype'], + (int) round($params['invoiceTotal'] * 100), + $brand_id, + $secret_key + ); } } @@ -197,9 +205,49 @@ public function autopayment($params) exit(); } - private function maybe_save_public_key($secret_key, $brand_id) + private function resolve_duitnow_methods($whitelist, $currency, $amount, $brand_id, $secret_key) { + // 1. Short-circuit: no dnqr-group member configured → return unchanged (no API call). + $has_group_member = count(array_intersect($whitelist, self::DUITNOW_GROUP)) > 0; + if (!$has_group_member) { + return $whitelist; + } + + // 2. Expand the group in-memory. + $expanded = array_values(array_unique(array_merge($whitelist, self::DUITNOW_GROUP))); + + // 3. Static cache keyed by brand + currency + amount-bucket (round to 100-sen steps). + static $cache = array(); + $cache_key = md5($brand_id . '|' . $currency . '|' . intval($amount / 100)); + + if (!array_key_exists($cache_key, $cache)) { + $chip = ChipApi::get_instance($secret_key, $brand_id); + $response = $chip->payment_methods($currency, $amount); + if (!is_array($response) || !isset($response['available_payment_methods'])) { + // 4. API fail → fallback to expanded whitelist. + return $expanded; + } + $cache[$cache_key] = $response['available_payment_methods']; + } + $available = $cache[$cache_key]; + // 5. Intersect: keep only group members the merchant actually has. + $resolved_group = array_values(array_intersect(self::DUITNOW_GROUP, $available)); + + // 6. Priority: dnqr wins when both are present. + if (in_array('dnqr', $resolved_group, true)) { + $resolved_group = array_values(array_diff($resolved_group, array('duitnow_qr'))); + } + + // 7. Final: original non-group entries + resolved group. + $final = array_values(array_diff($expanded, self::DUITNOW_GROUP)); + $final = array_merge($final, $resolved_group); + + return $final; + } + + private function maybe_save_public_key($secret_key, $brand_id) + { $public_key = $this->settings->get('plugin_chip_Public Key'); if (!str_contains($public_key, $brand_id)) { diff --git a/class.chip.api.php b/class.chip.api.php index 47a3c3c..1230f8a 100644 --- a/class.chip.api.php +++ b/class.chip.api.php @@ -36,11 +36,11 @@ public function charge_payment($payment_id, $params) return $this->call('POST', "/purchases/{$payment_id}/charge/", $params); } - public function payment_methods($currency) + public function payment_methods($currency, $amount = 200) { return $this->call( 'GET', - "/payment_methods/?brand_id={$this->brand_id}¤cy={$currency}&amount=200" + "/payment_methods/?brand_id={$this->brand_id}¤cy={$currency}&amount={$amount}" ); } From a8c46c891090b44a3682ef139595c5a719502ae6 Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Mon, 17 Aug 2026 03:22:31 +0200 Subject: [PATCH 2/5] Add shopee_pay group support via generalised payment method group resolver --- PluginChip.php | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/PluginChip.php b/PluginChip.php index b043304..f279ac3 100644 --- a/PluginChip.php +++ b/PluginChip.php @@ -9,6 +9,7 @@ class PluginChip extends GatewayPlugin { const DUITNOW_GROUP = array('duitnow_qr', 'dnqr'); + const SHOPEE_GROUP = array('razer_shopeepay', 'shopee_pay'); public function getVariables() { @@ -180,7 +181,7 @@ public function autopayment($params) $payment_method_whitelist = explode(',', $payment_method_whitelist); $diff = array_diff($payment_method_whitelist, ['fpx', 'fpx_b2b1', 'mastercard', 'maestro', 'visa', 'razer_atome', 'razer_grabpay', 'razer_maybankqr', 'razer_shopeepay', 'razer_tng', 'duitnow_qr']); if (empty($diff)) { - $purchase_params['payment_method_whitelist'] = $this->resolve_duitnow_methods( + $purchase_params['payment_method_whitelist'] = $this->resolve_payment_method_groups( $payment_method_whitelist, $params['currencytype'], (int) round($params['invoiceTotal'] * 100), @@ -205,16 +206,22 @@ public function autopayment($params) exit(); } - private function resolve_duitnow_methods($whitelist, $currency, $amount, $brand_id, $secret_key) + private function resolve_payment_method_groups($whitelist, $currency, $amount, $brand_id, $secret_key) { - // 1. Short-circuit: no dnqr-group member configured → return unchanged (no API call). - $has_group_member = count(array_intersect($whitelist, self::DUITNOW_GROUP)) > 0; + $groups = array( + 'dnqr' => self::DUITNOW_GROUP, + 'shopee' => self::SHOPEE_GROUP, + ); + + // 1. Short-circuit: no group member configured → return unchanged (no API call). + $all_group_members = array_merge(self::DUITNOW_GROUP, self::SHOPEE_GROUP); + $has_group_member = count(array_intersect($whitelist, $all_group_members)) > 0; if (!$has_group_member) { return $whitelist; } - // 2. Expand the group in-memory. - $expanded = array_values(array_unique(array_merge($whitelist, self::DUITNOW_GROUP))); + // 2. Expand the configured groups in-memory. + $expanded = array_values(array_unique(array_merge($whitelist, $all_group_members))); // 3. Static cache keyed by brand + currency + amount-bucket (round to 100-sen steps). static $cache = array(); @@ -231,17 +238,23 @@ private function resolve_duitnow_methods($whitelist, $currency, $amount, $brand_ } $available = $cache[$cache_key]; - // 5. Intersect: keep only group members the merchant actually has. - $resolved_group = array_values(array_intersect(self::DUITNOW_GROUP, $available)); - - // 6. Priority: dnqr wins when both are present. - if (in_array('dnqr', $resolved_group, true)) { - $resolved_group = array_values(array_diff($resolved_group, array('duitnow_qr'))); + // 5. Resolve each configured group against what the merchant actually has. + $resolved = array(); + foreach ($groups as $preferred => $group) { + $resolved_group = array_values(array_intersect($group, $available)); + if (empty($resolved_group)) { + continue; + } + // 6. Priority: preferred member wins when both are present. + if (in_array($preferred, $resolved_group, true)) { + $resolved_group = array_values(array_diff($resolved_group, array_diff($group, array($preferred)))); + } + $resolved = array_merge($resolved, $resolved_group); } - // 7. Final: original non-group entries + resolved group. - $final = array_values(array_diff($expanded, self::DUITNOW_GROUP)); - $final = array_merge($final, $resolved_group); + // 7. Final: original non-group entries + resolved groups. + $final = array_values(array_diff($expanded, $all_group_members)); + $final = array_merge($final, $resolved); return $final; } From b1432ac88b19d0a1457741599731d12826eb094b Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Mon, 17 Aug 2026 10:37:24 +0200 Subject: [PATCH 3/5] Migrate Shopee Pay to shopee_pay with backward-compatible in-memory migration --- PluginChip.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/PluginChip.php b/PluginChip.php index f279ac3..52d13d2 100644 --- a/PluginChip.php +++ b/PluginChip.php @@ -46,7 +46,7 @@ public function getVariables() ), lang('Payment Method Whitelist') => array( 'type' => 'text', - 'description' => 'Set payment method whitelist separated by comma. Acceptable value: fpx, fpx_b2b1, mastercard, maestro, visa, razer_atome, razer_grabpay, razer_maybankqr, razer_shopeepay, razer_tng, duitnow_qr. Leave blank if unsure.', + 'description' => 'Set payment method whitelist separated by comma. Acceptable value: fpx, fpx_b2b1, mastercard, maestro, visa, razer_atome, razer_grabpay, razer_maybankqr, shopee_pay, razer_tng, duitnow_qr. Note: razer_shopeepay is legacy and will be migrated to shopee_pay automatically. Leave blank if unsure.', 'value' => '' ), lang('Public Key') => array( @@ -179,8 +179,14 @@ public function autopayment($params) if (!empty($payment_method_whitelist = str_replace(' ', '', strtolower($params['plugin_chip_Payment Method Whitelist'])))) { $payment_method_whitelist = explode(',', $payment_method_whitelist); - $diff = array_diff($payment_method_whitelist, ['fpx', 'fpx_b2b1', 'mastercard', 'maestro', 'visa', 'razer_atome', 'razer_grabpay', 'razer_maybankqr', 'razer_shopeepay', 'razer_tng', 'duitnow_qr']); + $diff = array_diff($payment_method_whitelist, ['fpx', 'fpx_b2b1', 'mastercard', 'maestro', 'visa', 'razer_atome', 'razer_grabpay', 'razer_maybankqr', 'razer_shopeepay', 'shopee_pay', 'razer_tng', 'duitnow_qr']); if (empty($diff)) { + // In-memory migration: legacy razer_shopeepay → modern shopee_pay. + if (in_array('razer_shopeepay', $payment_method_whitelist, true) && !in_array('shopee_pay', $payment_method_whitelist, true)) { + $payment_method_whitelist = array_map(function ($method) { + return $method === 'razer_shopeepay' ? 'shopee_pay' : $method; + }, $payment_method_whitelist); + } $purchase_params['payment_method_whitelist'] = $this->resolve_payment_method_groups( $payment_method_whitelist, $params['currencytype'], @@ -210,7 +216,7 @@ private function resolve_payment_method_groups($whitelist, $currency, $amount, $ { $groups = array( 'dnqr' => self::DUITNOW_GROUP, - 'shopee' => self::SHOPEE_GROUP, + 'shopee_pay' => self::SHOPEE_GROUP, ); // 1. Short-circuit: no group member configured → return unchanged (no API call). From 42e61a45daf6340043c5ddf6c69b11fbca2c22db Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Mon, 17 Aug 2026 11:37:51 +0200 Subject: [PATCH 4/5] fix: use RM10 amount for payment method availability checks --- class.chip.api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/class.chip.api.php b/class.chip.api.php index 1230f8a..12ae85b 100644 --- a/class.chip.api.php +++ b/class.chip.api.php @@ -36,7 +36,7 @@ public function charge_payment($payment_id, $params) return $this->call('POST', "/purchases/{$payment_id}/charge/", $params); } - public function payment_methods($currency, $amount = 200) + public function payment_methods($currency, $amount = 1000) { return $this->call( 'GET', @@ -48,7 +48,7 @@ public function payment_recurring_methods($currency) { return $this->call( 'GET', - "/payment_methods/?brand_id={$this->brand_id}¤cy={$currency}&amount=200&recurring=true" + "/payment_methods/?brand_id={$this->brand_id}¤cy={$currency}&amount=1000&recurring=true" ); } From 2af312f037fc54eaff0a392b3848ccfa0fbdc10c Mon Sep 17 00:00:00 2001 From: Wan Zulkarnain Date: Mon, 17 Aug 2026 14:31:35 +0200 Subject: [PATCH 5/5] Add crypto_coin as a valid payment method whitelist value --- PluginChip.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PluginChip.php b/PluginChip.php index 52d13d2..0f942e0 100644 --- a/PluginChip.php +++ b/PluginChip.php @@ -46,7 +46,7 @@ public function getVariables() ), lang('Payment Method Whitelist') => array( 'type' => 'text', - 'description' => 'Set payment method whitelist separated by comma. Acceptable value: fpx, fpx_b2b1, mastercard, maestro, visa, razer_atome, razer_grabpay, razer_maybankqr, shopee_pay, razer_tng, duitnow_qr. Note: razer_shopeepay is legacy and will be migrated to shopee_pay automatically. Leave blank if unsure.', + 'description' => 'Set payment method whitelist separated by comma. Acceptable value: fpx, fpx_b2b1, mastercard, maestro, visa, razer_atome, razer_grabpay, razer_maybankqr, shopee_pay, razer_tng, duitnow_qr, crypto_coin. Note: razer_shopeepay is legacy and will be migrated to shopee_pay automatically. Leave blank if unsure.', 'value' => '' ), lang('Public Key') => array( @@ -179,7 +179,7 @@ public function autopayment($params) if (!empty($payment_method_whitelist = str_replace(' ', '', strtolower($params['plugin_chip_Payment Method Whitelist'])))) { $payment_method_whitelist = explode(',', $payment_method_whitelist); - $diff = array_diff($payment_method_whitelist, ['fpx', 'fpx_b2b1', 'mastercard', 'maestro', 'visa', 'razer_atome', 'razer_grabpay', 'razer_maybankqr', 'razer_shopeepay', 'shopee_pay', 'razer_tng', 'duitnow_qr']); + $diff = array_diff($payment_method_whitelist, ['fpx', 'fpx_b2b1', 'mastercard', 'maestro', 'visa', 'razer_atome', 'razer_grabpay', 'razer_maybankqr', 'razer_shopeepay', 'shopee_pay', 'razer_tng', 'duitnow_qr', 'crypto_coin']); if (empty($diff)) { // In-memory migration: legacy razer_shopeepay → modern shopee_pay. if (in_array('razer_shopeepay', $payment_method_whitelist, true) && !in_array('shopee_pay', $payment_method_whitelist, true)) {