From acdbcfd8baa93bc6f4cd33feff4ed4efeffbfa82 Mon Sep 17 00:00:00 2001 From: thisgun Date: Thu, 23 Jul 2026 08:03:16 +0000 Subject: [PATCH] =?UTF-8?q?KG=EC=9D=B4=EB=8B=88=EC=8B=9C=EC=8A=A4=20INIpay?= =?UTF-8?q?=20PRO=20=EA=B2=B0=EC=A0=9C=20=EC=97=B0=EB=8F=99=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=B0=EC=A0=9C=20=EC=B2=98=EB=A6=AC=20=ED=98=84=ED=99=A9=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- adm/admin.menu400.shop_1of2.php | 7 + adm/dbupgrade.php | 259 ++++- adm/shop_admin/admin.shop.lib.php | 199 +++- adm/shop_admin/configform.php | 134 ++- adm/shop_admin/configformupdate.php | 35 + adm/shop_admin/inicislog.lib.php | 407 +++++++ adm/shop_admin/inicislogcheck.php | 77 ++ adm/shop_admin/inicisloglist.php | 265 +++++ adm/shop_admin/inicislogview.php | 148 +++ adm/shop_admin/orderformcartupdate.php | 91 +- install/gnuboard5shop.sql | 97 ++ install/install_db.php | 2 + mobile/shop/inicis/orderform.1.php | 7 +- mobile/shop/orderform.sub.php | 25 +- mobile/shop/orderformupdate.php | 35 +- mobile/shop/personalpayform.sub.php | 8 +- mobile/shop/personalpayformupdate.php | 29 +- mobile/shop/settle_inicis.inc.php | 5 +- shop.config.php | 16 + shop/inicis/escrow.register.php | 3 +- shop/inicis/inipay_cancel.php | 73 +- shop/inicis/libs/inicis_youngcart_fn.php | 102 +- shop/inicis/orderform.1.php | 7 +- shop/inicis/orderpartcancel.inc.php | 9 +- shop/inicis/pro/_common.php | 7 + shop/inicis/pro/inicis_pro.lib.php | 1241 ++++++++++++++++++++++ shop/inicis/pro/monitor.php | 61 ++ shop/inicis/pro/noti.php | 411 +++++++ shop/inicis/pro/orderform.1.php | 52 + shop/inicis/pro/pay_result.php | 227 ++++ shop/inicis/pro/request.php | 148 +++ shop/inicis/pro/return.php | 278 +++++ shop/orderform.sub.php | 33 +- shop/orderformupdate.php | 30 +- shop/orderinquirycancel.php | 25 + shop/personalpayform.sub.php | 6 +- shop/personalpayformupdate.php | 29 +- shop/settle_inicis.inc.php | 5 +- 38 files changed, 4506 insertions(+), 87 deletions(-) create mode 100644 adm/shop_admin/inicislog.lib.php create mode 100644 adm/shop_admin/inicislogcheck.php create mode 100644 adm/shop_admin/inicisloglist.php create mode 100644 adm/shop_admin/inicislogview.php create mode 100644 shop/inicis/pro/_common.php create mode 100644 shop/inicis/pro/inicis_pro.lib.php create mode 100644 shop/inicis/pro/monitor.php create mode 100644 shop/inicis/pro/noti.php create mode 100644 shop/inicis/pro/orderform.1.php create mode 100644 shop/inicis/pro/pay_result.php create mode 100644 shop/inicis/pro/request.php create mode 100644 shop/inicis/pro/return.php diff --git a/adm/admin.menu400.shop_1of2.php b/adm/admin.menu400.shop_1of2.php index d6b8696f0..4e6c360c2 100644 --- a/adm/admin.menu400.shop_1of2.php +++ b/adm/admin.menu400.shop_1of2.php @@ -3,6 +3,8 @@ if (!defined('G5_USE_SHOP') || !G5_USE_SHOP) { return; } +global $default; + $menu['menu400'] = array( array('400000', '쇼핑몰관리', G5_ADMIN_URL . '/shop_admin/', 'shop_config'), array('400010', '쇼핑몰현황', G5_ADMIN_URL . '/shop_admin/', 'shop_index'), @@ -21,3 +23,8 @@ $menu['menu400'] = array( array('400750', '추가배송비관리', G5_ADMIN_URL . '/shop_admin/sendcostlist.php', 'scf_sendcost', 1), array('400410', '미완료주문', G5_ADMIN_URL . '/shop_admin/inorderlist.php', 'scf_inorder', 1), ); + +// 결제방식이 KG이니시스이고 INIpay PRO를 사용할 때만 노출한다. +if (isset($default['de_pg_service']) && $default['de_pg_service'] === 'inicis' && !empty($default['de_inicis_pro_use'])) { + $menu['menu400'][] = array('400420', 'KG이니시스 PRO 현황', G5_ADMIN_URL . '/shop_admin/inicisloglist.php', 'scf_inicis_log'); +} diff --git a/adm/dbupgrade.php b/adm/dbupgrade.php index ae11ece8f..5d6a80289 100644 --- a/adm/dbupgrade.php +++ b/adm/dbupgrade.php @@ -374,6 +374,263 @@ if (!sql_query(" DESC `{$g5['member_auto_login_table']}` ", false)) { $is_check = true; } +// KG이니시스 결제 처리 현황 및 단계별 이력 테이블 +if (defined('G5_USE_SHOP') && G5_USE_SHOP) { + $inicis_pro_config_columns = array( + 'de_inicis_pro_alert_use' => "ADD COLUMN `de_inicis_pro_alert_use` tinyint(4) NOT NULL DEFAULT '1'", + 'de_inicis_pro_reconcile_use' => "ADD COLUMN `de_inicis_pro_reconcile_use` tinyint(4) NOT NULL DEFAULT '0'", + 'de_inicis_pro_log_days' => "ADD COLUMN `de_inicis_pro_log_days` int(11) NOT NULL DEFAULT '365'", + 'de_inicis_pro_summary_days' => "ADD COLUMN `de_inicis_pro_summary_days` int(11) NOT NULL DEFAULT '1825'", + 'de_inicis_pro_monitor_at' => "ADD COLUMN `de_inicis_pro_monitor_at` datetime DEFAULT NULL", + 'de_inicis_pro_monitor_message' => "ADD COLUMN `de_inicis_pro_monitor_message` varchar(255) NOT NULL DEFAULT ''" + ); + $inicis_pro_config_alter = array(); + foreach ($inicis_pro_config_columns as $column => $alter) { + $column_result = sql_query(" SHOW COLUMNS FROM `{$g5['g5_shop_default_table']}` LIKE '$column' ", false); + if (!$column_result || sql_num_rows($column_result) === 0) + $inicis_pro_config_alter[] = $alter; + } + if (count($inicis_pro_config_alter)) { + sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}` ".implode(', ', $inicis_pro_config_alter), true); + $is_check = true; + } + + if (!isset($g5['g5_shop_inicis_pay_table'])) + $g5['g5_shop_inicis_pay_table'] = G5_SHOP_TABLE_PREFIX.'inicis_pay'; + if (!isset($g5['g5_shop_inicis_pay_event_table'])) + $g5['g5_shop_inicis_pay_event_table'] = G5_SHOP_TABLE_PREFIX.'inicis_pay_event'; + + if (!sql_query(" DESC `{$g5['g5_shop_inicis_pay_table']}` ", false)) { + sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['g5_shop_inicis_pay_table']}` ( + `ip_id` int(11) NOT NULL AUTO_INCREMENT, + `ip_oid` varchar(64) NOT NULL DEFAULT '', + `ip_tid` varchar(80) NOT NULL DEFAULT '', + `ip_auth_tid` varchar(80) NOT NULL DEFAULT '', + `ip_mid` varchar(80) NOT NULL DEFAULT '', + `ip_environment` varchar(10) NOT NULL DEFAULT '', + `mb_id` varchar(20) NOT NULL DEFAULT '', + `ip_amount` int(11) NOT NULL DEFAULT '0', + `ip_pay_type` varchar(20) NOT NULL DEFAULT '', + `ip_easy_pay` varchar(20) NOT NULL DEFAULT '', + `ip_device` varchar(10) NOT NULL DEFAULT '', + `ip_order_type` varchar(10) NOT NULL DEFAULT '', + `ip_status` varchar(30) NOT NULL DEFAULT '', + `ip_result_code` varchar(30) NOT NULL DEFAULT '', + `ip_result_message` varchar(255) NOT NULL DEFAULT '', + `ip_noti_status` varchar(30) NOT NULL DEFAULT '', + `ip_noti_code` varchar(30) NOT NULL DEFAULT '', + `ip_noti_message` varchar(255) NOT NULL DEFAULT '', + `ip_noti_failed_count` int(11) NOT NULL DEFAULT '0', + `ip_noti_at` datetime DEFAULT NULL, + `ip_cancel_status` varchar(30) NOT NULL DEFAULT '', + `ip_cancel_code` varchar(30) NOT NULL DEFAULT '', + `ip_cancel_message` varchar(255) NOT NULL DEFAULT '', + `ip_cancel_checked_at` datetime DEFAULT NULL, + `ip_refund_required` tinyint(4) NOT NULL DEFAULT '0', + `ip_vbank_due_at` datetime DEFAULT NULL, + `ip_expired_at` datetime DEFAULT NULL, + `ip_order_exists` tinyint(4) NOT NULL DEFAULT '0', + `ip_approved_at` datetime DEFAULT NULL, + `ip_ordered_at` datetime DEFAULT NULL, + `ip_notified_at` datetime DEFAULT NULL, + `ip_canceled_at` datetime DEFAULT NULL, + `ip_created_at` datetime DEFAULT NULL, + `ip_updated_at` datetime DEFAULT NULL, + `ip_ip` varchar(45) NOT NULL DEFAULT '', + `ip_event_count` int(11) NOT NULL DEFAULT '0', + `ip_audit_error` tinyint(4) NOT NULL DEFAULT '0', + `ip_alerted_at` datetime DEFAULT NULL, + `ip_alert_key` varchar(64) NOT NULL DEFAULT '', + `ip_pg_status` varchar(30) NOT NULL DEFAULT '', + `ip_pg_amount` int(11) NOT NULL DEFAULT '0', + `ip_pg_tid` varchar(80) NOT NULL DEFAULT '', + `ip_pg_result_code` varchar(30) NOT NULL DEFAULT '', + `ip_pg_message` varchar(255) NOT NULL DEFAULT '', + `ip_pg_checked_at` datetime DEFAULT NULL, + PRIMARY KEY (`ip_id`), + UNIQUE KEY `ip_oid` (`ip_oid`), + KEY `ip_tid` (`ip_tid`), + KEY `ip_auth_tid` (`ip_auth_tid`), + KEY `ip_status` (`ip_status`), + KEY `ip_noti_status` (`ip_noti_status`), + KEY `ip_cancel_status` (`ip_cancel_status`), + KEY `ip_refund_required` (`ip_refund_required`), + KEY `ip_updated_at` (`ip_updated_at`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ", true); + $is_check = true; + } + + if (sql_query(" DESC `{$g5['g5_shop_inicis_pay_table']}` ", false)) { + $inicis_pay_columns = array( + 'ip_environment' => "ADD COLUMN `ip_environment` varchar(10) NOT NULL DEFAULT '' AFTER `ip_mid`", + 'ip_easy_pay' => "ADD COLUMN `ip_easy_pay` varchar(20) NOT NULL DEFAULT '' AFTER `ip_pay_type`", + 'ip_noti_status' => "ADD COLUMN `ip_noti_status` varchar(30) NOT NULL DEFAULT '' AFTER `ip_result_message`", + 'ip_noti_code' => "ADD COLUMN `ip_noti_code` varchar(30) NOT NULL DEFAULT '' AFTER `ip_noti_status`", + 'ip_noti_message' => "ADD COLUMN `ip_noti_message` varchar(255) NOT NULL DEFAULT '' AFTER `ip_noti_code`", + 'ip_noti_failed_count' => "ADD COLUMN `ip_noti_failed_count` int(11) NOT NULL DEFAULT '0' AFTER `ip_noti_message`", + 'ip_noti_at' => "ADD COLUMN `ip_noti_at` datetime DEFAULT NULL AFTER `ip_noti_failed_count`", + 'ip_cancel_status' => "ADD COLUMN `ip_cancel_status` varchar(30) NOT NULL DEFAULT '' AFTER `ip_noti_at`", + 'ip_cancel_code' => "ADD COLUMN `ip_cancel_code` varchar(30) NOT NULL DEFAULT '' AFTER `ip_cancel_status`", + 'ip_cancel_message' => "ADD COLUMN `ip_cancel_message` varchar(255) NOT NULL DEFAULT '' AFTER `ip_cancel_code`", + 'ip_cancel_checked_at' => "ADD COLUMN `ip_cancel_checked_at` datetime DEFAULT NULL AFTER `ip_cancel_message`", + 'ip_refund_required' => "ADD COLUMN `ip_refund_required` tinyint(4) NOT NULL DEFAULT '0' AFTER `ip_cancel_checked_at`", + 'ip_vbank_due_at' => "ADD COLUMN `ip_vbank_due_at` datetime DEFAULT NULL AFTER `ip_refund_required`", + 'ip_expired_at' => "ADD COLUMN `ip_expired_at` datetime DEFAULT NULL AFTER `ip_vbank_due_at`", + 'ip_audit_error' => "ADD COLUMN `ip_audit_error` tinyint(4) NOT NULL DEFAULT '0'", + 'ip_alerted_at' => "ADD COLUMN `ip_alerted_at` datetime DEFAULT NULL", + 'ip_alert_key' => "ADD COLUMN `ip_alert_key` varchar(64) NOT NULL DEFAULT ''", + 'ip_pg_status' => "ADD COLUMN `ip_pg_status` varchar(30) NOT NULL DEFAULT ''", + 'ip_pg_amount' => "ADD COLUMN `ip_pg_amount` int(11) NOT NULL DEFAULT '0'", + 'ip_pg_tid' => "ADD COLUMN `ip_pg_tid` varchar(80) NOT NULL DEFAULT ''", + 'ip_pg_result_code' => "ADD COLUMN `ip_pg_result_code` varchar(30) NOT NULL DEFAULT ''", + 'ip_pg_message' => "ADD COLUMN `ip_pg_message` varchar(255) NOT NULL DEFAULT ''", + 'ip_pg_checked_at' => "ADD COLUMN `ip_pg_checked_at` datetime DEFAULT NULL" + ); + $inicis_pay_alter = array(); + foreach ($inicis_pay_columns as $column => $alter) { + $column_result = sql_query(" SHOW COLUMNS FROM `{$g5['g5_shop_inicis_pay_table']}` LIKE '$column' ", false); + if (!$column_result || sql_num_rows($column_result) === 0) + $inicis_pay_alter[] = $alter; + } + if (count($inicis_pay_alter)) { + sql_query(" ALTER TABLE `{$g5['g5_shop_inicis_pay_table']}` ".implode(', ', $inicis_pay_alter), true); + $is_check = true; + } + + if (count($inicis_pay_alter)) { + $environment = !empty($default['de_card_test']) ? 'test' : 'live'; + sql_query(" insert ignore into `{$g5['g5_shop_inicis_pay_table']}` + (ip_oid, ip_environment, ip_status, ip_noti_status, ip_noti_code, ip_noti_message, + ip_noti_failed_count, ip_noti_at, ip_created_at, ip_updated_at, ip_event_count) + select e.ip_oid, '$environment', e.pe_status, e.pe_status, e.pe_code, e.pe_message, + sum(if(e.pe_status = 'notification_failed', 1, 0)), max(e.pe_created_at), + min(e.pe_created_at), max(e.pe_created_at), count(*) + from `{$g5['g5_shop_inicis_pay_event_table']}` e + left join `{$g5['g5_shop_inicis_pay_table']}` p on p.ip_oid = e.ip_oid + where p.ip_id is null + and e.pe_stage = 'notification' + group by e.ip_oid ", false); + sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` + set ip_environment = case + when lower(ip_mid) in ('inipaytest','iniescrow0') then 'test' + when ip_mid <> '' then 'live' + else '$environment' + end + where ip_environment = '' ", false); + sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p + inner join ( + select ip_oid, max(pe_id) as pe_id, + sum(if(pe_status = 'notification_failed', 1, 0)) as fail_count + from `{$g5['g5_shop_inicis_pay_event_table']}` + where pe_stage = 'notification' + group by ip_oid + ) x on x.ip_oid = p.ip_oid + inner join `{$g5['g5_shop_inicis_pay_event_table']}` e on e.pe_id = x.pe_id + set p.ip_noti_status = e.pe_status, + p.ip_noti_code = e.pe_code, + p.ip_noti_message = e.pe_message, + p.ip_noti_failed_count = x.fail_count, + p.ip_noti_at = e.pe_created_at ", false); + sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p + inner join ( + select ip_oid, max(pe_id) as pe_id + from `{$g5['g5_shop_inicis_pay_event_table']}` + where pe_stage = 'cancel' + group by ip_oid + ) x on x.ip_oid = p.ip_oid + inner join `{$g5['g5_shop_inicis_pay_event_table']}` e on e.pe_id = x.pe_id + set p.ip_cancel_status = e.pe_status, + p.ip_cancel_code = e.pe_code, + p.ip_cancel_message = e.pe_message, + p.ip_cancel_checked_at = e.pe_created_at ", false); + sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p + inner join `{$g5['g5_shop_inicis_pay_event_table']}` e on e.ip_oid = p.ip_oid and e.pe_stage = 'request' + set p.ip_easy_pay = case + when e.pe_message like '삼성페이%' then 'SAMSUNGPAY' + when e.pe_message like 'lpay%' then 'LPAY' + when e.pe_message like 'inicis_kakaopay%' then 'KAKAOPAY' + when e.pe_message like '간편결제%' then 'EASYPAY' + else p.ip_easy_pay + end + where p.ip_easy_pay = '' ", false); + sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` + set ip_noti_status = case + when ip_status = 'paid' then 'paid' + when ip_status = 'vbank_issued' then 'vbank_issued' + when ip_status = 'notification_received' then 'notification_received' + else ip_noti_status + end, + ip_cancel_status = case + when ip_status in ('canceled','cancel_failed','partial_canceled','partial_cancel_failed') then ip_status + else ip_cancel_status + end ", false); + sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p + inner join {$g5['g5_shop_order_table']} o on o.od_id = p.ip_oid + set p.ip_status = 'paid_after_cancel', + p.ip_refund_required = '1', + p.ip_noti_status = 'paid_after_cancel', + p.ip_noti_message = '취소 주문에 가상계좌 입금 확인' + where p.ip_pay_type = 'VBANK' + and p.ip_status = 'paid' + and o.od_status = '취소' + and o.od_receipt_price > 0 ", false); + $inicis_pro_migrated_at = G5_TIME_YMDHIS; + $inicis_pro_migrated_message = '기존 결제·주문 이력 대조에서 취소 후 입금 확인'; + sql_query(" insert into `{$g5['g5_shop_inicis_pay_event_table']}` + (ip_oid, ip_tid, pe_stage, pe_status, pe_code, pe_message, pe_source, pe_ip, pe_created_at) + select p.ip_oid, p.ip_tid, 'reconcile', 'paid_after_cancel', '', + '".sql_escape_string($inicis_pro_migrated_message)."', 'system', '', '$inicis_pro_migrated_at' + from `{$g5['g5_shop_inicis_pay_table']}` p + left join `{$g5['g5_shop_inicis_pay_event_table']}` e + on e.ip_oid = p.ip_oid and e.pe_status = 'paid_after_cancel' + where p.ip_status = 'paid_after_cancel' + and p.ip_refund_required = '1' + and e.pe_id is null ", false); + sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p + inner join `{$g5['g5_shop_inicis_pay_event_table']}` e + on e.ip_oid = p.ip_oid + and e.pe_status = 'paid_after_cancel' + and e.pe_message = '".sql_escape_string($inicis_pro_migrated_message)."' + and e.pe_created_at = '$inicis_pro_migrated_at' + set p.ip_event_count = p.ip_event_count + 1 ", false); + } + + $inicis_pay_indexes = array( + 'ip_noti_status' => "ADD KEY `ip_noti_status` (`ip_noti_status`)", + 'ip_cancel_status' => "ADD KEY `ip_cancel_status` (`ip_cancel_status`)", + 'ip_refund_required' => "ADD KEY `ip_refund_required` (`ip_refund_required`)" + ); + foreach ($inicis_pay_indexes as $index_name => $index_sql) { + $index_result = sql_query(" SHOW INDEX FROM `{$g5['g5_shop_inicis_pay_table']}` WHERE Key_name = '$index_name' ", false); + if (!$index_result || sql_num_rows($index_result) === 0) { + sql_query(" ALTER TABLE `{$g5['g5_shop_inicis_pay_table']}` $index_sql ", true); + $is_check = true; + } + } + } + + if (!sql_query(" DESC `{$g5['g5_shop_inicis_pay_event_table']}` ", false)) { + sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['g5_shop_inicis_pay_event_table']}` ( + `pe_id` int(11) NOT NULL AUTO_INCREMENT, + `ip_oid` varchar(64) NOT NULL DEFAULT '', + `ip_tid` varchar(80) NOT NULL DEFAULT '', + `pe_stage` varchar(30) NOT NULL DEFAULT '', + `pe_status` varchar(30) NOT NULL DEFAULT '', + `pe_code` varchar(30) NOT NULL DEFAULT '', + `pe_message` varchar(255) NOT NULL DEFAULT '', + `pe_source` varchar(10) NOT NULL DEFAULT '', + `pe_ip` varchar(45) NOT NULL DEFAULT '', + `pe_created_at` datetime DEFAULT NULL, + PRIMARY KEY (`pe_id`), + KEY `ip_oid` (`ip_oid`), + KEY `ip_tid` (`ip_tid`), + KEY `pe_status` (`pe_status`), + KEY `pe_created_at` (`pe_created_at`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ", true); + $is_check = true; + } +} + $is_check = run_replace('admin_dbupgrade', $is_check); $db_upgrade_msg = $is_check ? 'DB 업그레이드가 완료되었습니다.' : '더 이상 업그레이드 할 내용이 없습니다.
현재 DB 업그레이드가 완료된 상태입니다.'; @@ -386,4 +643,4 @@ $db_upgrade_msg = $is_check ? 'DB 업그레이드가 완료되었습니다.' : ' = 30) { + inicis_pro_purge_events($log_days, 500); + inicis_pro_purge_payloads($log_days, 200); + } + $summary_days = isset($default['de_inicis_pro_summary_days']) ? (int) $default['de_inicis_pro_summary_days'] : 1825; + if ($summary_days >= 365) + inicis_pro_purge_summaries($summary_days, 50); + + $tables = inicis_pro_audit_tables(); + $expired_count = 0; + $expired_result = sql_query(" select ip_oid from `{$tables['summary']}` + where ip_status = 'request_ready' + and ip_updated_at < date_sub(now(), interval 30 minute) + order by ip_updated_at asc + limit 50 ", false); + if ($expired_result) { + while ($expired = sql_fetch_array($expired_result)) { + if (inicis_pro_audit_write($expired['ip_oid'], 'request', 'request_expired', array( + 'source' => 'system', + 'message' => '결제창 요청 후 인증결과가 없어 미진행으로 정리' + ))) + $expired_count++; + } + } + + $order_amount_sql = "(o.od_cart_price + o.od_send_cost + o.od_send_cost2 - o.od_cart_coupon - o.od_coupon - o.od_send_coupon - o.od_receipt_point)"; + $anomaly_sql = inicis_admin_anomaly_sql(); + $sql_from = " from `{$tables['summary']}` p + left join {$g5['g5_shop_order_table']} o on p.ip_order_type <> 'personal' and o.od_id = p.ip_oid + left join {$g5['g5_shop_personalpay_table']} pp on p.ip_order_type = 'personal' and pp.pp_id = p.ip_oid "; + $sql_fields = " select p.*, + o.od_id as order_oid, o.od_tno as order_tid, o.od_status as order_status, + o.od_receipt_price as order_receipt_price, o.od_misu as order_misu, o.od_cancel_price as order_cancel_price, + o.od_settle_case as order_settle_case, $order_amount_sql as order_amount, + pp.pp_id as personal_oid, pp.pp_tno as personal_tid, pp.pp_price as personal_amount, pp.pp_receipt_price as personal_receipt_price "; + + $recover_result = sql_query(" select ip_oid from `{$tables['summary']}` + where ip_tid = '' + and ip_status in ('authentication_received','approval_started','validation_failed','order_saving') + and ip_updated_at < date_sub(now(), interval 3 minute) + order by ip_updated_at asc + limit 5 ", false); + if ($recover_result) { + while ($recover_row = sql_fetch_array($recover_result)) + inicis_pro_recover_approved_tid($recover_row['ip_oid'], 'system'); + } + + $reconcile_count = 0; + $reconcile_failed_count = 0; + if ($run_external && empty($default['de_card_test']) && !empty($default['de_inicis_pro_reconcile_use']) && inicis_pro_get_iniapi_key() !== '') { + $result = sql_query(" $sql_fields $sql_from + where p.ip_tid <> '' + and p.ip_updated_at < date_sub(now(), interval 3 minute) + and (p.ip_pg_checked_at is null or p.ip_pg_checked_at < date_sub(now(), interval 1 hour)) + and (p.ip_refund_required = '1' + or $anomaly_sql + or (p.ip_updated_at > date_sub(now(), interval 7 day) + and (p.ip_status in ('order_saved','paid','paid_after_cancel','vbank_issued','canceled','refund_completed') + or p.ip_cancel_status <> ''))) + order by p.ip_refund_required desc, p.ip_updated_at asc + limit 10 ", false); + if ($result) { + while ($row = sql_fetch_array($result)) { + $inquiry = inicis_pro_inquiry($row['ip_tid'], $row['ip_oid'], $row); + if (inicis_pro_save_inquiry($row['ip_oid'], $inquiry, 'system') && !empty($inquiry['success'])) + $reconcile_count++; + else + $reconcile_failed_count++; + } + } + } + + $anomaly_count_row = sql_fetch(" select count(*) as cnt $sql_from where $anomaly_sql "); + $anomaly_count = isset($anomaly_count_row['cnt']) ? (int) $anomaly_count_row['cnt'] : 0; + if ($run_external) + $monitor_message = '미진행 '.$expired_count.'건 정리 / KG 대사 성공 '.$reconcile_count.'건, 실패 '.$reconcile_failed_count.'건 / 확인 필요 '.$anomaly_count.'건'; + else + $monitor_message = '미진행 '.$expired_count.'건 정리 / 확인 필요 '.$anomaly_count.'건'; + if (array_key_exists('de_inicis_pro_monitor_at', $default)) { + $monitor_set = "de_inicis_pro_monitor_at = '".G5_TIME_YMDHIS."'"; + if (isset($default['de_inicis_pro_monitor_message'])) + $monitor_set .= ", de_inicis_pro_monitor_message = '".sql_escape_string($monitor_message)."'"; + sql_query(" update {$g5['g5_shop_default_table']} set $monitor_set ", false); + } + + // 테스트 거래는 상세 화면에서 직접 조회하며 운영 메일에는 포함하지 않는다. + if (!empty($default['de_card_test'])) + return; + + if (empty($default['de_inicis_pro_alert_use']) || empty($config['cf_email_use']) || empty($config['cf_admin_email'])) + return; + + $result = sql_query(" $sql_fields $sql_from + where $anomaly_sql + order by p.ip_updated_at asc + limit 20 ", false); + if (!$result) + return; + + $alert_rows = array(); + $mail_msg = ''; + while ($row = sql_fetch_array($result)) { + if (!inicis_admin_is_anomaly($row)) + continue; + + $alert_key = inicis_admin_alert_key($row); + if (!empty($row['ip_alert_key']) && $row['ip_alert_key'] === $alert_key) + continue; + + $pg_state = !empty($row['ip_pg_checked_at']) + ? ($row['ip_pg_result_code'] === '00' ? inicis_admin_pg_status_label($row['ip_pg_status']) : '거래조회 실패') + : '미조회'; + $mail_msg .= '' + .''.get_text($row['ip_oid']).'' + .''.get_text(inicis_admin_status_label(inicis_admin_primary_status($row))).'' + .''.get_text($pg_state).'' + .''.number_format((int) $row['ip_amount']).'원' + .''; + $alert_rows[] = array('oid' => $row['ip_oid'], 'key' => $alert_key); + } + + if (!count($alert_rows)) + return; + + include_once(G5_LIB_PATH.'/mailer.lib.php'); + $content = '

KG이니시스 결제와 영카트 주문을 대조해야 하는 거래가 확인됐습니다.

' + .'

자동으로 주문을 생성하거나 결제를 취소하지 않았습니다. KG이니시스 거래조회 결과와 상점관리자 거래내역을 확인한 후 조치해 주십시오.

' + .'' + .$mail_msg.'
주문번호로컬 상태KG 상태금액
'; + $sent = mailer($config['cf_admin_email_name'], $config['cf_admin_email'], $config['cf_admin_email'], '['.$config['cf_title'].'] KG이니시스 결제 확인 필요', $content, 1); + if (!$sent) + return; + + foreach ($alert_rows as $alert_row) { + sql_query(" update `{$tables['summary']}` + set ip_alerted_at = '".G5_TIME_YMDHIS."', + ip_alert_key = '".sql_escape_string($alert_row['key'])."' + where ip_oid = '".sql_escape_string($alert_row['oid'])."' ", false); + } +} + function check_order_inicis_tmps(){ global $g5, $config, $default, $member; @@ -225,7 +418,9 @@ function check_order_inicis_tmps(){ if( ! $admin_cookie_time ){ - if( $default['de_pg_service'] === 'inicis' && empty($default['de_card_test']) ){ + if ($default['de_pg_service'] === 'inicis' && !empty($default['de_inicis_pro_use'])) { + check_order_inicis_pro_payments(); + } elseif( $default['de_pg_service'] === 'inicis' && empty($default['de_card_test']) ){ $sql = " select * from {$g5['g5_shop_inicis_log_table']} where P_TID <> '' and P_TYPE in ('CARD', 'ISP', 'BANK') and P_MID <> '' and P_STATUS = '00' and is_mail_send = 0 and substr(P_AUTH_DT, 1, 14) < '".date('YmdHis', strtotime('-3 minutes', G5_SERVER_TIME))."' "; $result = sql_query($sql, false); @@ -240,7 +435,7 @@ function check_order_inicis_tmps(){ $oid = $row['oid']; $p_tid = $row['P_TID']; - $p_mid = strtolower($tmps['P_MID']); + $p_mid = strtolower($row['P_MID']); if( in_array($p_mid, array('iniescrow0', 'inipaytest')) ) continue; diff --git a/adm/shop_admin/configform.php b/adm/shop_admin/configform.php index 0501e2f90..cc58a136c 100644 --- a/adm/shop_admin/configform.php +++ b/adm/shop_admin/configform.php @@ -207,6 +207,44 @@ if( ! isset($default['de_inicis_iniapi_key']) ){ sql_query($sql, false); } +// KG이니시스 INIpay PRO 설정 추가 +if (!isset($default['de_inicis_pro_use'])) { + $sql = "ALTER TABLE `{$g5['g5_shop_default_table']}` + ADD COLUMN `de_inicis_pro_use` TINYINT(4) NOT NULL DEFAULT '0' AFTER `de_inicis_sign_key`, + ADD COLUMN `de_inicis_hash_key` VARCHAR(255) NOT NULL DEFAULT '' AFTER `de_inicis_pro_use`; "; + sql_query($sql, false); +} + +if (!isset($default['de_inicis_pro_alert_use'])) { + sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_alert_use` TINYINT(4) NOT NULL DEFAULT '1'", false); + $default['de_inicis_pro_alert_use'] = 1; +} +if (!isset($default['de_inicis_pro_reconcile_use'])) { + sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_reconcile_use` TINYINT(4) NOT NULL DEFAULT '0'", false); + $default['de_inicis_pro_reconcile_use'] = 0; +} +if (!isset($default['de_inicis_pro_log_days'])) { + sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_log_days` INT(11) NOT NULL DEFAULT '365'", false); + $default['de_inicis_pro_log_days'] = 365; +} +if (!isset($default['de_inicis_pro_summary_days'])) { + sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_summary_days` INT(11) NOT NULL DEFAULT '1825'", false); + $default['de_inicis_pro_summary_days'] = 1825; +} +if (!array_key_exists('de_inicis_pro_monitor_at', $default)) { + sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_monitor_at` DATETIME DEFAULT NULL", false); + $default['de_inicis_pro_monitor_at'] = ''; +} +if (!isset($default['de_inicis_pro_monitor_message'])) { + sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_monitor_message` VARCHAR(255) NOT NULL DEFAULT ''", false); + $default['de_inicis_pro_monitor_message'] = ''; +} + +// KG이니시스 INIpay PRO 결제 감사 테이블이 없으면 생성한다. +include_once(G5_SHOP_PATH.'/inicis/pro/inicis_pro.lib.php'); +if (function_exists('inicis_pro_audit_ensure_tables')) + inicis_pro_audit_ensure_tables(); + // NICEPAY mid, key 추가 if (! isset($default['de_nicepay_mid'])) { $sql = "ALTER TABLE `{$g5['g5_shop_default_table']}` @@ -879,34 +917,73 @@ if(!$default['de_kakaopay_cancelpwd']){ SIR 영문소문자(숫자포함 가능) + + + + + > + + - KG이니시스 가맹점관리자 > 상점정보 > 계약정보 > 부가정보의 웹결제 signkey생성 조회 버튼 클릭, 팝업창에서 생성 버튼 클릭 후 해당 값을 입력합니다."); ?> + KG이니시스 가맹점관리자 > 상점정보 > 계약정보 > KEY 정보의 웹결제 signkey생성 조회 버튼 클릭, 팝업창에서 생성 버튼 클릭 후 해당 값을 입력합니다."); ?> + + + + KG이니시스 가맹점관리자 > 상점정보 > 계약정보 > KEY 정보의 모바일 금액위변조 HashKey를 입력합니다."); ?> + + + - KG이니시스 가맹점관리자 > 상점정보 > 계약정보 > 부가정보 > INIAPI key 생성 조회 하여 KEY를 여기에 입력합니다.\n이 항목은 영카트 주문에서 kg이니시스 PG 결제 취소, 부분취소, 에스크로 배송등록, 현금영수증 발급에 필요합니다."); ?> + KG이니시스 가맹점관리자 > 상점정보 > 계약정보 > KEY 정보 > INIAPI key 생성 조회 하여 KEY를 여기에 입력합니다.\n이 항목은 영카트 주문에서 kg이니시스 PG 결제 취소, 부분취소, 에스크로 배송등록, 현금영수증 발급에 필요합니다."); ?> - KG이니시스 가맹점관리자 > 상점정보 > 계약정보 > 부가정보 > INIAPI IV 생성 조회 하여 KEY를 여기에 입력합니다.\n이 항목은 영카트 주문에서 kg이니시스 현금영수증 발급에 필요합니다."); ?> + KG이니시스 가맹점관리자 > 상점정보 > 계약정보 > KEY 정보 > INIAPI IV 생성 조회 하여 KEY를 여기에 입력합니다.\n이 항목은 영카트 주문에서 kg이니시스 현금영수증 발급에 필요합니다."); ?> + + KG이니시스 PRO 운영 감시 + + + > +   + > +
최근 감시: + / + + + + + + + 일 + + + + + + + 일 + + 삼성페이 서비스신청하기 - 삼성페이 사용 계약을 하신 경우에만 체크해주세요. (모바일 주문서 결제수단에 삼성페이가 노출됩니다.)
실결제시 반드시 결제대행사 KG이니시스 항목에 상점 아이디와 웹결제 사인키를 입력해 주세요.", 50); ?> + 삼성페이 사용 계약을 하신 경우에만 체크해주세요. INIpay PRO 사용 시 PC와 모바일 주문서에 삼성페이가 노출되며 삼성페이 결제창을 직접 호출합니다. 구버전 결제에서는 모바일 주문서에만 노출됩니다.
실결제 시 상점 아이디와 사용 중인 결제모듈의 인증키(PRO: HashKey, 구버전: 웹결제 사인키)를 입력해 주세요.", 50); ?> > @@ -915,7 +992,7 @@ if(!$default['de_kakaopay_cancelpwd']){ - 실결제시 반드시 결제대행사 KG이니시스 항목의 상점 정보( 아이디, 웹결제 사인키 )를 입력해 주세요.", 50); ?> + 실결제 시 상점 아이디와 사용 중인 결제모듈의 인증키(PRO: HashKey, 구버전: 웹결제 사인키)를 입력해 주세요.", 50); ?> > @@ -924,7 +1001,7 @@ if(!$default['de_kakaopay_cancelpwd']){ - 실결제시 반드시 결제대행사 KG이니시스 항목의 상점 정보( 아이디, 웹결제 사인키 )를 입력해 주세요.", 50); ?> + 실결제 시 상점 아이디와 사용 중인 결제모듈의 인증키(PRO: HashKey, 구버전: 웹결제 사인키)를 입력해 주세요.", 50); ?> > @@ -933,7 +1010,7 @@ if(!$default['de_kakaopay_cancelpwd']){ - 체크시 pc 결제에서는 신용카드 포인트 사용 여부에 대한 팝업창에 사용 버튼과 사용안함 버튼이 표기되어 결제하는 고객의 선택여부에 따라 신용카드 포인트 결제가 가능합니다.
모바일에서는 신용카드 포인트 사용이 가능합니다.", 50); ?> + 체크 시 PC 결제에서는 신용카드 포인트 사용 여부를 선택할 수 있고 모바일에서도 카드 포인트를 사용할 수 있습니다.
INIpay PRO에는 이 설정을 전달하지 않습니다. PRO 카드 포인트 사용은 KG이니시스에서 해당 MID의 지원 여부와 요청 규격을 확인한 후 적용해야 합니다.", 50); ?> > @@ -1789,6 +1866,26 @@ function byte_check(el_cont, el_byte) + + 'personal' and o.od_id = p.ip_oid + left join {$g5['g5_shop_personalpay_table']} pp on p.ip_order_type = 'personal' and pp.pp_id = p.ip_oid + where p.ip_oid = '$oid_sql' "; +$row = sql_fetch($sql); +if (empty($row['ip_id'])) + alert('결제 처리 이력을 찾을 수 없습니다.', './inicisloglist.php'); + +$events = sql_query(" select * from `{$tables['event']}` where ip_oid = '$oid_sql' order by pe_id asc "); +$has_order = inicis_admin_has_order($row); +$is_anomaly = inicis_admin_is_anomaly($row); +$saved_tid = inicis_admin_order_tid($row); +$saved_amount = inicis_admin_order_amount($row); +$legacy_pro_log = empty($row['ip_tid']) ? inicis_pro_get_log($oid) : array(); +$legacy_pro_data = isset($legacy_pro_log['pro_data']) && is_array($legacy_pro_log['pro_data']) ? $legacy_pro_log['pro_data'] : array(); +// 승인 TID가 있거나 승인 로그에서 복구할 수 있을 때만 조회한다. +// TID를 참조할 수 없는 미완료(인증 전) 거래는 KG에 조회할 대상이 없다. +$can_inquiry = !empty($row['ip_tid']) + || (!empty($legacy_pro_data['__pro']) && $legacy_pro_data['__pro'] === '1' + && isset($legacy_pro_log['P_STATUS']) && $legacy_pro_log['P_STATUS'] === '00' && !empty($legacy_pro_data['P_APPL_TID'])); +$g5['title'] = 'KG이니시스 INIpay PRO 결제 처리 상세'; +include_once(G5_ADMIN_PATH.'/admin.head.php'); +?> + +
+

이 기록은 영카트 서버가 처리한 단계의 감사 이력입니다. 최종 결제·취소 여부는 KG이니시스 상점관리자의 TID 거래내역과 대조하여 확정하십시오.

+

설정된 보존기간이 지난 단계별 상세 이력은 순차 삭제될 수 있으며, 아래 누적 이력 수에는 삭제 전 기록도 포함됩니다.

+

이 거래에서 감사 상세 이력 기록 오류가 감지됐습니다. 서버 오류 로그와 KG이니시스 거래내역을 함께 확인하십시오.

+

취소 주문에 입금이 확인되어 환불이 필요합니다. 상품을 발송하지 말고 KG이니시스 상점관리자에서 입금 및 환불 상태를 확인하십시오.

+
+ +
+

:

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
결제 처리 요약
주문번호 OID처리 판정확인 필요' : ($has_order ? '주문 정상 연결' : get_text(inicis_admin_status_label(inicis_admin_primary_status($row)))); ?>
인증 TID승인 TID
회원 IDMID
결제금액원결제수단
결제환경테스트' : ($row['ip_environment'] === 'live' ? '실결제' : '확인 필요'); ?>환불 확인 필요예' : '아니오'; ?>
접속 구분결제 구분
현재 상태결과 코드
최근 서버 통보통보 실패 누계회
최근 취소 처리가상계좌 입금기한
통보 메시지취소 메시지
감사 이력 상태상세 기록 오류' : '정상'; ?>최근 이상 알림
마지막 메시지
승인 일시주문 저장 일시
통보 일시취소 일시
주문 DB의 TID주문 DB의 금액
주문 상태/입금액주문 미수금/취소액
KG 거래조회 상태KG 거래조회 일시
KG 조회 TIDKG 조회 금액 0 ? number_format((int) $row['ip_pg_amount']).'원' : '-'; ?>
KG 조회 메시지
+
+ +
+ 목록 + +
+ + + + +
+ + + 개인결제 보기 + + 주문 보기 + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + '; + ?> + +
결제 단계별 처리 이력
순서처리일시경로단계결과코드메시지IP
단계별 이력이 없습니다.
+
+ + 0) + $selected_ct_ids[$pre_ct_id] = $pre_ct_id; + } + + if (count($selected_ct_ids)) { + $selected_ct_sql = implode(',', array_values($selected_ct_ids)); + $future = sql_fetch(" select count(*) as total_count, + sum(if(ct_status in ('취소','반품','품절') or ct_id in ($selected_ct_sql), 1, 0)) as cancel_count + from {$g5['g5_shop_cart_table']} + where od_id = '".sql_escape_string($od_id)."' "); + if ((int) $future['total_count'] > 0 && (int) $future['total_count'] === (int) $future['cancel_count']) { + $pre_od = sql_fetch(" select * from {$g5['g5_shop_order_table']} where od_id = '".sql_escape_string($od_id)."' "); + if (!empty($pre_od['od_tno']) && $pre_od['od_pg'] === 'inicis') { + include_once(G5_SHOP_PATH.'/inicis/pro/inicis_pro.lib.php'); + $pro_tables = inicis_pro_audit_tables(); + $pro_summary = sql_fetch(" select * from `{$pro_tables['summary']}` + where ip_oid = '".sql_escape_string($pre_od['od_id'])."' + and ip_tid = '".sql_escape_string($pre_od['od_tno'])."' ", false); + if (!empty($pro_summary['ip_id'])) { + if (!isset($_POST['pg_cancel']) || (int) $_POST['pg_cancel'] !== 1) + alert('INIpay PRO 주문은 PG 승인취소를 함께 처리해야 전체취소할 수 있습니다. PG 승인취소를 선택한 후 다시 처리해 주십시오.'); + + $inicis_pro_order_lock = inicis_pro_lock($pre_od['od_id']); + if ($inicis_pro_order_lock === '') + alert('동일 주문의 결제 또는 통보 처리가 진행 중입니다. 잠시 후 다시 취소해 주십시오.'); + + $pro_environment = !empty($pro_summary['ip_environment']) ? $pro_summary['ip_environment'] : inicis_pro_environment(); + if ($pro_summary['ip_mid'] !== inicis_pro_get_mid(!empty($pro_summary['ip_pay_type']) ? $pro_summary['ip_pay_type'] : null) || $pro_environment !== inicis_pro_environment()) + alert('거래 당시 MID 또는 결제환경과 현재 설정이 달라 PG 취소를 실행할 수 없습니다. KG이니시스 상점관리자에서 원거래를 확인해 주십시오.'); + + include_once(G5_SHOP_PATH.'/settle_inicis.inc.php'); + $pre_cancel_args = array( + 'paymethod' => get_type_inicis_paymethod($pre_od['od_settle_case']), + 'tid' => $pre_od['od_tno'], + 'mid' => $pro_summary['ip_mid'], + 'audit_oid' => $pre_od['od_id'], + 'audit_source' => 'admin', + 'msg' => '쇼핑몰 운영자 승인 취소', + 'url' => $pro_environment === 'test' ? 'https://stginiapi.inicis.com/api/v1/refund' : 'https://iniapi.inicis.com/api/v1/refund' + ); + $pre_cancel_response = inicis_tid_cancel($pre_cancel_args); + $pre_cancel_result = json_decode($pre_cancel_response, true); + if (!isset($pre_cancel_result['resultCode']) || $pre_cancel_result['resultCode'] !== '00') { + $pre_cancel_code = !empty($pre_cancel_result['resultCode']) ? $pre_cancel_result['resultCode'] : 'COMMUNICATION_FAILED'; + $pre_cancel_message = !empty($pre_cancel_result['resultMsg']) ? $pre_cancel_result['resultMsg'] : 'KG이니시스 취소 응답을 확인하지 못했습니다.'; + alert($pre_cancel_message.' 코드 : '.$pre_cancel_code); + } + $inicis_pro_cancel_preprocessed = true; + } + } + } + } +} + $search = isset($_REQUEST['search']) ? get_search_string($_REQUEST['search']) : ''; $sort1 = isset($_REQUEST['sort1']) ? clean_xss_tags($_REQUEST['sort1'], 1, 1) : ''; $sort2 = isset($_REQUEST['sort2']) ? clean_xss_tags($_REQUEST['sort2'], 1, 1) : ''; @@ -227,15 +293,19 @@ if (in_array($_POST['ct_status'], $status_cancel)) { case 'inicis': include_once(G5_SHOP_PATH.'/settle_inicis.inc.php'); $cancel_msg = '쇼핑몰 운영자 승인 취소'; - - $args = array( - 'paymethod' => get_type_inicis_paymethod($od['od_settle_case']), - 'tid' => $od['od_tno'], - 'msg' => $cancel_msg - ); - $response = inicis_tid_cancel($args); - $result = json_decode($response, true); + if ($inicis_pro_cancel_preprocessed) { + $result = array('resultCode' => '00', 'resultMsg' => 'INIpay PRO PG 취소 선처리 완료'); + } else { + $args = array( + 'paymethod' => get_type_inicis_paymethod($od['od_settle_case']), + 'tid' => $od['od_tno'], + 'msg' => $cancel_msg + ); + + $response = inicis_tid_cancel($args); + $result = json_decode($response, true); + } if (isset($result['resultCode'])) { if ($result['resultCode'] != '00') { @@ -376,6 +446,9 @@ $qstr = "sort1=$sort1&sort2=$sort2&sel_field=$sel_field&search=$sear $url = "./orderform.php?od_id=$od_id&$qstr"; +if ($inicis_pro_order_lock !== '') + inicis_pro_unlock($inicis_pro_order_lock); + // 신용카드 취소 때 오류가 있으면 알림 if($pg_cancel == 1 && $pg_res_cd && $pg_res_msg) { alert('오류코드 : '.$pg_res_cd.' 오류내용 : '.$pg_res_msg, $url); @@ -386,4 +459,4 @@ if($pg_cancel == 1 && $pg_res_cd && $pg_res_msg) { alert("포인트로 결제한 주문은,\\n\\n주문상태 변경으로 인해 포인트의 가감이 발생하는 경우\\n\\n회원관리 > 포인트관리에서 수작업으로 포인트를 맞추어 주셔야 합니다.", $url); else goto_url($url); -} \ No newline at end of file +} diff --git a/install/gnuboard5shop.sql b/install/gnuboard5shop.sql index de6a5647f..c101e3942 100644 --- a/install/gnuboard5shop.sql +++ b/install/gnuboard5shop.sql @@ -354,6 +354,14 @@ CREATE TABLE IF NOT EXISTS `g5_shop_default` ( `de_inicis_iniapi_key` varchar(30) NOT NULL DEFAULT '', `de_inicis_iniapi_iv` varchar(30) NOT NULL DEFAULT '', `de_inicis_sign_key` varchar(255) NOT NULL DEFAULT '', + `de_inicis_pro_use` tinyint(4) NOT NULL DEFAULT '0', + `de_inicis_hash_key` varchar(255) NOT NULL DEFAULT '', + `de_inicis_pro_alert_use` tinyint(4) NOT NULL DEFAULT '1', + `de_inicis_pro_reconcile_use` tinyint(4) NOT NULL DEFAULT '0', + `de_inicis_pro_log_days` int(11) NOT NULL DEFAULT '365', + `de_inicis_pro_summary_days` int(11) NOT NULL DEFAULT '1825', + `de_inicis_pro_monitor_at` datetime DEFAULT NULL, + `de_inicis_pro_monitor_message` varchar(255) NOT NULL DEFAULT '', `de_iche_use` tinyint(4) NOT NULL DEFAULT '0', `de_easy_pay_use` tinyint(4) NOT NULL DEFAULT '0', `de_easy_pay_services` varchar(255) NOT NULL DEFAULT '', @@ -888,3 +896,92 @@ CREATE TABLE IF NOT EXISTS `g5_shop_inicis_log` ( `is_mail_send` tinyint(4) NOT NULL DEFAULT '1', PRIMARY KEY (`oid`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `g5_shop_inicis_pay` +-- + +DROP TABLE IF EXISTS `g5_shop_inicis_pay`; +CREATE TABLE IF NOT EXISTS `g5_shop_inicis_pay` ( + `ip_id` int(11) NOT NULL AUTO_INCREMENT, + `ip_oid` varchar(64) NOT NULL DEFAULT '', + `ip_tid` varchar(80) NOT NULL DEFAULT '', + `ip_auth_tid` varchar(80) NOT NULL DEFAULT '', + `ip_mid` varchar(80) NOT NULL DEFAULT '', + `ip_environment` varchar(10) NOT NULL DEFAULT '', + `mb_id` varchar(20) NOT NULL DEFAULT '', + `ip_amount` int(11) NOT NULL DEFAULT '0', + `ip_pay_type` varchar(20) NOT NULL DEFAULT '', + `ip_easy_pay` varchar(20) NOT NULL DEFAULT '', + `ip_device` varchar(10) NOT NULL DEFAULT '', + `ip_order_type` varchar(10) NOT NULL DEFAULT '', + `ip_status` varchar(30) NOT NULL DEFAULT '', + `ip_result_code` varchar(30) NOT NULL DEFAULT '', + `ip_result_message` varchar(255) NOT NULL DEFAULT '', + `ip_noti_status` varchar(30) NOT NULL DEFAULT '', + `ip_noti_code` varchar(30) NOT NULL DEFAULT '', + `ip_noti_message` varchar(255) NOT NULL DEFAULT '', + `ip_noti_failed_count` int(11) NOT NULL DEFAULT '0', + `ip_noti_at` datetime DEFAULT NULL, + `ip_cancel_status` varchar(30) NOT NULL DEFAULT '', + `ip_cancel_code` varchar(30) NOT NULL DEFAULT '', + `ip_cancel_message` varchar(255) NOT NULL DEFAULT '', + `ip_cancel_checked_at` datetime DEFAULT NULL, + `ip_refund_required` tinyint(4) NOT NULL DEFAULT '0', + `ip_vbank_due_at` datetime DEFAULT NULL, + `ip_expired_at` datetime DEFAULT NULL, + `ip_order_exists` tinyint(4) NOT NULL DEFAULT '0', + `ip_approved_at` datetime DEFAULT NULL, + `ip_ordered_at` datetime DEFAULT NULL, + `ip_notified_at` datetime DEFAULT NULL, + `ip_canceled_at` datetime DEFAULT NULL, + `ip_created_at` datetime DEFAULT NULL, + `ip_updated_at` datetime DEFAULT NULL, + `ip_ip` varchar(45) NOT NULL DEFAULT '', + `ip_event_count` int(11) NOT NULL DEFAULT '0', + `ip_audit_error` tinyint(4) NOT NULL DEFAULT '0', + `ip_alerted_at` datetime DEFAULT NULL, + `ip_alert_key` varchar(64) NOT NULL DEFAULT '', + `ip_pg_status` varchar(30) NOT NULL DEFAULT '', + `ip_pg_amount` int(11) NOT NULL DEFAULT '0', + `ip_pg_tid` varchar(80) NOT NULL DEFAULT '', + `ip_pg_result_code` varchar(30) NOT NULL DEFAULT '', + `ip_pg_message` varchar(255) NOT NULL DEFAULT '', + `ip_pg_checked_at` datetime DEFAULT NULL, + PRIMARY KEY (`ip_id`), + UNIQUE KEY `ip_oid` (`ip_oid`), + KEY `ip_tid` (`ip_tid`), + KEY `ip_auth_tid` (`ip_auth_tid`), + KEY `ip_status` (`ip_status`), + KEY `ip_noti_status` (`ip_noti_status`), + KEY `ip_cancel_status` (`ip_cancel_status`), + KEY `ip_refund_required` (`ip_refund_required`), + KEY `ip_updated_at` (`ip_updated_at`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `g5_shop_inicis_pay_event` +-- + +DROP TABLE IF EXISTS `g5_shop_inicis_pay_event`; +CREATE TABLE IF NOT EXISTS `g5_shop_inicis_pay_event` ( + `pe_id` int(11) NOT NULL AUTO_INCREMENT, + `ip_oid` varchar(64) NOT NULL DEFAULT '', + `ip_tid` varchar(80) NOT NULL DEFAULT '', + `pe_stage` varchar(30) NOT NULL DEFAULT '', + `pe_status` varchar(30) NOT NULL DEFAULT '', + `pe_code` varchar(30) NOT NULL DEFAULT '', + `pe_message` varchar(255) NOT NULL DEFAULT '', + `pe_source` varchar(10) NOT NULL DEFAULT '', + `pe_ip` varchar(45) NOT NULL DEFAULT '', + `pe_created_at` datetime DEFAULT NULL, + PRIMARY KEY (`pe_id`), + KEY `ip_oid` (`ip_oid`), + KEY `ip_tid` (`ip_tid`), + KEY `pe_status` (`pe_status`), + KEY `pe_created_at` (`pe_created_at`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8; diff --git a/install/install_db.php b/install/install_db.php index 9ed54cf2b..de3d0394b 100644 --- a/install/install_db.php +++ b/install/install_db.php @@ -660,6 +660,8 @@ if($g5_shop_install) { install_file_write($f, "\$g5['g5_shop_post_log_table'] = G5_SHOP_TABLE_PREFIX.'order_post_log'; // 주문요청 로그 테이블\n"); install_file_write($f, "\$g5['g5_shop_order_data_table'] = G5_SHOP_TABLE_PREFIX.'order_data'; // 모바일 결제정보 임시저장 테이블\n"); install_file_write($f, "\$g5['g5_shop_inicis_log_table'] = G5_SHOP_TABLE_PREFIX.'inicis_log'; // 이니시스 모바일 계좌이체 로그 테이블\n"); + install_file_write($f, "\$g5['g5_shop_inicis_pay_table'] = G5_SHOP_TABLE_PREFIX.'inicis_pay'; // 이니시스 결제 처리 현황 테이블\n"); + install_file_write($f, "\$g5['g5_shop_inicis_pay_event_table'] = G5_SHOP_TABLE_PREFIX.'inicis_pay_event'; // 이니시스 결제 처리 이력 테이블\n"); } install_file_write($f, "?>"); diff --git a/mobile/shop/inicis/orderform.1.php b/mobile/shop/inicis/orderform.1.php index c9e8210a3..078b559c2 100644 --- a/mobile/shop/inicis/orderform.1.php +++ b/mobile/shop/inicis/orderform.1.php @@ -1,6 +1,11 @@ - \ No newline at end of file + diff --git a/mobile/shop/orderform.sub.php b/mobile/shop/orderform.sub.php index d39098e95..aa21e6b43 100644 --- a/mobile/shop/orderform.sub.php +++ b/mobile/shop/orderform.sub.php @@ -4,7 +4,7 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가 require_once(G5_MSHOP_PATH.'/settle_'.$default['de_pg_service'].'.inc.php'); require_once(G5_SHOP_PATH.'/settle_kakaopay.inc.php'); -if( is_inicis_simple_pay() ){ //이니시스 삼성페이 또는 Lpay 사용시 +if( empty($inicis_pro_use) && is_inicis_simple_pay() ){ //이니시스 삼성페이 또는 Lpay 사용시 require_once(G5_MSHOP_PATH.'/samsungpay/incSamsungpayCommon.php'); } @@ -265,7 +265,7 @@ ob_end_clean(); // 결제대행사별 코드 include (결제등록 필드) require_once(G5_MSHOP_PATH.'/'.$default['de_pg_service'].'/orderform.1.php'); -if( is_inicis_simple_pay() ){ //이니시스 삼성페이 또는 lpay 사용시 +if( empty($inicis_pro_use) && is_inicis_simple_pay() ){ //이니시스 삼성페이 또는 lpay 사용시 require_once(G5_MSHOP_PATH.'/samsungpay/orderform.1.php'); } @@ -698,18 +698,21 @@ if($is_kakaopay_use) { //이니시스 삼성페이 if($default['de_samsung_pay_use']) { + $multi_settle++; echo '
  • '.PHP_EOL; $checked = ''; } //이니시스 Lpay if($default['de_inicis_lpay_use']) { + $multi_settle++; echo '
  • '.PHP_EOL; $checked = ''; } //이니시스 카카오페이 if($default['de_inicis_kakaopay_use']) { + $multi_settle++; echo '
  • '.PHP_EOL; $checked = ''; } @@ -770,7 +773,7 @@ if($is_kakaopay_use) { echo ''; } - if ($default['de_bank_use'] || $default['de_vbank_use'] || $default['de_iche_use'] || $default['de_card_use'] || $default['de_hp_use'] || $default['de_easy_pay_use'] || is_inicis_simple_pay() ) { + if ($default['de_bank_use'] || $default['de_vbank_use'] || $default['de_iche_use'] || $default['de_card_use'] || $default['de_hp_use'] || $default['de_easy_pay_use'] || is_inicis_simple_pay()) { echo ''; } @@ -783,7 +786,7 @@ if($is_kakaopay_use) { // 결제대행사별 코드 include (결제대행사 정보 필드 및 주분버튼) require_once(G5_MSHOP_PATH.'/'.$default['de_pg_service'].'/orderform.2.php'); - if( is_inicis_simple_pay() ){ //삼성페이 또는 L.pay 사용시 + if( empty($inicis_pro_use) && is_inicis_simple_pay() ){ //삼성페이 또는 L.pay 사용시 require_once(G5_MSHOP_PATH.'/samsungpay/orderform.2.php'); } @@ -813,7 +816,7 @@ if($is_kakaopay_use) { // 결제대행사별 코드 include (에스크로 안내) require_once(G5_MSHOP_PATH.'/'.$default['de_pg_service'].'/orderform.3.php'); - if( is_inicis_simple_pay() ){ //삼성페이 사용시 + if( empty($inicis_pro_use) && is_inicis_simple_pay() ){ //삼성페이 사용시 require_once(G5_MSHOP_PATH.'/samsungpay/orderform.3.php'); } } @@ -822,7 +825,7 @@ if($is_kakaopay_use) { ){ form_order_method = 'samsungpay'; } else if(settle_method == "간편결제") { if(jQuery("input[name='od_settle_case']:checked" ).attr("data-pay") === "naverpay"){ @@ -1479,6 +1482,7 @@ function pay_approval() f.windowTarget.value = 'self'; + var paymethod = ""; var width = 330; var height = 480; @@ -1535,6 +1539,7 @@ function pay_approval() f.P_RETURN_URL.value = ""; f.action = "https://mobile.inicis.com/smart/" + paymethod + "/"; + f.Amt.value = f.good_mny.value; @@ -1636,6 +1641,10 @@ function pay_approval() return false; } + + return inicis_pro_pay("", "MOBILE"); + + nicepayStart(f); return false; @@ -1862,4 +1871,4 @@ $(function(){ $("#od_hope_date").datepicker({ changeMonth: true, changeYear: true, dateFormat: "yy-mm-dd", showButtonPanel: true, yearRange: "c-99:c+99", minDate: "+d;", maxDate: "+d;" }); }); - \ No newline at end of file + diff --git a/mobile/shop/orderformupdate.php b/mobile/shop/orderformupdate.php index 0852226ec..cab00b88d 100644 --- a/mobile/shop/orderformupdate.php +++ b/mobile/shop/orderformupdate.php @@ -372,7 +372,10 @@ else if ($od_settle_case == "계좌이체") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -404,7 +407,10 @@ else if ($od_settle_case == "가상계좌") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -436,7 +442,10 @@ else if ($od_settle_case == "휴대폰") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -466,7 +475,10 @@ else if ($od_settle_case == "신용카드") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -498,7 +510,10 @@ else if ($od_settle_case == "간편결제") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -523,7 +538,10 @@ else if ($od_settle_case == "간편결제") else if ( is_inicis_order_pay($od_settle_case) ) //이니시스의 삼성페이 또는 L.pay 또는 이니시스 카카오페이 { // 이니시스에서만 지원 - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; $od_tno = $tno; $od_app_no = $app_no; @@ -751,6 +769,9 @@ if(!$result) { die('

    고객님의 주문 정보를 처리하는 중 오류가 발생해서 주문이 완료되지 않았습니다.

    '.strtoupper($od_pg).'를 이용한 전자결제(신용카드, 계좌이체, 가상계좌 등)은 자동 취소되었습니다.'); } +if (!empty($_POST['inicis_pro']) && function_exists('inicis_pro_audit_order_saved')) + inicis_pro_audit_order_saved($od_id, $od_tno, 'order', 'mobile'); + // 회원이면서 포인트를 사용했다면 포인트 테이블에 사용을 추가 // 동시 주문 race condition 방지 — 회원 단위 GET_LOCK + 잔액 재조회 if ($is_member && $od_receipt_point) { @@ -1013,7 +1034,7 @@ set_session('ss_orderview_uid', $uid); $sql = " delete from {$g5['g5_shop_order_data_table']} where od_id = '$od_id' and dt_pg = '$od_pg' "; sql_query($sql); -if( $od_pg == 'inicis' && $od_tno ){ +if( $od_pg == 'inicis' && $od_tno && empty($_POST['inicis_pro']) ){ $sql = "delete from {$g5['g5_shop_inicis_log_table']} where oid = '$od_id' and P_TID = '$od_tno' "; sql_query($sql, false); } diff --git a/mobile/shop/personalpayform.sub.php b/mobile/shop/personalpayform.sub.php index 43b54aef6..092f9a718 100644 --- a/mobile/shop/personalpayform.sub.php +++ b/mobile/shop/personalpayform.sub.php @@ -230,6 +230,7 @@ function pay_approval() f.windowTarget.value = 'self'; + var paymethod = ""; var width = 330; var height = 480; @@ -261,6 +262,7 @@ function pay_approval() f.P_RETURN_URL.value = ""; f.action = "https://mobile.inicis.com/smart/" + paymethod + "/"; + f.Amt.value = f.good_mny.value; @@ -330,6 +332,10 @@ function pay_approval() alert(save_result); return false; } + + + return inicis_pro_pay("", "MOBILE"); + nicepayStart(f); @@ -421,4 +427,4 @@ function payment_check(f) return true; } - \ No newline at end of file + diff --git a/mobile/shop/personalpayformupdate.php b/mobile/shop/personalpayformupdate.php index eeb77edd0..77c4cb2a3 100644 --- a/mobile/shop/personalpayformupdate.php +++ b/mobile/shop/personalpayformupdate.php @@ -69,7 +69,10 @@ if ($pp_settle_case == "계좌이체") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -97,7 +100,10 @@ else if ($pp_settle_case == "가상계좌") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -126,7 +132,10 @@ else if ($pp_settle_case == "휴대폰") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -152,7 +161,10 @@ else if ($pp_settle_case == "신용카드") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_MSHOP_PATH.'/inicis/pay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_MSHOP_PATH.'/inicis/pay_result.php'; break; case 'nicepay': include G5_MSHOP_PATH.'/nicepay/nicepay_result.php'; @@ -237,7 +249,7 @@ if(!$result) { break; } - die("

    $sql

    " . sql_error_info() . "

    error file : {$_SERVER['SCRIPT_NAME']}"); + die('개인결제 정보를 처리하는 중 오류가 발생했습니다. 결제 취소 여부를 관리자에게 문의해 주십시오.'); } // 주문번호가 있으면 결제정보 반영 @@ -281,7 +293,7 @@ if($pp_receipt_price > 0 && $pp['pp_id'] && $pp['od_id']) { break; } - die("

    $sql

    " . sql_error_info() . "

    error file : {$_SERVER['SCRIPT_NAME']}"); + die('주문 결제정보를 처리하는 중 오류가 발생했습니다. 결제 취소 여부를 관리자에게 문의해 주십시오.'); } // 미수금 정보 업데이트 @@ -304,6 +316,9 @@ if($pp_receipt_price > 0 && $pp['pp_id'] && $pp['od_id']) { } // 주문 정보 임시 데이터 삭제 +if (!empty($_POST['inicis_pro']) && function_exists('inicis_pro_audit_order_saved')) + inicis_pro_audit_order_saved($pp['pp_id'], $pp_tno, 'personal', 'mobile'); + $sql = " delete from {$g5['g5_shop_order_data_table']} where od_id = '{$pp['pp_id']}' and dt_pg = '$pp_pg' "; sql_query($sql); @@ -321,4 +336,4 @@ if( $is_noti_pay ){ return; } -goto_url(G5_SHOP_URL.'/personalpayresult.php?pp_id='.$pp['pp_id'].'&uid='.$uid); \ No newline at end of file +goto_url(G5_SHOP_URL.'/personalpayresult.php?pp_id='.$pp['pp_id'].'&uid='.$uid); diff --git a/mobile/shop/settle_inicis.inc.php b/mobile/shop/settle_inicis.inc.php index d8d7e0459..9461e2641 100644 --- a/mobile/shop/settle_inicis.inc.php +++ b/mobile/shop/settle_inicis.inc.php @@ -2,6 +2,7 @@ if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가 define('G5_MOBILE_INICIS_SETTLE', true); +$inicis_pro_use = isset($default['de_inicis_pro_use']) && (int) $default['de_inicis_pro_use'] === 1; // curl 체크 if (!function_exists('curl_init')) { @@ -108,7 +109,9 @@ $inicis_cardpoint = $default['de_inicis_cartpoint_use'] ? '&cp_yn=Y' : ''; //$inicis_cardpoint .= ''; // &merc_noint=Y&noint_quota=카드사코드-개월:개월^추가카드사코드-개월:개월:개월 형식으로 설정 require_once(G5_SHOP_PATH.'/inicis/libs/inicis_youngcart_fn.php'); +if ($inicis_pro_use) + require_once(G5_SHOP_PATH.'/inicis/pro/inicis_pro.lib.php'); $noti_url = G5_MSHOP_URL.'/inicis/settle_common.php'; $next_url = G5_MSHOP_URL.'/inicis/pay_approval.php'; -$return_url = G5_MSHOP_URL.'/inicis/pay_return.php?oid='; \ No newline at end of file +$return_url = G5_MSHOP_URL.'/inicis/pay_return.php?oid='; diff --git a/shop.config.php b/shop.config.php index c909c995a..879dfbfda 100644 --- a/shop.config.php +++ b/shop.config.php @@ -126,6 +126,22 @@ define('G5_OD_STATUS_FINISH' , '배송완료'); '품절' : 주문이나 입금후 상품의 품절된 상태를 나타냅니다. */ +// KG이니시스 PRO 경량 운영 감시 +// 서버 작업 스케줄러 없이 사이트 접속(GET) 흐름에서 주기적으로 실행한다. +// $default 는 이미 로드되어 있어 평상시에는 추가 조회 없이 시각만 비교한다. +// 실제 감시는 응답 종료 후 잠금 대기 없이 처리하므로 방문자 페이지에 영향을 주지 않는다. +if (!empty($default['de_inicis_pro_use']) + && isset($default['de_pg_service']) && $default['de_pg_service'] === 'inicis' + && PHP_SAPI !== 'cli' + && isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'GET' + && array_key_exists('de_inicis_pro_monitor_at', $default) + && (empty($default['de_inicis_pro_monitor_at']) + || $default['de_inicis_pro_monitor_at'] < date('Y-m-d H:i:s', G5_SERVER_TIME - 600))) { + include_once(G5_ADMIN_PATH.'/shop_admin/admin.shop.lib.php'); + if (function_exists('check_order_inicis_pro_payments_inline')) + register_shutdown_function('check_order_inicis_pro_payments_inline'); +} + //============================================================================== // 쇼핑몰 필수 실행코드 모음 끝 //==============================================================================; \ No newline at end of file diff --git a/shop/inicis/escrow.register.php b/shop/inicis/escrow.register.php index 18420f9fb..7132a996a 100644 --- a/shop/inicis/escrow.register.php +++ b/shop/inicis/escrow.register.php @@ -111,7 +111,8 @@ curl_setopt($ch, CURLOPT_URL, $url); // 전 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 요청 결과를 문자열로 반환 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // connection timeout 10초 curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); // POST data -curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); // (※ 로컬 테스트에서만 사용) 원격 서버의 인증서가 유효한지 검사 안함 +curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // 원격 서버 인증서 검증 +curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 인증서 호스트명 검증 curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded; charset=utf-8')); // 전송헤더 설정 curl_setopt($ch, CURLOPT_POST, 1); // post 전송 diff --git a/shop/inicis/inipay_cancel.php b/shop/inicis/inipay_cancel.php index 4c4849362..54cafaca4 100644 --- a/shop/inicis/inipay_cancel.php +++ b/shop/inicis/inipay_cancel.php @@ -23,6 +23,56 @@ if($cancelFlag == "true") include_once(G5_SHOP_PATH.'/settle_inicis.inc.php'); + $pro_oid = ''; + $pro_audit_values = array(); + $pro_cancel_needs_fallback = false; + + // INIpay PRO 승인 직후 주문 DB 저장에 실패한 경우에는 인증 TID로 망취소한다. + // 망취소가 실패하면 아래의 기존 INIAPI 취소를 보조 경로로 계속 시도한다. + if (!empty($_POST['inicis_pro'])) { + include_once(G5_SHOP_PATH.'/inicis/pro/inicis_pro.lib.php'); + + $pro_oid = isset($_POST['inicis_pro_oid']) ? inicis_pro_clean_oid($_POST['inicis_pro_oid']) : inicis_pro_clean_oid(get_session('ss_order_id')); + $pro_log = inicis_pro_get_log($pro_oid); + $pro_data = isset($pro_log['pro_data']) && is_array($pro_log['pro_data']) ? $pro_log['pro_data'] : array(); + $pro_auth_tid = isset($pro_data['P_AUTH_TID']) ? inicis_pro_clean_tid($pro_data['P_AUTH_TID']) : ''; + $pro_idc = isset($pro_data['P_IDCNAME']) ? strtolower($pro_data['P_IDCNAME']) : ''; + $pro_mid = isset($pro_data['P_MID']) ? $pro_data['P_MID'] : ''; + $pro_amount = isset($pro_data['P_AMT']) ? (int) $pro_data['P_AMT'] : 0; + $pro_tid = isset($pro_data['P_APPL_TID']) ? inicis_pro_clean_tid($pro_data['P_APPL_TID']) : ''; + $pro_audit_values = array( + 'tid' => $pro_tid, + 'auth_tid' => $pro_auth_tid, + 'mid' => $pro_mid, + 'amount' => $pro_amount, + 'pay_type' => isset($pro_data['P_TYPE']) ? $pro_data['P_TYPE'] : '', + 'device' => isset($pro_data['__device']) ? $pro_data['__device'] : '', + 'source' => 'system', + 'message' => '주문 저장 이후 DB 처리 오류로 자동 취소를 요청했습니다.' + ); + + $pro_pay_type = isset($pro_data['P_TYPE']) ? $pro_data['P_TYPE'] : null; + if ($pro_oid !== '' && $pro_auth_tid !== '' && $pro_mid === inicis_pro_get_mid($pro_pay_type) && $pro_amount > 0) { + inicis_pro_audit_write($pro_oid, 'cancel', 'cancel_requested', $pro_audit_values); + $pro_cancel = inicis_pro_net_cancel($pro_idc, $pro_mid, $pro_auth_tid, $pro_amount, $pro_oid, isset($cancel_msg) ? $cancel_msg : 'DB FAIL', $pro_pay_type); + if (!empty($pro_cancel['success'])) { + sql_query(" update {$g5['g5_shop_inicis_log_table']} + set P_STATUS = 'cancel', + P_AUTH_DT = '".preg_replace('/[^0-9]/', '', G5_TIME_YMDHIS)."', + P_RMESG1 = 'INIpay PRO net-cancel success' + where oid = '".sql_escape_string($pro_oid)."' ", false); + $pro_audit_values['code'] = isset($pro_cancel['data']['P_STATUS']) ? $pro_cancel['data']['P_STATUS'] : '00'; + $pro_audit_values['message'] = '주문 DB 처리 실패 후 망취소 완료'; + inicis_pro_audit_write($pro_oid, 'cancel', 'canceled', $pro_audit_values); + return; + } + + $pro_cancel_needs_fallback = true; + $pro_audit_values['message'] = '망취소 결과를 확인하지 못해 INIAPI 취소를 시도합니다.'; + inicis_pro_audit_write($pro_oid, 'cancel', 'cancel_requested', $pro_audit_values); + } + } + if( get_session('ss_order_id') && $tno ){ $ini_oid = preg_replace('/[^a-z0-9_\-]/i', '', get_session('ss_order_id')); @@ -53,9 +103,28 @@ if($cancelFlag == "true") $args = array( 'paymethod' => $ini_paymethod, 'tid' => $tno, - 'msg' => 'DB FAIL' // 취소사유 + 'msg' => 'DB FAIL', // 취소사유 + 'audit' => false ); $response = inicis_tid_cancel($args); + + if ($pro_oid !== '' && $pro_cancel_needs_fallback) { + $result_code = ''; + if (is_string($response) && preg_match('/["\']resultCode["\']\s*:\s*["\']([^"\']+)["\']/', $response, $matches)) + $result_code = inicis_pro_cut($matches[1], 30); + + $pro_audit_values['code'] = $result_code; + if ($result_code === '00') { + $pro_audit_values['message'] = 'INIAPI 보조 취소 완료'; + inicis_pro_audit_write($pro_oid, 'cancel', 'canceled', $pro_audit_values); + } else { + $pro_audit_values['message'] = '자동 취소 결과를 확인하지 못했습니다. 상점관리자 확인이 필요합니다.'; + inicis_pro_audit_write($pro_oid, 'cancel', 'cancel_failed', $pro_audit_values); + } + } + } elseif ($pro_oid !== '' && $pro_cancel_needs_fallback) { + $pro_audit_values['message'] = '자동 취소수단을 확인하지 못했습니다. 상점관리자 확인이 필요합니다.'; + inicis_pro_audit_write($pro_oid, 'cancel', 'cancel_failed', $pro_audit_values); } -} \ No newline at end of file +} diff --git a/shop/inicis/libs/inicis_youngcart_fn.php b/shop/inicis/libs/inicis_youngcart_fn.php index 5d9afd20c..86fcac38c 100644 --- a/shop/inicis/libs/inicis_youngcart_fn.php +++ b/shop/inicis/libs/inicis_youngcart_fn.php @@ -87,19 +87,97 @@ function inicis_tid_cancel($args, $is_part=false){ ); } - $url = "https://iniapi.inicis.com/api/v1/refund"; + $url = !empty($args['url']) ? $args['url'] : (!empty($default['de_card_test']) ? 'https://stginiapi.inicis.com/api/v1/refund' : 'https://iniapi.inicis.com/api/v1/refund'); + if (!function_exists('json_encode')) + require_once(G5_SHOP_PATH.'/inicis/libs/json_lib.php'); + if (!in_array($url, array('https://iniapi.inicis.com/api/v1/refund', 'https://stginiapi.inicis.com/api/v1/refund'))) + return json_encode(array('resultCode' => 'INVALID_ENDPOINT', 'resultMsg' => 'Invalid INIAPI endpoint')); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded; charset=utf-8')); - curl_setopt($ch, CURLOPT_POST, 1); + $audit_row = array(); + $audit_values = array(); + if (!isset($args['audit']) || $args['audit']) { + include_once(G5_SHOP_PATH.'/inicis/pro/inicis_pro.lib.php'); + if (function_exists('inicis_pro_audit_tables') && function_exists('inicis_pro_audit_write')) { + $tables = inicis_pro_audit_tables(); + $audit_tid = inicis_pro_clean_tid($tid); + $audit_oid = isset($args['audit_oid']) ? inicis_pro_clean_oid($args['audit_oid']) : ''; + $audit_where = $audit_oid !== '' + ? "ip_oid = '".sql_escape_string($audit_oid)."' and ip_tid = '".sql_escape_string($audit_tid)."'" + : "ip_tid = '".sql_escape_string($audit_tid)."'"; + $audit_row = $audit_tid !== '' ? sql_fetch(" select * from `{$tables['summary']}` where $audit_where order by ip_id desc limit 1 ", false) : array(); + if (!empty($audit_row['ip_oid'])) { + $audit_source = isset($args['audit_source']) && in_array($args['audit_source'], array('web', 'admin', 'system')) ? $args['audit_source'] : 'system'; + $audit_actor = $audit_source === 'web' ? '주문자' : ($audit_source === 'admin' ? '관리자' : '시스템'); + $audit_values = array( + 'tid' => $audit_tid, + 'auth_tid' => isset($audit_row['ip_auth_tid']) ? $audit_row['ip_auth_tid'] : '', + 'mid' => isset($audit_row['ip_mid']) ? $audit_row['ip_mid'] : '', + 'environment' => isset($audit_row['ip_environment']) ? $audit_row['ip_environment'] : '', + 'amount' => isset($audit_row['ip_amount']) ? (int) $audit_row['ip_amount'] : 0, + 'pay_type' => isset($audit_row['ip_pay_type']) ? $audit_row['ip_pay_type'] : '', + 'easy_pay' => isset($audit_row['ip_easy_pay']) ? $audit_row['ip_easy_pay'] : '', + 'device' => isset($audit_row['ip_device']) ? $audit_row['ip_device'] : '', + 'order_type' => isset($audit_row['ip_order_type']) ? $audit_row['ip_order_type'] : '', + 'source' => $audit_source, + 'message' => $is_part ? $audit_actor.' 부분취소 요청' : $audit_actor.' 전체취소 요청' + ); + if ($is_part) + $audit_values['event_only'] = '1'; + inicis_pro_audit_write($audit_row['ip_oid'], 'cancel', 'cancel_requested', $audit_values); + } + } + } - $response = curl_exec($ch); - curl_close($ch); + $response = ''; + $request_environment = strpos($url, 'stginiapi.') !== false ? 'test' : 'live'; + if (!empty($audit_row['ip_oid'])) { + $audit_environment = !empty($audit_row['ip_environment']) ? $audit_row['ip_environment'] : $request_environment; + if ((!empty($audit_row['ip_mid']) && $audit_row['ip_mid'] !== $mid) || $audit_environment !== $request_environment) + $response = json_encode(array('resultCode' => 'TRANSACTION_CONFIG_MISMATCH', 'resultMsg' => '거래 당시 MID 또는 결제환경과 현재 취소 설정이 다릅니다.')); + } + + if ($response === '') { + $ch = curl_init(); + if (!$ch) { + $response = json_encode(array('resultCode' => 'COMMUNICATION_FAILED', 'resultMsg' => 'cURL 초기화 실패')); + } else { + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded; charset=utf-8')); + curl_setopt($ch, CURLOPT_POST, 1); + + $response = curl_exec($ch); + $curl_error = curl_error($ch); + $http_status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($response === false || $http_status < 200 || $http_status >= 300) { + $error_message = $curl_error !== '' ? $curl_error : 'HTTP '.$http_status; + $response = json_encode(array('resultCode' => 'COMMUNICATION_FAILED', 'resultMsg' => substr($error_message, 0, 150))); + } + } + } + + // INIpay PRO 거래는 관리자 취소 결과도 단계별 결제 이력에 남긴다. + if (!empty($audit_row['ip_oid'])) { + if (!function_exists('json_decode')) + require_once(G5_SHOP_PATH.'/inicis/libs/json_lib.php'); + $result_data = json_decode($response, true); + $result_code = is_array($result_data) && isset($result_data['resultCode']) ? inicis_pro_cut($result_data['resultCode'], 30) : 'INVALID_RESPONSE'; + $result_message = is_array($result_data) && isset($result_data['resultMsg']) ? inicis_pro_cut($result_data['resultMsg'], 180) : ''; + $audit_success = $result_code === '00'; + $audit_values['code'] = $result_code; + $audit_values['message'] = $is_part ? ($audit_success ? $audit_actor.' 부분취소 완료' : $audit_actor.' 부분취소 결과 확인 필요') : ($audit_success ? $audit_actor.' 전체취소 완료' : $audit_actor.' 전체취소 결과 확인 필요'); + if ($result_message !== '') + $audit_values['message'] .= ' - '.$result_message; + + inicis_pro_audit_write($audit_row['ip_oid'], 'cancel', $is_part ? ($audit_success ? 'partial_canceled' : 'partial_cancel_failed') : ($audit_success ? 'canceled' : 'cancel_failed'), $audit_values); + } //step3. 요청 결과 return $response; @@ -131,4 +209,4 @@ function get_type_inicis_paymethod($od_settle_case){ } return $ini_paymethod; -} \ No newline at end of file +} diff --git a/shop/inicis/orderform.1.php b/shop/inicis/orderform.1.php index 57f560620..2476b61d8 100644 --- a/shop/inicis/orderform.1.php +++ b/shop/inicis/orderform.1.php @@ -1,6 +1,11 @@ ', 10); @@ -39,4 +44,4 @@ function paybtn(f) { INIStdPay.pay(f.id); } - get_type_inicis_paymethod($od['od_settle_case']), 'tid' => $od['od_tno'], + 'audit_oid' => $od['od_id'], + 'audit_source' => 'admin', 'msg' => $od['od_id'].' '.$mod_memo, 'price' => $price, 'confirmPrice' => $confirm_price, @@ -30,14 +32,13 @@ $result = json_decode($response, true); if(isset($result['resultCode']) && $result['resultCode'] == '00') { // 환불금액기록 - $tno = $result['prtcTid']; - $re_price = $result['prtcPrice']; + $re_price = (int) $result['prtcPrice']; $sql = " update {$g5['g5_shop_order_table']} set od_refund_price = od_refund_price + '$re_price', od_shop_memo = concat(od_shop_memo, \"$mod_memo\") where od_id = '{$od['od_id']}' - and od_tno = '$tno' "; + and od_tno = '{$od['od_tno']}' "; sql_query($sql); // 미수금 등의 정보 업데이트 @@ -56,4 +57,4 @@ $result = json_decode($response, true); } else { alert('curl 오류로 부분환불에 실패했습니다.'); } - } \ No newline at end of file + } diff --git a/shop/inicis/pro/_common.php b/shop/inicis/pro/_common.php new file mode 100644 index 000000000..0aa347840 --- /dev/null +++ b/shop/inicis/pro/_common.php @@ -0,0 +1,7 @@ +쇼핑몰 설치 후 이용해 주십시오.

    '); + +define('_SHOP_', true); diff --git a/shop/inicis/pro/inicis_pro.lib.php b/shop/inicis/pro/inicis_pro.lib.php new file mode 100644 index 000000000..5b898008f --- /dev/null +++ b/shop/inicis/pro/inicis_pro.lib.php @@ -0,0 +1,1241 @@ + 'fcpaypro.inicis.com', + 'ks' => 'kspaypro.inicis.com', + 'stg' => 'stgpaypro.inicis.com' + ); + + return isset($hosts[$idc]) ? $hosts[$idc] : ''; + } +} + +if (!function_exists('inicis_pro_http_post')) { + function inicis_pro_http_post($url, $params, $timeout = 30) + { + $timeout = (int) $timeout; + if ($timeout < 5 || $timeout > 30) + $timeout = 30; + + if (!function_exists('curl_init')) + return array('success' => false, 'error' => 'cURL 모듈이 설치되어 있지 않습니다.', 'body' => ''); + + $handle = curl_init(); + if (!$handle) + return array('success' => false, 'error' => '결제 서버 연결을 초기화하지 못했습니다.', 'body' => ''); + + curl_setopt($handle, CURLOPT_URL, $url); + curl_setopt($handle, CURLOPT_POST, true); + curl_setopt($handle, CURLOPT_POSTFIELDS, http_build_query($params, '', '&')); + curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, $timeout < 10 ? $timeout : 10); + curl_setopt($handle, CURLOPT_TIMEOUT, $timeout); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, 2); + curl_setopt($handle, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded; charset=UTF-8')); + + $body = curl_exec($handle); + $errno = curl_errno($handle); + $error = curl_error($handle); + $status = (int) curl_getinfo($handle, CURLINFO_HTTP_CODE); + curl_close($handle); + + if ($body === false || $errno) + return array('success' => false, 'error' => '결제 서버 통신 오류: '.$error, 'body' => ''); + + if ($status < 200 || $status >= 300) + return array('success' => false, 'error' => '결제 서버 응답 오류: HTTP '.$status, 'body' => (string) $body); + + return array('success' => true, 'error' => '', 'body' => (string) $body); + } +} + +if (!function_exists('inicis_pro_pay_type')) { + function inicis_pro_pay_type($settle_case) + { + switch ($settle_case) { + case '계좌이체': + return 'BANK'; + case '가상계좌': + return 'VBANK'; + case '휴대폰': + return 'HPP'; + case '신용카드': + case '간편결제': + case '삼성페이': + case 'lpay': + case 'inicis_kakaopay': + return 'CARD'; + } + + return ''; + } +} + +if (!function_exists('inicis_pro_easypay_code')) { + function inicis_pro_easypay_code($settle_case) + { + switch ($settle_case) { + case '삼성페이': + return 'B'; + case 'lpay': + return 'L'; + case 'inicis_kakaopay': + return 'O'; + } + + return ''; + } +} + +if (!function_exists('inicis_pro_easypay_name')) { + function inicis_pro_easypay_name($settle_case) + { + switch ($settle_case) { + case '삼성페이': + return 'SAMSUNGPAY'; + case 'lpay': + return 'LPAY'; + case 'inicis_kakaopay': + return 'KAKAOPAY'; + case '간편결제': + return 'EASYPAY'; + } + + return ''; + } +} + +if (!function_exists('inicis_pro_environment')) { + function inicis_pro_environment() + { + global $default; + + return !empty($default['de_card_test']) ? 'test' : 'live'; + } +} + +if (!function_exists('inicis_pro_easypay_is_enabled')) { + function inicis_pro_easypay_is_enabled($settle_case) + { + global $default; + + switch ($settle_case) { + case '삼성페이': + return !empty($default['de_samsung_pay_use']); + case 'lpay': + return !empty($default['de_inicis_lpay_use']); + case 'inicis_kakaopay': + return !empty($default['de_inicis_kakaopay_use']); + } + + return true; + } +} + +if (!function_exists('inicis_pro_result_type_matches')) { + function inicis_pro_result_type_matches($settle_case, $result_type) + { + $expected = inicis_pro_pay_type($settle_case); + $result_type = strtoupper((string) $result_type); + + if ($expected === 'HPP') + return $result_type === 'HPP' || $result_type === 'MOBILE'; + + return $expected !== '' && $expected === $result_type; + } +} + +if (!function_exists('inicis_pro_expected_amount')) { + function inicis_pro_expected_amount($order_data, $temp_row) + { + global $g5; + + if (!empty($order_data['pp_id'])) { + $pp_id = inicis_pro_clean_oid($order_data['pp_id']); + $pp = sql_fetch(" select pp_id, pp_price, pp_use, pp_tno from {$g5['g5_shop_personalpay_table']} where pp_id = '".sql_escape_string($pp_id)."' "); + if (empty($pp['pp_id']) || empty($pp['pp_use']) || !empty($pp['pp_tno'])) + return 0; + + return (int) $pp['pp_price']; + } + + $price = isset($order_data['od_price']) ? (int) $order_data['od_price'] : 0; + $send_cost = isset($order_data['od_send_cost']) ? (int) $order_data['od_send_cost'] : 0; + $send_cost2 = isset($order_data['od_send_cost2']) ? (int) $order_data['od_send_cost2'] : 0; + $send_coupon = isset($order_data['od_send_coupon']) ? abs((int) $order_data['od_send_coupon']) : 0; + $point = isset($order_data['od_temp_point']) ? (int) $order_data['od_temp_point'] : 0; + + return $price + $send_cost + $send_cost2 - $send_coupon - $point; + } +} + +if (!function_exists('inicis_pro_goods_name')) { + function inicis_pro_goods_name($order_data, $temp_row) + { + global $g5; + + if (!empty($order_data['pp_id'])) { + $name = isset($order_data['pp_name']) ? $order_data['pp_name'] : '개인결제'; + return inicis_pro_cut($name.'님 개인결제', 80); + } + + if (!empty($order_data['od_goods_name'])) + return inicis_pro_cut($order_data['od_goods_name'], 80); + + $cart_id = isset($temp_row['cart_id']) ? sql_escape_string($temp_row['cart_id']) : ''; + $row = sql_fetch(" select min(it_name) as it_name, count(distinct it_id) as cnt from {$g5['g5_shop_cart_table']} where od_id = '$cart_id' and ct_select = '1' "); + $name = !empty($row['it_name']) ? $row['it_name'] : '상품 주문'; + if ((int) $row['cnt'] > 1) + $name .= ' 외 '.((int) $row['cnt'] - 1).'건'; + + return inicis_pro_cut($name, 80); + } +} + +if (!function_exists('inicis_pro_make_noti')) { + function inicis_pro_make_noti($oid, $amount, $device, $hashkey) + { + $device_code = $device === 'MOBILE' ? 'M' : 'W'; + $signature = hash('sha256', 'PRO|'.$device_code.'|'.$oid.'|'.$amount.'|'.$hashkey); + + // 결제 요청의 가맹점 예약필드는 이니시스 호환성을 위해 영문과 숫자만 사용한다. + return 'PRO'.$device_code.$signature; + } +} + +if (!function_exists('inicis_pro_check_noti')) { + function inicis_pro_check_noti($noti, $oid, $amount, $hashkey) + { + $noti = (string) $noti; + $device_code = ''; + + if (strlen($noti) === 68 && substr($noti, 0, 3) === 'PRO') { + $device_code = substr($noti, 3, 1); + } else { + // 코드 교체 전에 열린 결제창의 콜백도 검증할 수 있도록 이전 형식을 허용한다. + $parts = explode(':', $noti, 3); + if (count($parts) === 3 && $parts[0] === 'PRO') + $device_code = $parts[1]; + } + + if ($device_code !== 'W' && $device_code !== 'M') + return ''; + + $device = $device_code === 'M' ? 'MOBILE' : 'WEB'; + $expected = inicis_pro_make_noti($oid, $amount, $device, $hashkey); + if (inicis_pro_equals($expected, $noti)) + return $device; + + $legacy_expected = 'PRO:'.$device_code.':'.substr($expected, 4); + + return inicis_pro_equals($legacy_expected, $noti) ? $device : ''; + } +} + +if (!function_exists('inicis_pro_lock')) { + function inicis_pro_lock($oid, $timeout = 10) + { + $timeout = (int) $timeout; + if ($timeout < 0) + $timeout = 0; + $lock_name = 'g5_inicis_pro_'.md5($oid); + $row = sql_fetch(" select get_lock('".sql_escape_string($lock_name)."', ".$timeout.") as lock_result "); + + return !empty($row['lock_result']) ? $lock_name : ''; + } +} + +if (!function_exists('inicis_pro_unlock')) { + function inicis_pro_unlock($lock_name) + { + if ($lock_name !== '') + sql_query(" do release_lock('".sql_escape_string($lock_name)."') ", false); + } +} + +if (!function_exists('inicis_pro_log_payload')) { + function inicis_pro_log_payload($payload) + { + $allowed = array( + 'P_STATUS', 'P_RMESG', 'P_MID', 'P_OID', 'P_AUTH_TID', 'P_APPL_TID', + 'P_APPL_DT', 'P_APPL_TM', 'P_AMT', 'P_TYPE', 'P_UNAME', 'P_NOTI', + 'P_APPL_NO', 'P_FN_CD1', 'P_FN_NM', 'P_CARD_ISSUER_CODE', + 'P_CARD_ISSUER_NAME', 'P_SRC_CODE', 'P_VACT_NUM', 'P_VACT_DATE', + 'P_VACT_TIME', 'P_VACT_NAME', 'P_CSHR_CODE', 'P_CSHR_AUTH_NO', + 'P_CSHR_DT', 'P_HPP_CORP', 'P_HPP_NUM', 'P_APPL_NUM', + 'P_IN_TID', 'P_IDCNAME', '__pro', '__device', '__noti_status', + '__noti_auth_dt', '__noti_received_at', '__noti_ip' + ); + $result = array(); + foreach ($allowed as $key) { + if (isset($payload[$key]) && !is_array($payload[$key])) + $result[$key] = inicis_pro_cut($payload[$key], 600); + } + + return $result; + } +} + +if (!function_exists('inicis_pro_save_log')) { + function inicis_pro_save_log($payload) + { + global $g5; + + $payload = inicis_pro_log_payload($payload); + $oid = isset($payload['P_OID']) ? inicis_pro_clean_oid($payload['P_OID']) : ''; + if ($oid === '') + return false; + + $tid = isset($payload['P_APPL_TID']) && $payload['P_APPL_TID'] !== '' ? $payload['P_APPL_TID'] : (isset($payload['P_AUTH_TID']) ? $payload['P_AUTH_TID'] : ''); + $mid = isset($payload['P_MID']) ? $payload['P_MID'] : ''; + $auth_dt = (isset($payload['P_APPL_DT']) ? $payload['P_APPL_DT'] : '').(isset($payload['P_APPL_TM']) ? $payload['P_APPL_TM'] : ''); + $status = isset($payload['P_STATUS']) ? $payload['P_STATUS'] : ''; + $type = isset($payload['P_TYPE']) ? $payload['P_TYPE'] : ''; + $fn_name = isset($payload['P_FN_NM']) ? $payload['P_FN_NM'] : ''; + $auth_no = isset($payload['P_APPL_NO']) ? $payload['P_APPL_NO'] : ''; + $amount = isset($payload['P_AMT']) ? (int) $payload['P_AMT'] : 0; + $message = isset($payload['P_RMESG']) ? $payload['P_RMESG'] : ''; + $post_data = base64_encode(serialize($payload)); + + $sql = " insert into {$g5['g5_shop_inicis_log_table']} + set oid = '".sql_escape_string($oid)."', + P_TID = '".sql_escape_string($tid)."', + P_MID = '".sql_escape_string($mid)."', + P_AUTH_DT = '".sql_escape_string($auth_dt)."', + P_STATUS = '".sql_escape_string($status)."', + P_TYPE = '".sql_escape_string($type)."', + P_OID = '".sql_escape_string($oid)."', + P_FN_NM = '".sql_escape_string($fn_name)."', + P_AUTH_NO = '".sql_escape_string($auth_no)."', + P_AMT = '$amount', + P_RMESG1 = '".sql_escape_string($message)."', + post_data = '".sql_escape_string($post_data)."', + is_mail_send = '1' + on duplicate key update + P_TID = values(P_TID), + P_MID = values(P_MID), + P_AUTH_DT = values(P_AUTH_DT), + P_STATUS = values(P_STATUS), + P_TYPE = values(P_TYPE), + P_OID = values(P_OID), + P_FN_NM = values(P_FN_NM), + P_AUTH_NO = values(P_AUTH_NO), + P_AMT = values(P_AMT), + P_RMESG1 = values(P_RMESG1), + post_data = values(post_data), + is_mail_send = values(is_mail_send) "; + + return (bool) sql_query($sql, false); + } +} + +if (!function_exists('inicis_pro_get_log')) { + function inicis_pro_get_log($oid) + { + global $g5; + + $oid = inicis_pro_clean_oid($oid); + if ($oid === '') + return array(); + + $row = sql_fetch(" select * from {$g5['g5_shop_inicis_log_table']} where oid = '".sql_escape_string($oid)."' "); + if (empty($row['oid'])) + return array(); + + $payload = !empty($row['post_data']) ? @unserialize(base64_decode($row['post_data'])) : array(); + $row['pro_data'] = is_array($payload) ? $payload : array(); + + return $row; + } +} + +if (!function_exists('inicis_pro_audit_tables')) { + function inicis_pro_audit_tables() + { + global $g5; + + $summary = isset($g5['g5_shop_inicis_pay_table']) ? $g5['g5_shop_inicis_pay_table'] : G5_SHOP_TABLE_PREFIX.'inicis_pay'; + $event = isset($g5['g5_shop_inicis_pay_event_table']) ? $g5['g5_shop_inicis_pay_event_table'] : G5_SHOP_TABLE_PREFIX.'inicis_pay_event'; + + return array('summary' => $summary, 'event' => $event); + } +} + +if (!function_exists('inicis_pro_audit_ensure_tables')) { + // 감사 테이블이 없으면 생성한다(스키마는 dbupgrade.php와 동일하게 유지). + function inicis_pro_audit_ensure_tables() + { + $tables = inicis_pro_audit_tables(); + + if (!sql_query(" DESC `{$tables['summary']}` ", false)) { + sql_query(" CREATE TABLE IF NOT EXISTS `{$tables['summary']}` ( + `ip_id` int(11) NOT NULL AUTO_INCREMENT, + `ip_oid` varchar(64) NOT NULL DEFAULT '', + `ip_tid` varchar(80) NOT NULL DEFAULT '', + `ip_auth_tid` varchar(80) NOT NULL DEFAULT '', + `ip_mid` varchar(80) NOT NULL DEFAULT '', + `ip_environment` varchar(10) NOT NULL DEFAULT '', + `mb_id` varchar(20) NOT NULL DEFAULT '', + `ip_amount` int(11) NOT NULL DEFAULT '0', + `ip_pay_type` varchar(20) NOT NULL DEFAULT '', + `ip_easy_pay` varchar(20) NOT NULL DEFAULT '', + `ip_device` varchar(10) NOT NULL DEFAULT '', + `ip_order_type` varchar(10) NOT NULL DEFAULT '', + `ip_status` varchar(30) NOT NULL DEFAULT '', + `ip_result_code` varchar(30) NOT NULL DEFAULT '', + `ip_result_message` varchar(255) NOT NULL DEFAULT '', + `ip_noti_status` varchar(30) NOT NULL DEFAULT '', + `ip_noti_code` varchar(30) NOT NULL DEFAULT '', + `ip_noti_message` varchar(255) NOT NULL DEFAULT '', + `ip_noti_failed_count` int(11) NOT NULL DEFAULT '0', + `ip_noti_at` datetime DEFAULT NULL, + `ip_cancel_status` varchar(30) NOT NULL DEFAULT '', + `ip_cancel_code` varchar(30) NOT NULL DEFAULT '', + `ip_cancel_message` varchar(255) NOT NULL DEFAULT '', + `ip_cancel_checked_at` datetime DEFAULT NULL, + `ip_refund_required` tinyint(4) NOT NULL DEFAULT '0', + `ip_vbank_due_at` datetime DEFAULT NULL, + `ip_expired_at` datetime DEFAULT NULL, + `ip_order_exists` tinyint(4) NOT NULL DEFAULT '0', + `ip_approved_at` datetime DEFAULT NULL, + `ip_ordered_at` datetime DEFAULT NULL, + `ip_notified_at` datetime DEFAULT NULL, + `ip_canceled_at` datetime DEFAULT NULL, + `ip_created_at` datetime DEFAULT NULL, + `ip_updated_at` datetime DEFAULT NULL, + `ip_ip` varchar(45) NOT NULL DEFAULT '', + `ip_event_count` int(11) NOT NULL DEFAULT '0', + `ip_audit_error` tinyint(4) NOT NULL DEFAULT '0', + `ip_alerted_at` datetime DEFAULT NULL, + `ip_alert_key` varchar(64) NOT NULL DEFAULT '', + `ip_pg_status` varchar(30) NOT NULL DEFAULT '', + `ip_pg_amount` int(11) NOT NULL DEFAULT '0', + `ip_pg_tid` varchar(80) NOT NULL DEFAULT '', + `ip_pg_result_code` varchar(30) NOT NULL DEFAULT '', + `ip_pg_message` varchar(255) NOT NULL DEFAULT '', + `ip_pg_checked_at` datetime DEFAULT NULL, + PRIMARY KEY (`ip_id`), + UNIQUE KEY `ip_oid` (`ip_oid`), + KEY `ip_tid` (`ip_tid`), + KEY `ip_auth_tid` (`ip_auth_tid`), + KEY `ip_status` (`ip_status`), + KEY `ip_noti_status` (`ip_noti_status`), + KEY `ip_cancel_status` (`ip_cancel_status`), + KEY `ip_refund_required` (`ip_refund_required`), + KEY `ip_updated_at` (`ip_updated_at`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ", false); + } + + if (!sql_query(" DESC `{$tables['event']}` ", false)) { + sql_query(" CREATE TABLE IF NOT EXISTS `{$tables['event']}` ( + `pe_id` int(11) NOT NULL AUTO_INCREMENT, + `ip_oid` varchar(64) NOT NULL DEFAULT '', + `ip_tid` varchar(80) NOT NULL DEFAULT '', + `pe_stage` varchar(30) NOT NULL DEFAULT '', + `pe_status` varchar(30) NOT NULL DEFAULT '', + `pe_code` varchar(30) NOT NULL DEFAULT '', + `pe_message` varchar(255) NOT NULL DEFAULT '', + `pe_source` varchar(10) NOT NULL DEFAULT '', + `pe_ip` varchar(45) NOT NULL DEFAULT '', + `pe_created_at` datetime DEFAULT NULL, + PRIMARY KEY (`pe_id`), + KEY `ip_oid` (`ip_oid`), + KEY `ip_tid` (`ip_tid`), + KEY `pe_status` (`pe_status`), + KEY `pe_created_at` (`pe_created_at`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ", false); + } + } +} + +if (!function_exists('inicis_pro_audit_tables_ready')) { + function inicis_pro_audit_tables_ready() + { + $tables = inicis_pro_audit_tables(); + + return (bool) sql_query(" select ip_id from `{$tables['summary']}` limit 1 ", false) + && (bool) sql_query(" select pe_id from `{$tables['event']}` limit 1 ", false); + } +} + +if (!function_exists('inicis_pro_audit_column_exists')) { + function inicis_pro_audit_column_exists($table, $column) + { + static $columns = array(); + + $cache_key = $table.'.'.$column; + if (isset($columns[$cache_key])) + return $columns[$cache_key]; + + if (!preg_match('/^[A-Za-z0-9_]+$/', $column)) + return false; + + $result = sql_query(" show columns from `{$table}` like '".sql_escape_string($column)."' ", false); + $columns[$cache_key] = $result && sql_num_rows($result) > 0; + + return $columns[$cache_key]; + } +} + +if (!function_exists('inicis_pro_audit_schema_ready')) { + function inicis_pro_audit_schema_ready() + { + if (!inicis_pro_audit_tables_ready()) + return false; + + $tables = inicis_pro_audit_tables(); + $required = array( + 'ip_audit_error', 'ip_alerted_at', 'ip_alert_key', 'ip_pg_status', + 'ip_pg_amount', 'ip_pg_tid', 'ip_pg_result_code', 'ip_pg_message', 'ip_pg_checked_at', + 'ip_environment', 'ip_easy_pay', 'ip_noti_status', 'ip_noti_code', + 'ip_noti_message', 'ip_noti_failed_count', 'ip_noti_at', 'ip_cancel_status', + 'ip_cancel_code', 'ip_cancel_message', 'ip_cancel_checked_at', + 'ip_refund_required', 'ip_vbank_due_at', 'ip_expired_at' + ); + foreach ($required as $column) { + if (!inicis_pro_audit_column_exists($tables['summary'], $column)) + return false; + } + + return true; + } +} + +if (!function_exists('inicis_pro_audit_error')) { + function inicis_pro_audit_error($oid, $stage, $status, $part) + { + $message = '[INIpay PRO audit] '.$part.' write failed oid='.(string) $oid + .' stage='.(string) $stage.' status='.(string) $status; + error_log($message); + } +} + +if (!function_exists('inicis_pro_audit_write')) { + function inicis_pro_audit_write($oid, $stage, $status, $values) + { + $oid = inicis_pro_clean_oid($oid); + $stage = preg_replace('/[^a-z0-9_]/', '', strtolower((string) $stage)); + $status = preg_replace('/[^a-z0-9_]/', '', strtolower((string) $status)); + if ($oid === '' || $stage === '' || $status === '') + return false; + + if (!is_array($values)) + $values = array(); + + $tables = inicis_pro_audit_tables(); + $event_only = !empty($values['event_only']); + $tid = isset($values['tid']) ? inicis_pro_clean_tid($values['tid']) : ''; + $auth_tid = isset($values['auth_tid']) ? inicis_pro_clean_tid($values['auth_tid']) : ''; + $mid = isset($values['mid']) ? inicis_pro_cut($values['mid'], 80) : ''; + $member_id = isset($values['member_id']) ? inicis_pro_cut($values['member_id'], 20) : ''; + $amount = isset($values['amount']) ? (int) $values['amount'] : 0; + if ($amount < 0) + $amount = 0; + + $pay_type = isset($values['pay_type']) ? strtoupper(preg_replace('/[^A-Za-z0-9_]/', '', (string) $values['pay_type'])) : ''; + $environment = isset($values['environment']) ? strtolower(preg_replace('/[^a-z]/', '', (string) $values['environment'])) : ''; + if (!in_array($environment, array('', 'test', 'live'))) + $environment = ''; + $easy_pay = isset($values['easy_pay']) ? strtoupper(preg_replace('/[^A-Za-z0-9_]/', '', (string) $values['easy_pay'])) : ''; + $device = isset($values['device']) ? strtoupper(preg_replace('/[^A-Za-z]/', '', (string) $values['device'])) : ''; + if (!in_array($device, array('', 'WEB', 'MOBILE', 'SERVER'))) + $device = ''; + + $order_type = isset($values['order_type']) ? strtolower((string) $values['order_type']) : ''; + if (!in_array($order_type, array('', 'order', 'personal'))) + $order_type = ''; + + $code = isset($values['code']) ? inicis_pro_cut($values['code'], 30) : ''; + $message = isset($values['message']) ? inicis_pro_cut($values['message'], 255) : ''; + $source = isset($values['source']) ? strtolower(preg_replace('/[^a-z]/i', '', (string) $values['source'])) : 'web'; + if (!in_array($source, array('web', 'mobile', 'server', 'system', 'admin'))) + $source = 'system'; + + $remote_ip = isset($values['remote_ip']) ? $values['remote_ip'] : (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''); + $remote_ip = substr(preg_replace('/[^0-9a-fA-F:.]/', '', (string) $remote_ip), 0, 45); + $now = G5_TIME_YMDHIS; + $approved_at_sql = $status === 'approved' ? "'$now'" : 'NULL'; + $ordered_at_sql = $status === 'order_saved' ? "'$now'" : 'NULL'; + $notified_at_sql = in_array($status, array('notification_received', 'vbank_issued', 'paid', 'paid_after_cancel')) ? "'$now'" : 'NULL'; + $canceled_at_sql = $status === 'canceled' ? "'$now'" : 'NULL'; + $order_exists = $status === 'order_saved' ? 1 : 0; + $noti_status = $stage === 'notification' ? $status : ''; + $noti_code = $stage === 'notification' ? $code : ''; + $noti_message = $stage === 'notification' ? $message : ''; + $noti_failed_count = $status === 'notification_failed' ? 1 : 0; + $noti_at_sql = $stage === 'notification' ? "'$now'" : 'NULL'; + $cancel_status = $stage === 'cancel' ? $status : ''; + $cancel_code = $stage === 'cancel' ? $code : ''; + $cancel_message = $stage === 'cancel' ? $message : ''; + $cancel_at_sql = $stage === 'cancel' ? "'$now'" : 'NULL'; + $refund_required = !empty($values['refund_required']) || in_array($status, array('paid_after_cancel', 'refund_required')) ? 1 : 0; + $vbank_due_at = isset($values['vbank_due_at']) ? trim((string) $values['vbank_due_at']) : ''; + if ($vbank_due_at !== '' && !preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$/', $vbank_due_at)) + $vbank_due_at = ''; + $vbank_due_at_sql = $vbank_due_at !== '' ? "'".sql_escape_string($vbank_due_at)."'" : 'NULL'; + $expired_at_sql = $status === 'request_expired' ? "'$now'" : 'NULL'; + + $extended_schema = inicis_pro_audit_column_exists($tables['summary'], 'ip_noti_status') + && inicis_pro_audit_column_exists($tables['summary'], 'ip_refund_required'); + $extended_insert = ''; + $extended_update = ''; + if ($extended_schema) { + $extended_insert = ", + ip_environment = '".sql_escape_string($environment)."', + ip_easy_pay = '".sql_escape_string($easy_pay)."', + ip_noti_status = '".sql_escape_string($noti_status)."', + ip_noti_code = '".sql_escape_string($noti_code)."', + ip_noti_message = '".sql_escape_string($noti_message)."', + ip_noti_failed_count = '$noti_failed_count', + ip_noti_at = $noti_at_sql, + ip_cancel_status = '".sql_escape_string($cancel_status)."', + ip_cancel_code = '".sql_escape_string($cancel_code)."', + ip_cancel_message = '".sql_escape_string($cancel_message)."', + ip_cancel_checked_at = $cancel_at_sql, + ip_refund_required = '$refund_required', + ip_vbank_due_at = $vbank_due_at_sql, + ip_expired_at = $expired_at_sql"; + $extended_update = ", + ip_environment = if(values(ip_environment) <> '', values(ip_environment), ip_environment), + ip_easy_pay = if(values(ip_easy_pay) <> '', values(ip_easy_pay), ip_easy_pay), + ip_noti_status = if(values(ip_noti_status) <> '', values(ip_noti_status), ip_noti_status), + ip_noti_code = if(values(ip_noti_status) <> '', values(ip_noti_code), ip_noti_code), + ip_noti_message = if(values(ip_noti_status) <> '', values(ip_noti_message), ip_noti_message), + ip_noti_failed_count = ip_noti_failed_count + values(ip_noti_failed_count), + ip_noti_at = if(values(ip_noti_at) is null, ip_noti_at, values(ip_noti_at)), + ip_cancel_status = if(values(ip_cancel_status) <> '', values(ip_cancel_status), ip_cancel_status), + ip_cancel_code = if(values(ip_cancel_status) <> '', values(ip_cancel_code), ip_cancel_code), + ip_cancel_message = if(values(ip_cancel_status) <> '', values(ip_cancel_message), ip_cancel_message), + ip_cancel_checked_at = if(values(ip_cancel_checked_at) is null, ip_cancel_checked_at, values(ip_cancel_checked_at)), + ip_refund_required = case + when ip_status = 'refund_completed' then '0' + when values(ip_status) = 'refund_completed' then '0' + when values(ip_refund_required) = '1' then '1' + else ip_refund_required + end, + ip_vbank_due_at = if(values(ip_vbank_due_at) is null, ip_vbank_due_at, values(ip_vbank_due_at)), + ip_expired_at = if(values(ip_expired_at) is null, ip_expired_at, values(ip_expired_at))"; + } + + $status_update = $event_only + ? "if(ip_status = '', values(ip_status), ip_status)" + : "case + when ip_status = 'refund_completed' then ip_status + when values(ip_status) = 'paid_after_cancel' then values(ip_status) + when values(ip_status) = 'refund_completed' then values(ip_status) + when ip_status = 'paid_after_cancel' then ip_status + when ip_status = 'canceled' and values(ip_status) <> 'paid_after_cancel' then ip_status + when ip_status = 'cancel_failed' and values(ip_status) <> 'canceled' then ip_status + when ip_status = 'paid' and values(ip_status) not in ('cancel_requested', 'canceled', 'cancel_failed', 'paid_after_cancel') then ip_status + when ip_status = 'order_saved' and values(ip_status) in ('request_ready', 'authentication_received', 'authentication_failed', 'validation_failed', 'approval_started', 'approval_failed', 'approved', 'order_saving') then ip_status + else values(ip_status) + end"; + + $sql = " insert into `{$tables['summary']}` + set ip_oid = '".sql_escape_string($oid)."', + ip_tid = '".sql_escape_string($tid)."', + ip_auth_tid = '".sql_escape_string($auth_tid)."', + ip_mid = '".sql_escape_string($mid)."', + mb_id = '".sql_escape_string($member_id)."', + ip_amount = '$amount', + ip_pay_type = '".sql_escape_string($pay_type)."', + ip_device = '".sql_escape_string($device)."', + ip_order_type = '".sql_escape_string($order_type)."', + ip_status = '".sql_escape_string($status)."', + ip_result_code = '".sql_escape_string($code)."', + ip_result_message = '".sql_escape_string($message)."', + ip_order_exists = '$order_exists', + ip_approved_at = $approved_at_sql, + ip_ordered_at = $ordered_at_sql, + ip_notified_at = $notified_at_sql, + ip_canceled_at = $canceled_at_sql, + ip_created_at = '$now', + ip_updated_at = '$now', + ip_ip = '".sql_escape_string($remote_ip)."', + ip_event_count = '1' + $extended_insert + on duplicate key update + ip_tid = if(values(ip_tid) <> '', values(ip_tid), ip_tid), + ip_auth_tid = if(values(ip_auth_tid) <> '', values(ip_auth_tid), ip_auth_tid), + ip_mid = if(values(ip_mid) <> '', values(ip_mid), ip_mid), + mb_id = if(values(mb_id) <> '', values(mb_id), mb_id), + ip_amount = if(values(ip_amount) > 0, values(ip_amount), ip_amount), + ip_pay_type = if(values(ip_pay_type) <> '', values(ip_pay_type), ip_pay_type), + ip_device = if(values(ip_device) <> '', values(ip_device), ip_device), + ip_order_type = if(values(ip_order_type) <> '', values(ip_order_type), ip_order_type), + ip_status = $status_update, + ip_result_code = ".($event_only ? 'ip_result_code' : "if(values(ip_result_code) <> '', values(ip_result_code), ip_result_code)").", + ip_result_message = ".($event_only ? 'ip_result_message' : "if(values(ip_result_message) <> '', values(ip_result_message), ip_result_message)").", + ip_order_exists = if(values(ip_order_exists) = '1', '1', ip_order_exists), + ip_approved_at = if(values(ip_approved_at) is null, ip_approved_at, values(ip_approved_at)), + ip_ordered_at = if(values(ip_ordered_at) is null, ip_ordered_at, values(ip_ordered_at)), + ip_notified_at = if(values(ip_notified_at) is null, ip_notified_at, values(ip_notified_at)), + ip_canceled_at = if(values(ip_canceled_at) is null, ip_canceled_at, values(ip_canceled_at)), + ip_updated_at = values(ip_updated_at), + ip_ip = if(values(ip_ip) <> '', values(ip_ip), ip_ip), + ip_event_count = ip_event_count + 1 + $extended_update "; + + $summary_result = sql_query($sql, false); + + if (!$summary_result) { + inicis_pro_audit_error($oid, $stage, $status, 'summary'); + return false; + } + + $event_sql = " insert into `{$tables['event']}` + set ip_oid = '".sql_escape_string($oid)."', + ip_tid = '".sql_escape_string($tid)."', + pe_stage = '".sql_escape_string($stage)."', + pe_status = '".sql_escape_string($status)."', + pe_code = '".sql_escape_string($code)."', + pe_message = '".sql_escape_string($message)."', + pe_source = '".sql_escape_string($source)."', + pe_ip = '".sql_escape_string($remote_ip)."', + pe_created_at = '$now' "; + + $event_result = (bool) sql_query($event_sql, false); + if (!$event_result) { + inicis_pro_audit_error($oid, $stage, $status, 'event'); + if (inicis_pro_audit_column_exists($tables['summary'], 'ip_audit_error')) + sql_query(" update `{$tables['summary']}` set ip_audit_error = '1' where ip_oid = '".sql_escape_string($oid)."' ", false); + } + + // 주문/결제 상태가 요약 테이블에 안전하게 남았다면 통보 재시도 여부는 + // 상세 이벤트 기록 성공과 분리한다. 이벤트 실패는 ip_audit_error로 경고한다. + return (bool) $summary_result; + } +} + +if (!function_exists('inicis_pro_get_iniapi_key')) { + function inicis_pro_get_iniapi_key($pay_type = null) + { + global $default; + + if (!empty($default['de_card_test'])) + return inicis_pro_use_escrow_creds($pay_type) ? 'yERbIlJ3NhTeObsA' : 'ItEQKi3rY7uvDS8l'; + + return isset($default['de_inicis_iniapi_key']) ? trim($default['de_inicis_iniapi_key']) : ''; + } +} + +if (!function_exists('inicis_pro_server_ipv4')) { + function inicis_pro_server_ipv4() + { + $ip = defined('G5_INICIS_PRO_CLIENT_IP') ? G5_INICIS_PRO_CLIENT_IP : (isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : ''); + $ip = trim((string) $ip); + if ($ip === '' || ip2long($ip) === false || strpos($ip, ':') !== false) + return ''; + + return $ip; + } +} + +if (!function_exists('inicis_pro_inquiry')) { + function inicis_pro_inquiry($tid, $oid, $transaction = array()) + { + global $default; + + if (!is_array($transaction)) + $transaction = array(); + $tid = inicis_pro_clean_tid($tid); + $oid = inicis_pro_clean_oid($oid); + $environment = isset($transaction['ip_environment']) ? strtolower($transaction['ip_environment']) : ''; + if (!in_array($environment, array('test', 'live'))) + $environment = inicis_pro_environment(); + $txn_pay_type = isset($transaction['ip_pay_type']) && $transaction['ip_pay_type'] !== '' ? $transaction['ip_pay_type'] : null; + $mid = !empty($transaction['ip_mid']) ? trim($transaction['ip_mid']) : inicis_pro_get_mid($txn_pay_type); + $configured_mid = inicis_pro_get_mid($txn_pay_type); + if ($environment === 'test') + $key = inicis_pro_use_escrow_creds($txn_pay_type) ? 'yERbIlJ3NhTeObsA' : 'ItEQKi3rY7uvDS8l'; + else + $key = isset($default['de_inicis_iniapi_key']) ? trim($default['de_inicis_iniapi_key']) : ''; + $client_ip = inicis_pro_server_ipv4(); + if ($tid === '' && $oid === '') + return array('success' => false, 'result_code' => 'REFERENCE_REQUIRED', 'message' => '승인 TID 또는 주문번호가 없어 KG이니시스 거래조회를 실행할 수 없습니다.', 'data' => array()); + if ($key === '' || $mid === '') + return array('success' => false, 'result_code' => 'CONFIG_REQUIRED', 'message' => 'MID 또는 INIAPI KEY가 설정되지 않았습니다.', 'data' => array()); + if ($mid !== $configured_mid || $environment !== inicis_pro_environment()) + return array('success' => false, 'result_code' => 'TRANSACTION_CONFIG_MISMATCH', 'message' => '거래 당시 MID 또는 결제환경과 현재 설정이 달라 자동 조회하지 않았습니다.', 'data' => array()); + if ($client_ip === '') + return array('success' => false, 'result_code' => 'CLIENT_IP_INVALID', 'message' => 'INIAPI 거래조회에 사용할 서버 IPv4 주소를 확인할 수 없습니다.', 'data' => array()); + if (!function_exists('hash') || !in_array('sha512', hash_algos())) + return array('success' => false, 'result_code' => 'SHA512_REQUIRED', 'message' => '서버에서 SHA-512 해시를 지원하지 않습니다.', 'data' => array()); + + $type = 'Extra'; + $paymethod = 'Inquiry'; + $timestamp = date('YmdHis', G5_SERVER_TIME); + $params = array( + 'type' => $type, + 'paymethod' => $paymethod, + 'timestamp' => $timestamp, + 'clientIp' => $client_ip, + 'mid' => $mid, + 'hashData' => hash('sha512', $key.$type.$paymethod.$timestamp.$client_ip.$mid) + ); + if ($tid !== '') + $params['originalTid'] = $tid; + if ($oid !== '') + $params['oid'] = $oid; + + $host = $environment === 'test' ? 'stginiapi.inicis.com' : 'iniapi.inicis.com'; + $response = inicis_pro_http_post('https://'.$host.'/api/v1/extra', $params, 10); + if (!$response['success']) + return array('success' => false, 'result_code' => 'COMMUNICATION_FAILED', 'message' => inicis_pro_cut($response['error'], 255), 'data' => array()); + + if (!function_exists('json_decode')) + require_once(G5_SHOP_PATH.'/inicis/libs/json_lib.php'); + $data = json_decode($response['body'], true); + if (!is_array($data)) + return array('success' => false, 'result_code' => 'INVALID_RESPONSE', 'message' => 'KG이니시스 거래조회 응답을 해석하지 못했습니다.', 'data' => array()); + + $result_code = isset($data['resultCode']) ? inicis_pro_cut($data['resultCode'], 30) : ''; + $message = isset($data['resultMsg']) ? inicis_pro_cut($data['resultMsg'], 255) : ''; + + return array( + 'success' => $result_code === '00', + 'result_code' => $result_code !== '' ? $result_code : 'INVALID_RESPONSE', + 'message' => $message !== '' ? $message : ($result_code === '00' ? '거래조회 완료' : '거래조회 실패'), + 'data' => $data + ); + } +} + +if (!function_exists('inicis_pro_save_inquiry')) { + function inicis_pro_save_inquiry($oid, $inquiry, $source) + { + $oid = inicis_pro_clean_oid($oid); + if ($oid === '' || !is_array($inquiry) || !inicis_pro_audit_schema_ready()) + return false; + + $tables = inicis_pro_audit_tables(); + $data = isset($inquiry['data']) && is_array($inquiry['data']) ? $inquiry['data'] : array(); + $pg_status = isset($data['status']) ? strtoupper(preg_replace('/[^A-Za-z0-9_]/', '', (string) $data['status'])) : ''; + $pg_amount = isset($data['price']) && preg_match('/^[0-9]+$/', (string) $data['price']) ? (int) $data['price'] : 0; + $pg_tid = isset($data['tid']) ? inicis_pro_clean_tid($data['tid']) : ''; + $result_code = isset($inquiry['result_code']) ? inicis_pro_cut($inquiry['result_code'], 30) : ''; + $message = isset($inquiry['message']) ? inicis_pro_cut($inquiry['message'], 255) : ''; + $now = G5_TIME_YMDHIS; + $before = sql_fetch(" select ip_refund_required from `{$tables['summary']}` where ip_oid = '".sql_escape_string($oid)."' "); + $refund_completed = !empty($before['ip_refund_required']) && $result_code === '00' + && in_array($pg_status, array('1', '9', 'REFUND_COMPLETED', 'C', 'CANCEL', 'DEPOSIT_CANCELED')); + + $saved = sql_query(" update `{$tables['summary']}` + set ip_tid = if(ip_tid = '' and '".sql_escape_string($result_code)."' = '00' and '".sql_escape_string($pg_tid)."' <> '', '".sql_escape_string($pg_tid)."', ip_tid), + ip_pg_status = '".sql_escape_string($pg_status)."', + ip_pg_amount = '$pg_amount', + ip_pg_tid = '".sql_escape_string($pg_tid)."', + ip_pg_result_code = '".sql_escape_string($result_code)."', + ip_pg_message = '".sql_escape_string($message)."', + ip_pg_checked_at = '$now', + ip_status = case + when '".sql_escape_string($result_code)."' = '00' + and '".sql_escape_string($pg_status)."' in ('1','9','REFUND_COMPLETED','C','CANCEL','DEPOSIT_CANCELED') + and ip_refund_required = '1' then 'refund_completed' + else ip_status + end, + ip_refund_required = case + when '".sql_escape_string($result_code)."' = '00' + and '".sql_escape_string($pg_status)."' in ('1','9','REFUND_COMPLETED','C','CANCEL','DEPOSIT_CANCELED') then '0' + else ip_refund_required + end + where ip_oid = '".sql_escape_string($oid)."' ", false); + if (!$saved) { + inicis_pro_audit_error($oid, 'reconcile', !empty($inquiry['success']) ? 'inquiry_success' : 'inquiry_failed', 'inquiry'); + return false; + } + + inicis_pro_audit_write($oid, 'reconcile', !empty($inquiry['success']) ? 'inquiry_success' : 'inquiry_failed', array( + 'tid' => $pg_tid, + 'source' => in_array($source, array('system', 'admin')) ? $source : 'system', + 'code' => $result_code, + 'message' => $message, + 'event_only' => '1' + )); + if ($refund_completed) { + inicis_pro_audit_write($oid, 'reconcile', 'refund_completed', array( + 'tid' => $pg_tid, + 'source' => in_array($source, array('system', 'admin')) ? $source : 'system', + 'code' => $result_code, + 'message' => 'KG 거래조회에서 취소 또는 환불 완료 확인', + 'event_only' => '1' + )); + } + + return true; + } +} + +if (!function_exists('inicis_pro_recover_approved_tid')) { + function inicis_pro_recover_approved_tid($oid, $source) + { + $oid = inicis_pro_clean_oid($oid); + if ($oid === '' || !inicis_pro_audit_schema_ready()) + return ''; + + $tables = inicis_pro_audit_tables(); + $summary = sql_fetch(" select * from `{$tables['summary']}` where ip_oid = '".sql_escape_string($oid)."' "); + if (empty($summary['ip_id'])) + return ''; + if (!empty($summary['ip_tid'])) + return inicis_pro_clean_tid($summary['ip_tid']); + + $log = inicis_pro_get_log($oid); + $data = isset($log['pro_data']) && is_array($log['pro_data']) ? $log['pro_data'] : array(); + $tid = isset($data['P_APPL_TID']) ? inicis_pro_clean_tid($data['P_APPL_TID']) : ''; + $amount = isset($data['P_AMT']) ? (int) $data['P_AMT'] : 0; + $summary_pay_type = isset($summary['ip_pay_type']) && $summary['ip_pay_type'] !== '' ? $summary['ip_pay_type'] : null; + $summary_mid = !empty($summary['ip_mid']) ? $summary['ip_mid'] : inicis_pro_get_mid($summary_pay_type); + if ($tid === '' || empty($data['__pro']) || $data['__pro'] !== '1' + || !isset($log['P_STATUS']) || $log['P_STATUS'] !== '00' + || !isset($data['P_APPL_TID']) || $data['P_APPL_TID'] !== $tid + || !isset($data['P_MID']) || $data['P_MID'] !== $summary_mid + || !isset($data['P_OID']) || $data['P_OID'] !== $oid + || $amount <= 0 || ((int) $summary['ip_amount'] > 0 && (int) $summary['ip_amount'] !== $amount)) + return ''; + + if (!sql_query(" update `{$tables['summary']}` + set ip_tid = '".sql_escape_string($tid)."', + ip_amount = if(ip_amount > 0, ip_amount, '$amount') + where ip_oid = '".sql_escape_string($oid)."' + and ip_tid = '' ", false)) + return ''; + + inicis_pro_audit_write($oid, 'reconcile', 'approval_reference_recovered', array( + 'tid' => $tid, + 'amount' => $amount, + 'source' => in_array($source, array('system', 'admin')) ? $source : 'system', + 'message' => '승인 로그에서 KG 거래조회용 TID 복구', + 'event_only' => '1' + )); + + return $tid; + } +} + +if (!function_exists('inicis_pro_noti_allowed_ips')) { + function inicis_pro_noti_allowed_ips() + { + // KG이니시스 공식 가상계좌 통보 서버 IPv4. 관리자 입력 대신 코드에 고정한다. + $value = '203.238.37.15,118.129.210.25,183.109.71.153'; + + $result = array(); + $items = preg_split('/[\s,;]+/', $value); + foreach ($items as $ip) { + $ip = trim($ip); + if ($ip !== '' && strpos($ip, ':') === false && ip2long($ip) !== false) + $result[$ip] = $ip; + } + + $result = array_values($result); + return function_exists('run_replace') ? run_replace('inicis_pro_noti_allowed_ips', $result) : $result; + } +} + +if (!function_exists('inicis_pro_purge_events')) { + function inicis_pro_purge_events($days, $limit) + { + $days = (int) $days; + $limit = (int) $limit; + if ($days < 30 || $limit < 1 || !inicis_pro_audit_tables_ready()) + return false; + if ($limit > 1000) + $limit = 1000; + + $tables = inicis_pro_audit_tables(); + $cutoff = date('Y-m-d H:i:s', G5_SERVER_TIME - ($days * 86400)); + + return (bool) sql_query(" delete from `{$tables['event']}` + where pe_created_at < '".sql_escape_string($cutoff)."' + order by pe_id asc + limit $limit ", false); + } +} + +if (!function_exists('inicis_pro_purge_payloads')) { + function inicis_pro_purge_payloads($days, $limit) + { + global $g5; + + $days = (int) $days; + $limit = (int) $limit; + if ($days < 30 || $limit < 1) + return false; + if ($limit > 1000) + $limit = 1000; + + $cutoff = date('YmdHis', G5_SERVER_TIME - ($days * 86400)); + + return (bool) sql_query(" update {$g5['g5_shop_inicis_log_table']} + set post_data = '' + where post_data <> '' + and length(P_AUTH_DT) >= 14 + and P_AUTH_DT not regexp '[^0-9]' + and substr(P_AUTH_DT, 1, 14) < '".sql_escape_string($cutoff)."' + limit $limit ", false); + } +} + +if (!function_exists('inicis_pro_purge_summaries')) { + function inicis_pro_purge_summaries($days, $limit) + { + global $g5; + + $days = (int) $days; + $limit = (int) $limit; + if ($days < 365 || $limit < 1 || !inicis_pro_audit_schema_ready()) + return 0; + if ($limit > 200) + $limit = 200; + + $tables = inicis_pro_audit_tables(); + $cutoff = date('Y-m-d H:i:s', G5_SERVER_TIME - ($days * 86400)); + $ids = array(); + $result = sql_query(" select p.ip_id + from `{$tables['summary']}` p + left join {$g5['g5_shop_order_table']} o on p.ip_order_type <> 'personal' and o.od_id = p.ip_oid + left join {$g5['g5_shop_personalpay_table']} pp on p.ip_order_type = 'personal' and pp.pp_id = p.ip_oid + where p.ip_created_at < '".sql_escape_string($cutoff)."' + and p.ip_refund_required = '0' + and p.ip_audit_error = '0' + and o.od_id is null + and pp.pp_id is null + and p.ip_status in ('request_expired','authentication_failed','validation_failed','approval_failed','order_saved','paid','canceled','refund_completed','notification_failed') + order by p.ip_id asc + limit $limit ", false); + if (!$result) + return 0; + while ($row = sql_fetch_array($result)) + $ids[] = (int) $row['ip_id']; + if (!count($ids)) + return 0; + + $id_list = implode(',', $ids); + sql_query(" delete e from `{$tables['event']}` e + inner join `{$tables['summary']}` p on p.ip_oid = e.ip_oid + where p.ip_id in ($id_list) ", false); + if (!sql_query(" delete from `{$tables['summary']}` where ip_id in ($id_list) ", false)) + return 0; + + return count($ids); + } +} + +if (!function_exists('inicis_pro_audit_order_saved')) { + function inicis_pro_audit_order_saved($oid, $tid, $order_type, $source) + { + return inicis_pro_audit_write($oid, 'order', 'order_saved', array( + 'tid' => $tid, + 'order_type' => $order_type, + 'source' => $source, + 'message' => $order_type === 'personal' ? '개인결제 정보 저장 완료' : '영카트 주문 저장 완료' + )); + } +} + +if (!function_exists('inicis_pro_net_cancel')) { + function inicis_pro_net_cancel($idc, $mid, $auth_tid, $amount, $oid, $message, $pay_type = null) + { + $host = inicis_pro_idc_host($idc); + $hashkey = inicis_pro_get_hashkey($pay_type); + if ($host === '' || $hashkey === '') + return array('success' => false, 'data' => array(), 'error' => '망취소 설정이 올바르지 않습니다.'); + + $timestamp = inicis_pro_timestamp(); + $params = array( + 'P_MID' => $mid, + 'P_AUTH_TID' => $auth_tid, + 'P_AMT' => (string) $amount, + 'P_OID' => $oid, + 'P_CANCEL_MSG' => inicis_pro_cut($message, 100), + 'P_TIMESTAMP' => $timestamp, + 'P_CHKFAKE' => inicis_pro_hash($amount, $oid, $timestamp, $hashkey), + 'P_CHARSET' => 'UTF-8' + ); + $response = inicis_pro_http_post('https://'.$host.'/payment/v1/rest/payNetCancel.ini', $params); + if (!$response['success']) + return array('success' => false, 'data' => array(), 'error' => $response['error']); + + $data = inicis_pro_parse_nvp($response['body']); + return array('success' => isset($data['P_STATUS']) && $data['P_STATUS'] === '00', 'data' => $data, 'error' => isset($data['P_RMESG']) ? $data['P_RMESG'] : ''); + } +} diff --git a/shop/inicis/pro/monitor.php b/shop/inicis/pro/monitor.php new file mode 100644 index 000000000..2945868f8 --- /dev/null +++ b/shop/inicis/pro/monitor.php @@ -0,0 +1,61 @@ + '1', + 'source' => 'server', + 'device' => 'SERVER', + 'remote_ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '', + 'code' => $code, + 'message' => '가상계좌 통보 검증 실패' + )); + } + + inicis_pro_noti_result(false, $lock_name); + } +} + +if (!function_exists('inicis_pro_noti_time')) { + function inicis_pro_noti_time($auth_date) + { + $auth_date = preg_replace('/[^0-9]/', '', (string) $auth_date); + if (strlen($auth_date) === 12) + $auth_date = '20'.$auth_date; + if (strlen($auth_date) !== 14) + return ''; + + return substr($auth_date, 0, 4).'-'.substr($auth_date, 4, 2).'-'.substr($auth_date, 6, 2) + .' '.substr($auth_date, 8, 2).':'.substr($auth_date, 10, 2).':'.substr($auth_date, 12, 2); + } +} + +if (!function_exists('inicis_pro_noti_saved_device')) { + function inicis_pro_noti_saved_device($noti, $pro_log, $pro_data) + { + if (!isset($pro_log['P_STATUS']) || $pro_log['P_STATUS'] !== '00' + || empty($pro_data['__pro']) || $pro_data['__pro'] !== '1' + || !isset($pro_data['P_NOTI']) || !inicis_pro_equals($pro_data['P_NOTI'], $noti)) + return ''; + + $device = isset($pro_data['__device']) ? strtoupper((string) $pro_data['__device']) : ''; + + return in_array($device, array('WEB', 'MOBILE'), true) ? $device : ''; + } +} + +if (!function_exists('inicis_pro_noti_approved_payment_exists')) { + function inicis_pro_noti_approved_payment_exists($oid, $tid, $mid, $amount, $pay_type, $pro_log, $pro_data) + { + global $g5; + + if (!isset($pro_log['P_STATUS']) || $pro_log['P_STATUS'] !== '00' + || empty($pro_data['__pro']) || $pro_data['__pro'] !== '1' + || !isset($pro_data['P_MID']) || $pro_data['P_MID'] !== $mid + || !isset($pro_data['P_OID']) || $pro_data['P_OID'] !== $oid + || !isset($pro_data['P_AMT']) || (int) $pro_data['P_AMT'] !== $amount + || !isset($pro_data['P_TYPE']) || strtoupper($pro_data['P_TYPE']) !== $pay_type + || !isset($pro_data['P_APPL_TID']) || inicis_pro_clean_tid($pro_data['P_APPL_TID']) !== $tid + || $pro_data['P_APPL_TID'] !== $tid) + return false; + + $oid_sql = sql_escape_string($oid); + $tid_sql = sql_escape_string($tid); + $order = sql_fetch(" select od_id, od_receipt_price + from {$g5['g5_shop_order_table']} + where od_id = '$oid_sql' + and od_tno = '$tid_sql' + and od_pg = 'inicis' "); + if (!empty($order['od_id']) && (int) $order['od_receipt_price'] === $amount) + return true; + + $personal = sql_fetch(" select pp_id, pp_receipt_price + from {$g5['g5_shop_personalpay_table']} + where pp_id = '$oid_sql' + and pp_tno = '$tid_sql' + and pp_pg = 'inicis' + and pp_use = '1' "); + + return !empty($personal['pp_id']) && (int) $personal['pp_receipt_price'] === $amount; + } +} + +if (!function_exists('inicis_pro_noti_save_audit')) { + function inicis_pro_noti_save_audit($oid, $status, $auth_date, $remote_ip, $payment_status, $payment_message) + { + $log = inicis_pro_get_log($oid); + $data = isset($log['pro_data']) && is_array($log['pro_data']) ? $log['pro_data'] : array(); + if (empty($data['__pro']) || $data['__pro'] !== '1') + return false; + + $data['__noti_status'] = $status; + $data['__noti_auth_dt'] = preg_replace('/[^0-9]/', '', (string) $auth_date); + $data['__noti_received_at'] = G5_TIME_YMDHIS; + $data['__noti_ip'] = $remote_ip; + if (!empty($_POST['P_IN_TID'])) + $data['P_IN_TID'] = inicis_pro_clean_tid($_POST['P_IN_TID']); + + $saved = inicis_pro_save_log($data); + $audit_saved = false; + if ($saved) { + $audit_saved = inicis_pro_audit_write($oid, 'notification', $payment_status, array( + 'tid' => isset($data['P_APPL_TID']) ? $data['P_APPL_TID'] : '', + 'auth_tid' => isset($data['P_AUTH_TID']) ? $data['P_AUTH_TID'] : '', + 'mid' => isset($data['P_MID']) ? $data['P_MID'] : '', + 'amount' => isset($data['P_AMT']) ? (int) $data['P_AMT'] : 0, + 'pay_type' => 'VBANK', + 'device' => 'SERVER', + 'source' => 'server', + 'remote_ip' => $remote_ip, + 'code' => $status, + 'refund_required' => $payment_status === 'paid_after_cancel' ? '1' : '', + 'message' => $payment_message + )); + } + + return $saved && $audit_saved; + } +} + +if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'POST') + inicis_pro_noti_result(false, ''); + +$remote_ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; +$allowed_ips = inicis_pro_noti_allowed_ips(); +if (!in_array($remote_ip, $allowed_ips, true)) + inicis_pro_noti_result(false, ''); + +$status = isset($_POST['P_STATUS']) ? trim((string) $_POST['P_STATUS']) : ''; +$mid = isset($_POST['P_MID']) ? trim((string) $_POST['P_MID']) : ''; +$oid_raw = isset($_POST['P_OID']) ? trim((string) $_POST['P_OID']) : ''; +$oid = inicis_pro_clean_oid($oid_raw); +$tid_raw = isset($_POST['P_TID']) ? trim((string) $_POST['P_TID']) : ''; +$tid = inicis_pro_clean_tid($tid_raw); +$amount_raw = isset($_POST['P_AMT']) ? trim((string) $_POST['P_AMT']) : ''; +$pay_type = isset($_POST['P_TYPE']) ? strtoupper(trim((string) $_POST['P_TYPE'])) : ''; +$noti = isset($_POST['P_NOTI']) ? trim((string) $_POST['P_NOTI']) : ''; +$auth_date = isset($_POST['P_AUTH_DT']) ? $_POST['P_AUTH_DT'] : ''; +$hashkey = inicis_pro_get_hashkey($pay_type); +$amount = preg_match('/^[0-9]+$/', $amount_raw) ? (int) $amount_raw : 0; +$pro_log = array(); +$pro_data = array(); +if ($oid !== '' && $oid === $oid_raw) { + $pro_log = inicis_pro_get_log($oid); + $pro_data = isset($pro_log['pro_data']) && is_array($pro_log['pro_data']) ? $pro_log['pro_data'] : array(); +} + +$expected_mid = inicis_pro_get_mid($pay_type); +if (isset($pro_log['P_STATUS']) && $pro_log['P_STATUS'] === '00' + && !empty($pro_data['__pro']) && $pro_data['__pro'] === '1' && !empty($pro_data['P_MID'])) + $expected_mid = trim((string) $pro_data['P_MID']); + +if ($mid === '') + inicis_pro_noti_fail('MID_EMPTY', $oid, ''); +if ($mid !== $expected_mid) + inicis_pro_noti_fail('MID_MISMATCH', $oid, ''); +if ($oid === '' || $oid !== $oid_raw) + inicis_pro_noti_fail('OID_INVALID', $oid, ''); +if ($tid === '' || $tid !== $tid_raw) + inicis_pro_noti_fail('TID_INVALID', $oid, ''); +if ($amount <= 0) + inicis_pro_noti_fail('AMOUNT_INVALID', $oid, ''); +if ($pay_type === '' || !preg_match('/^[A-Z0-9_]+$/', $pay_type)) + inicis_pro_noti_fail('TYPE_INVALID', $oid, ''); + +$device = inicis_pro_noti_saved_device($noti, $pro_log, $pro_data); +if ($device === '' && $hashkey !== '') + $device = inicis_pro_check_noti($noti, $oid, $amount, $hashkey); +if ($device === '' && $hashkey === '') + inicis_pro_noti_fail('HASHKEY_EMPTY', $oid, ''); +if ($device === '') + inicis_pro_noti_fail('NOTI_SIGNATURE_INVALID', $oid, ''); + +// 과거 요청에서 모든 결제수단에 통보 URL을 전달한 경우 카드 등 승인 통보도 +// 이 URL로 재전송된다. 이미 승인과 주문 저장이 끝난 동일 거래는 변경 없이 확인한다. +if ($pay_type !== 'VBANK') { + if ($status !== '00') + inicis_pro_noti_fail('NON_VBANK_STATUS_INVALID', $oid, ''); + if (!inicis_pro_noti_approved_payment_exists($oid, $tid, $mid, $amount, $pay_type, $pro_log, $pro_data)) + inicis_pro_noti_fail('NON_VBANK_ORDER_MISMATCH', $oid, ''); + + inicis_pro_audit_write($oid, 'notification', 'notification_acknowledged', array( + 'event_only' => '1', + 'tid' => $tid, + 'mid' => $mid, + 'amount' => $amount, + 'pay_type' => $pay_type, + 'device' => 'SERVER', + 'source' => 'server', + 'remote_ip' => $remote_ip, + 'code' => $status, + 'message' => '이미 처리된 결제의 서버 통보 확인' + )); + inicis_pro_noti_result(true, ''); +} + +// 가상계좌 채번 통보는 브라우저 승인 처리와 경쟁시키지 않고 확인 응답만 한다. +if ($status === '00') { + $issued_saved = inicis_pro_audit_write($oid, 'notification', 'vbank_issued', array( + 'tid' => $tid, + 'mid' => $mid, + 'amount' => $amount, + 'pay_type' => 'VBANK', + 'device' => 'SERVER', + 'source' => 'server', + 'remote_ip' => $remote_ip, + 'code' => $status, + 'vbank_due_at' => !empty($pro_data['P_VACT_DATE']) + ? substr($pro_data['P_VACT_DATE'], 0, 4).'-'.substr($pro_data['P_VACT_DATE'], 4, 2).'-'.substr($pro_data['P_VACT_DATE'], 6, 2).' ' + .(!empty($pro_data['P_VACT_TIME']) ? substr($pro_data['P_VACT_TIME'].'000000', 0, 2).':'.substr($pro_data['P_VACT_TIME'].'000000', 2, 2).':'.substr($pro_data['P_VACT_TIME'].'000000', 4, 2) : '23:59:00') + : '', + 'message' => '가상계좌 발급 통보 수신' + )); + inicis_pro_noti_result($issued_saved, ''); +} +if ($status !== '02') + inicis_pro_noti_fail('STATUS_UNSUPPORTED', $oid, ''); + +$in_tid_raw = isset($_POST['P_IN_TID']) ? trim((string) $_POST['P_IN_TID']) : ''; +$in_tid = inicis_pro_clean_tid($in_tid_raw); +// 상점관리자의 입금통보 테스트 등에서는 P_IN_TID가 빈 값으로 올 수 있다. +// 주문 식별에는 채번 TID(P_TID)를 사용하고, 입금 TID는 전달된 경우에만 형식을 검증한다. +if ($in_tid_raw !== '' && ($in_tid === '' || $in_tid !== $in_tid_raw)) + inicis_pro_noti_fail('IN_TID_FORMAT_INVALID', $oid, ''); + +$receipt_time = inicis_pro_noti_time($auth_date); +if ($receipt_time === '') + inicis_pro_noti_fail('AUTH_TIME_INVALID', $oid, ''); + +$lock_name = inicis_pro_lock($oid); +if ($lock_name === '') + inicis_pro_noti_fail('LOCK_BUSY', $oid, ''); + +$approved_tid = isset($pro_data['P_APPL_TID']) ? inicis_pro_clean_tid($pro_data['P_APPL_TID']) : ''; +if (empty($pro_data['__pro']) || $pro_data['__pro'] !== '1') + inicis_pro_noti_fail('APPROVAL_LOG_INVALID', $oid, $lock_name); +if (!isset($pro_log['P_STATUS']) || $pro_log['P_STATUS'] !== '00') + inicis_pro_noti_fail('APPROVAL_STATUS_INVALID', $oid, $lock_name); +if (!isset($pro_data['P_MID']) || $pro_data['P_MID'] !== $mid) + inicis_pro_noti_fail('APPROVAL_MID_MISMATCH', $oid, $lock_name); +if (!isset($pro_data['P_OID']) || $pro_data['P_OID'] !== $oid) + inicis_pro_noti_fail('APPROVAL_OID_MISMATCH', $oid, $lock_name); +if (!isset($pro_data['P_AMT']) || (int) $pro_data['P_AMT'] !== $amount) + inicis_pro_noti_fail('APPROVAL_AMOUNT_MISMATCH', $oid, $lock_name); +if (!isset($pro_data['P_TYPE']) || strtoupper($pro_data['P_TYPE']) !== 'VBANK') + inicis_pro_noti_fail('APPROVAL_TYPE_MISMATCH', $oid, $lock_name); +if ($approved_tid !== $tid) + inicis_pro_noti_fail('APPROVAL_TID_MISMATCH', $oid, $lock_name); + +// 환불 완료 후 동일 입금통보가 재전송되면 환불 필요 상태를 다시 만들지 않는다. +$audit_tables = inicis_pro_audit_tables(); +$completed_refund = sql_fetch(" select ip_status, ip_refund_required, ip_tid, ip_amount + from `{$audit_tables['summary']}` + where ip_oid = '".sql_escape_string($oid)."' ", false); +if (!empty($completed_refund['ip_status']) && $completed_refund['ip_status'] === 'refund_completed' + && empty($completed_refund['ip_refund_required']) + && $completed_refund['ip_tid'] === $tid && (int) $completed_refund['ip_amount'] === $amount) { + $acknowledged = inicis_pro_audit_write($oid, 'notification', 'notification_acknowledged', array( + 'event_only' => '1', + 'tid' => $tid, + 'mid' => $mid, + 'amount' => $amount, + 'pay_type' => 'VBANK', + 'device' => 'SERVER', + 'source' => 'server', + 'remote_ip' => $remote_ip, + 'code' => $status, + 'message' => '환불 완료 거래의 중복 입금 통보 확인' + )); + inicis_pro_noti_result($acknowledged, $lock_name); +} + +$received_saved = inicis_pro_audit_write($oid, 'notification', 'notification_received', array( + 'tid' => $tid, + 'auth_tid' => isset($pro_data['P_AUTH_TID']) ? $pro_data['P_AUTH_TID'] : '', + 'mid' => $mid, + 'amount' => $amount, + 'pay_type' => 'VBANK', + 'device' => 'SERVER', + 'order_type' => !empty($pro_data['__order_type']) ? $pro_data['__order_type'] : '', + 'source' => 'server', + 'remote_ip' => $remote_ip, + 'code' => $status, + 'message' => '가상계좌 입금 통보 수신' +)); +if (!$received_saved) + inicis_pro_noti_fail('AUDIT_SUMMARY_FAILED', $oid, $lock_name); + +$oid_sql = sql_escape_string($oid); +$tid_sql = sql_escape_string($tid); +$time_sql = sql_escape_string($receipt_time); +$personal = sql_fetch(" select * from {$g5['g5_shop_personalpay_table']} + where pp_id = '$oid_sql' + and pp_tno = '$tid_sql' + and pp_pg = 'inicis' + and pp_settle_case = '가상계좌' + and pp_use = '1' "); + +if (!empty($personal['pp_id'])) { + if ((int) $personal['pp_price'] !== $amount + || ((int) $personal['pp_receipt_price'] !== 0 && (int) $personal['pp_receipt_price'] !== $amount)) + inicis_pro_noti_fail('PERSONAL_AMOUNT_MISMATCH', $oid, $lock_name); + + // 연결 주문은 메모의 고유 표식으로 중복 가산을 막는다. + if (!empty($personal['od_id'])) { + $linked_id = inicis_pro_clean_oid($personal['od_id']); + $linked = sql_fetch(" select od_id, od_status, od_cancel_price, od_shop_memo from {$g5['g5_shop_order_table']} where od_id = '".sql_escape_string($linked_id)."' "); + if (empty($linked['od_id'])) + inicis_pro_noti_fail('LINKED_ORDER_MISSING', $oid, $lock_name); + if ($linked['od_status'] === '취소' || (int) $linked['od_cancel_price'] > 0) { + $processed = inicis_pro_noti_save_audit($oid, $status, $auth_date, $remote_ip, 'paid_after_cancel', '취소된 연결 주문에 가상계좌 입금 확인 - 환불 필요'); + inicis_pro_noti_result($processed, $lock_name); + } + + $marker = '[INIpay PRO personalpay '.$oid.']'; + if (strpos($linked['od_shop_memo'], $marker) === false) { + $memo = "\n".$marker.' 입금완료 - '.$receipt_time; + $update_linked = " update {$g5['g5_shop_order_table']} + set od_receipt_price = od_receipt_price + '$amount', + od_receipt_time = '$time_sql', + od_shop_memo = concat(od_shop_memo, '".sql_escape_string($memo)."') + where od_id = '".sql_escape_string($linked_id)."' "; + if (!sql_query($update_linked, false)) + inicis_pro_noti_fail('LINKED_ORDER_RECEIPT_UPDATE_FAILED', $oid, $lock_name); + } + + $linked_info = get_order_info($linked_id); + $linked_status_sql = " update {$g5['g5_shop_order_table']} set od_misu = '".(int) $linked_info['od_misu']."' "; + if ((int) $linked_info['od_misu'] === 0) + $linked_status_sql .= ", od_status = '입금' "; + $linked_status_sql .= " where od_id = '".sql_escape_string($linked_id)."' "; + if (!sql_query($linked_status_sql, false)) + inicis_pro_noti_fail('LINKED_ORDER_STATUS_UPDATE_FAILED', $oid, $lock_name); + + if ((int) $linked_info['od_misu'] === 0 + && !sql_query(" update {$g5['g5_shop_cart_table']} set ct_status = '입금' where od_id = '".sql_escape_string($linked_id)."' ", false)) + inicis_pro_noti_fail('LINKED_CART_STATUS_UPDATE_FAILED', $oid, $lock_name); + } + + if (!sql_query(" update {$g5['g5_shop_personalpay_table']} + set pp_receipt_price = '$amount', + pp_receipt_time = '$time_sql' + where pp_id = '$oid_sql' + and pp_tno = '$tid_sql' ", false)) + inicis_pro_noti_fail('PERSONAL_RECEIPT_UPDATE_FAILED', $oid, $lock_name); + + $check_personal = sql_fetch(" select pp_receipt_price from {$g5['g5_shop_personalpay_table']} where pp_id = '$oid_sql' and pp_tno = '$tid_sql' "); + $processed = isset($check_personal['pp_receipt_price']) && (int) $check_personal['pp_receipt_price'] === $amount; + if ($processed) + $processed = inicis_pro_noti_save_audit($oid, $status, $auth_date, $remote_ip, 'paid', '가상계좌 입금 통보 처리 완료'); + else + inicis_pro_noti_fail('PERSONAL_RECEIPT_VERIFY_FAILED', $oid, $lock_name); + inicis_pro_noti_result($processed, $lock_name); +} + +$order = sql_fetch(" select * from {$g5['g5_shop_order_table']} + where od_id = '$oid_sql' + and od_tno = '$tid_sql' + and od_pg = 'inicis' + and od_settle_case = '가상계좌' "); +if (empty($order['od_id'])) + inicis_pro_noti_fail('ORDER_MISSING', $oid, $lock_name); +if ($order['od_status'] === '취소' || (int) $order['od_cancel_price'] > 0) { + $processed = inicis_pro_noti_save_audit($oid, $status, $auth_date, $remote_ip, 'paid_after_cancel', '취소 주문에 가상계좌 입금 확인 - 환불 필요'); + inicis_pro_noti_result($processed, $lock_name); +} +if ((int) $order['od_receipt_price'] !== 0 && (int) $order['od_receipt_price'] !== $amount) + inicis_pro_noti_fail('ORDER_RECEIPT_MISMATCH', $oid, $lock_name); + +if (!sql_query(" update {$g5['g5_shop_order_table']} + set od_receipt_price = '$amount', + od_receipt_time = '$time_sql' + where od_id = '$oid_sql' + and od_tno = '$tid_sql' ", false)) + inicis_pro_noti_fail('ORDER_RECEIPT_UPDATE_FAILED', $oid, $lock_name); + +$order_info = get_order_info($oid); +$status_sql = " update {$g5['g5_shop_order_table']} set od_misu = '".(int) $order_info['od_misu']."' "; +if ((int) $order_info['od_misu'] === 0) + $status_sql .= ", od_status = '입금' "; +$status_sql .= " where od_id = '$oid_sql' and od_tno = '$tid_sql' "; +if (!sql_query($status_sql, false)) + inicis_pro_noti_fail('ORDER_STATUS_UPDATE_FAILED', $oid, $lock_name); + +if ((int) $order_info['od_misu'] === 0 + && !sql_query(" update {$g5['g5_shop_cart_table']} set ct_status = '입금' where od_id = '$oid_sql' ", false)) + inicis_pro_noti_fail('CART_STATUS_UPDATE_FAILED', $oid, $lock_name); + +$check_order = sql_fetch(" select od_receipt_price, od_misu from {$g5['g5_shop_order_table']} where od_id = '$oid_sql' and od_tno = '$tid_sql' "); +$processed = isset($check_order['od_receipt_price']) && (int) $check_order['od_receipt_price'] === $amount; +if ($processed) + $processed = inicis_pro_noti_save_audit($oid, $status, $auth_date, $remote_ip, 'paid', '가상계좌 입금 통보 처리 완료'); +else + inicis_pro_noti_fail('ORDER_RECEIPT_VERIFY_FAILED', $oid, $lock_name); +inicis_pro_noti_result($processed, $lock_name); diff --git a/shop/inicis/pro/orderform.1.php b/shop/inicis/pro/orderform.1.php new file mode 100644 index 000000000..1864f6dbb --- /dev/null +++ b/shop/inicis/pro/orderform.1.php @@ -0,0 +1,52 @@ +', 10); + +if (G5_IS_MOBILE) { +?> +
    + +
    + + diff --git a/shop/inicis/pro/pay_result.php b/shop/inicis/pro/pay_result.php new file mode 100644 index 000000000..5da58a3ac --- /dev/null +++ b/shop/inicis/pro/pay_result.php @@ -0,0 +1,227 @@ + 0) { + $old_cancel = inicis_pro_net_cancel($old_idc, $expected_mid, $old_auth_tid, $old_amount, $oid, 'stale authorization', $pay_type); + $old_cancel_ok = !empty($old_cancel['success']); + } + $new_cancel = inicis_pro_net_cancel($idc, $expected_mid, $auth_tid, $expected_amount, $oid, 'duplicate authorization', $pay_type); + inicis_pro_return_error('동일 주문의 다른 승인 처리가 확인되어 안전을 위해 결제를 중단했습니다.', $page_return_url, $lock_name, $oid, 'cancel', $old_cancel_ok && !empty($new_cancel['success']) ? 'canceled' : 'cancel_failed', 'CONCURRENT_AUTH', $audit_values); + } + + if (empty($approval)) { + inicis_pro_audit_write($oid, 'approval', 'approval_started', $audit_values); + $processing = array( + 'P_STATUS' => 'processing', + 'P_RMESG' => 'approval request started', + 'P_MID' => $expected_mid, + 'P_OID' => $oid, + 'P_AUTH_TID' => $auth_tid, + 'P_AMT' => (string) $expected_amount, + 'P_TYPE' => $result_type, + 'P_NOTI' => $noti, + 'P_IDCNAME' => $idc, + '__pro' => '1', + '__device' => $device, + '__environment' => inicis_pro_environment(), + '__easy_pay' => inicis_pro_easypay_name($settle_case), + '__order_type' => $is_personal ? 'personal' : 'order' + ); + if (!inicis_pro_save_log($processing)) + inicis_pro_return_error('결제 승인 준비정보를 저장하지 못했습니다.', $page_return_url, $lock_name, $oid, 'approval', 'approval_failed', 'APPROVAL_LOG_FAILED', $audit_values); + + $approval_response = inicis_pro_http_post( + 'https://'.inicis_pro_idc_host($idc).'/payment/v1/rest/payAppl.ini', + array( + 'P_MID' => $expected_mid, + 'P_AUTH_TID' => $auth_tid, + 'P_AMT' => (string) $expected_amount, + 'P_CHARSET' => 'UTF-8' + ) + ); + if (!$approval_response['success']) { + $communication_cancel = inicis_pro_net_cancel($idc, $expected_mid, $auth_tid, $expected_amount, $oid, 'approval communication failure', $pay_type); + $communication_status = !empty($communication_cancel['success']) ? 'cancel' : 'fail'; + sql_query(" update {$g5['g5_shop_inicis_log_table']} set P_STATUS = '$communication_status', P_RMESG1 = 'approval communication failure' where oid = '".sql_escape_string($oid)."' ", false); + inicis_pro_return_error('KG이니시스 승인 서버와 통신하지 못했습니다. 결제 여부를 관리자에게 문의해 주십시오.', $page_return_url, $lock_name, $oid, 'approval', !empty($communication_cancel['success']) ? 'canceled' : 'communication_failed', 'APPROVAL_COMMUNICATION', $audit_values); + } + + $approval = inicis_pro_parse_nvp($approval_response['body']); + $approval_message = isset($approval['P_RMESG']) ? inicis_pro_cut($approval['P_RMESG'], 150) : ''; + if (!isset($approval['P_STATUS']) || $approval['P_STATUS'] !== '00') { + $processing['P_STATUS'] = isset($approval['P_STATUS']) ? $approval['P_STATUS'] : 'fail'; + $processing['P_RMESG'] = $approval_message !== '' ? $approval_message : 'approval failed'; + inicis_pro_save_log($processing); + inicis_pro_return_error('결제 승인 실패: '.$processing['P_RMESG'], $page_return_url, $lock_name, $oid, 'approval', 'approval_failed', $processing['P_STATUS'], $audit_values); + } + + $approved_tid = isset($approval['P_APPL_TID']) ? inicis_pro_clean_tid($approval['P_APPL_TID']) : ''; + $approved_type = isset($approval['P_TYPE']) ? strtoupper($approval['P_TYPE']) : ''; + $approved_amount = isset($approval['P_AMT']) ? $approval['P_AMT'] : ''; + $approved_oid = isset($approval['P_OID']) ? inicis_pro_clean_oid($approval['P_OID']) : ''; + $approved_auth_tid = isset($approval['P_AUTH_TID']) ? inicis_pro_clean_tid($approval['P_AUTH_TID']) : ''; + $approval_valid = isset($approval['P_MID']) && $approval['P_MID'] === $expected_mid + && $approved_oid === $oid && isset($approval['P_OID']) && $approval['P_OID'] === $approved_oid + && preg_match('/^[0-9]+$/', $approved_amount) && (int) $approved_amount === (int) $expected_amount + && $approved_auth_tid === $auth_tid && isset($approval['P_AUTH_TID']) && $approval['P_AUTH_TID'] === $approved_auth_tid + && $approved_tid !== '' && isset($approval['P_APPL_TID']) && $approval['P_APPL_TID'] === $approved_tid + && inicis_pro_result_type_matches($settle_case, $approved_type); + + if (!$approval_valid) { + $mismatch_cancel = inicis_pro_net_cancel($idc, $expected_mid, $auth_tid, $expected_amount, $oid, 'approval result mismatch', $pay_type); + $processing['P_STATUS'] = !empty($mismatch_cancel['success']) ? 'cancel' : 'mismatch'; + $processing['P_RMESG'] = 'approval result mismatch'; + inicis_pro_save_log($processing); + inicis_pro_return_error('승인 결과가 결제요청 정보와 일치하지 않아 주문을 중단했습니다. 취소 여부는 관리자에게 문의해 주십시오.', $page_return_url, $lock_name, $oid, 'validation', !empty($mismatch_cancel['success']) ? 'canceled' : 'cancel_failed', 'APPROVAL_RESULT_MISMATCH', $audit_values); + } + + $approval['P_NOTI'] = $noti; + $approval['P_IDCNAME'] = $idc; + $approval['__pro'] = '1'; + $approval['__device'] = $device; + $approval['__environment'] = inicis_pro_environment(); + $approval['__easy_pay'] = inicis_pro_easypay_name($settle_case); + $approval['__order_type'] = $is_personal ? 'personal' : 'order'; + if (!inicis_pro_save_log($approval)) { + $approval_log_cancel = inicis_pro_net_cancel($idc, $expected_mid, $auth_tid, $expected_amount, $oid, 'approval log failure', $pay_type); + inicis_pro_return_error('승인정보를 저장하지 못해 결제를 취소했습니다.', $page_return_url, $lock_name, $oid, 'cancel', !empty($approval_log_cancel['success']) ? 'canceled' : 'cancel_failed', 'APPROVAL_LOG_FAILED', $audit_values); + } + } + + $audit_values['tid'] = isset($approval['P_APPL_TID']) ? $approval['P_APPL_TID'] : ''; + $audit_values['pay_type'] = isset($approval['P_TYPE']) ? $approval['P_TYPE'] : $audit_values['pay_type']; + $audit_values['code'] = isset($approval['P_STATUS']) ? $approval['P_STATUS'] : ''; + $audit_values['message'] = isset($approval['P_RMESG']) ? $approval['P_RMESG'] : 'KG이니시스 승인 완료'; + + $final_tid = isset($approval['P_APPL_TID']) ? inicis_pro_clean_tid($approval['P_APPL_TID']) : ''; + if ($final_tid === '' || !isset($approval['P_APPL_TID']) || $approval['P_APPL_TID'] !== $final_tid + || !isset($approval['P_STATUS']) || $approval['P_STATUS'] !== '00' + || !isset($approval['P_MID']) || $approval['P_MID'] !== $expected_mid + || !isset($approval['P_OID']) || $approval['P_OID'] !== $oid + || !isset($approval['P_AMT']) || (int) $approval['P_AMT'] !== (int) $expected_amount + || !isset($approval['P_AUTH_TID']) || $approval['P_AUTH_TID'] !== $auth_tid + || !isset($approval['P_NOTI']) || !inicis_pro_equals($approval['P_NOTI'], $noti) + || !isset($approval['P_IDCNAME']) || $approval['P_IDCNAME'] !== $idc + || !isset($approval['__device']) || $approval['__device'] !== $device + || !isset($approval['P_TYPE']) || !inicis_pro_result_type_matches($settle_case, $approval['P_TYPE'])) { + inicis_pro_return_error('저장된 승인정보를 검증하지 못했습니다.', $page_return_url, $lock_name, $oid, 'validation', 'validation_failed', 'SAVED_APPROVAL_INVALID', $audit_values); + } + + inicis_pro_audit_write($oid, 'approval', 'approved', $audit_values); + + $_POST['P_TYPE'] = isset($approval['P_TYPE']) ? $approval['P_TYPE'] : ''; + $_POST['P_AUTH_DT'] = (isset($approval['P_APPL_DT']) ? $approval['P_APPL_DT'] : '').(isset($approval['P_APPL_TM']) ? $approval['P_APPL_TM'] : ''); + $_POST['P_AUTH_NO'] = isset($approval['P_APPL_NO']) ? $approval['P_APPL_NO'] : ''; + $_POST['P_HASH'] = md5($final_tid.$expected_mid.$expected_amount); + + set_session('P_TID', $final_tid); + set_session('P_AMT', (string) $expected_amount); + set_session('P_HASH', $_POST['P_HASH']); + set_session('ss_inicis_pro_oid', $oid); +} + + +$pro_oid = isset($_POST['inicis_pro_oid']) ? inicis_pro_clean_oid($_POST['inicis_pro_oid']) : ''; +$pro_log = inicis_pro_get_log($pro_oid); +$pro_data = isset($pro_log['pro_data']) && is_array($pro_log['pro_data']) ? $pro_log['pro_data'] : array(); + +if ($pro_oid === '' || empty($pro_data['__pro']) || $pro_data['__pro'] !== '1' || !isset($pro_log['P_STATUS']) || $pro_log['P_STATUS'] !== '00') + alert('승인된 INIpay PRO 결제정보를 확인할 수 없습니다.'); + +$pro_tid = isset($pro_data['P_APPL_TID']) ? inicis_pro_clean_tid($pro_data['P_APPL_TID']) : ''; +$pro_mid = isset($pro_data['P_MID']) ? $pro_data['P_MID'] : ''; +$pro_amount = isset($pro_data['P_AMT']) ? (int) $pro_data['P_AMT'] : 0; +$pro_hash = md5($pro_tid.$pro_mid.$pro_amount); +$post_hash = isset($_POST['P_HASH']) ? (string) $_POST['P_HASH'] : ''; + +if ($pro_tid === '' || $pro_mid !== inicis_pro_get_mid(isset($pro_data['P_TYPE']) ? $pro_data['P_TYPE'] : null) || !isset($pro_data['P_OID']) || $pro_data['P_OID'] !== $pro_oid + || !inicis_pro_equals($pro_hash, $post_hash) || !inicis_pro_equals($pro_hash, (string) get_session('P_HASH')) + || (string) get_session('P_TID') !== $pro_tid || (int) get_session('P_AMT') !== $pro_amount + || (string) get_session('ss_inicis_pro_oid') !== $pro_oid) + alert('INIpay PRO 승인정보가 주문정보와 일치하지 않습니다.'); + +$tno = $pro_tid; +$amount = $pro_amount; +$app_time = (isset($pro_data['P_APPL_DT']) ? preg_replace('/[^0-9]/', '', $pro_data['P_APPL_DT']) : '') + .(isset($pro_data['P_APPL_TM']) ? preg_replace('/[^0-9]/', '', $pro_data['P_APPL_TM']) : ''); +$pay_method = isset($pro_data['P_TYPE']) ? strtoupper($pro_data['P_TYPE']) : ''; +$pay_type = ''; +switch ($pay_method) { + case 'CARD': + $pay_type = '신용카드'; + break; + case 'BANK': + $pay_type = '계좌이체'; + break; + case 'VBANK': + $pay_type = '가상계좌'; + break; + case 'HPP': + case 'MOBILE': + $pay_type = '휴대폰'; + break; +} + +$depositor = isset($pro_data['P_VACT_NAME']) ? $pro_data['P_VACT_NAME'] : (isset($pro_data['P_UNAME']) ? $pro_data['P_UNAME'] : ''); +$commid = isset($pro_data['P_HPP_CORP']) ? $pro_data['P_HPP_CORP'] : (isset($pro_data['P_FN_NM']) ? $pro_data['P_FN_NM'] : ''); +$mobile_no = isset($pro_data['P_HPP_NUM']) ? $pro_data['P_HPP_NUM'] : (isset($pro_data['P_APPL_NUM']) ? $pro_data['P_APPL_NUM'] : ''); +$app_no = isset($pro_data['P_APPL_NO']) ? $pro_data['P_APPL_NO'] : ''; +$card_name = isset($pro_data['P_CARD_ISSUER_NAME']) && $pro_data['P_CARD_ISSUER_NAME'] !== '' ? $pro_data['P_CARD_ISSUER_NAME'] : (isset($pro_data['P_FN_NM']) ? $pro_data['P_FN_NM'] : ''); +$bank_name = isset($pro_data['P_FN_NM']) ? $pro_data['P_FN_NM'] : ''; +$bankname = $bank_name; +$account = isset($pro_data['P_VACT_NUM']) ? $pro_data['P_VACT_NUM'] : ''; +if (!empty($pro_data['P_VACT_NAME'])) + $account .= ' '.$pro_data['P_VACT_NAME']; +if ($pay_method === 'VBANK' && !empty($pro_data['P_VACT_NUM'])) + $app_no = $pro_data['P_VACT_NUM']; + +if (!empty($default['de_escrow_use']) && ($pay_method === 'BANK' || $pay_method === 'VBANK')) + $escw_yn = 'Y'; + +set_session('P_TID', ''); +set_session('P_AMT', ''); +set_session('P_HASH', ''); +set_session('ss_inicis_pro_oid', ''); diff --git a/shop/inicis/pro/request.php b/shop/inicis/pro/request.php new file mode 100644 index 000000000..175af8227 --- /dev/null +++ b/shop/inicis/pro/request.php @@ -0,0 +1,148 @@ + false, 'message' => '올바른 요청이 아닙니다.')); + +if (function_exists('check_request_origin')) + check_request_origin(G5_SHOP_URL); + +if (!inicis_pro_is_enabled()) + inicis_pro_json_response(array('success' => false, 'message' => 'INIpay PRO 결제가 활성화되어 있지 않습니다.')); + +$oid = isset($_POST['oid']) ? inicis_pro_clean_oid($_POST['oid']) : ''; +$device = isset($_POST['device_type']) ? strtoupper(preg_replace('/[^A-Z]/i', '', $_POST['device_type'])) : ''; +if ($oid === '' || ($device !== 'WEB' && $device !== 'MOBILE')) + inicis_pro_json_response(array('success' => false, 'message' => '결제 요청 정보가 올바르지 않습니다.')); + +$sql = " select * from {$g5['g5_shop_order_data_table']} + where od_id = '".sql_escape_string($oid)."' + and dt_pg = 'inicis' + order by dt_time desc + limit 1 "; +$temp_row = sql_fetch($sql); +if (empty($temp_row['od_id'])) + inicis_pro_json_response(array('success' => false, 'message' => '임시 주문정보가 저장되지 않았습니다.')); + +$order_data = @unserialize(base64_decode($temp_row['dt_data'])); +if (!is_array($order_data)) + inicis_pro_json_response(array('success' => false, 'message' => '임시 주문정보를 확인할 수 없습니다.')); + +$is_personal = !empty($order_data['pp_id']); +if ($is_personal) { + if ((string) get_session('ss_personalpay_id') !== (string) $oid) + inicis_pro_json_response(array('success' => false, 'message' => '개인결제 세션이 일치하지 않습니다.')); +} else { + if ((string) get_session('ss_order_id') !== (string) $oid) + inicis_pro_json_response(array('success' => false, 'message' => '주문 세션이 일치하지 않습니다.')); + + $session_member_id = isset($member['mb_id']) ? (string) $member['mb_id'] : ''; + if ((string) $temp_row['mb_id'] !== $session_member_id) + inicis_pro_json_response(array('success' => false, 'message' => '주문 회원정보가 일치하지 않습니다.')); + + $cart_id = preg_replace('/[^0-9]/', '', (string) $temp_row['cart_id']); + if ($cart_id === '' || get_cart_count($cart_id) === 0) + inicis_pro_json_response(array('success' => false, 'message' => '결제할 장바구니 상품이 없습니다.')); + if (function_exists('before_check_cart_price') && !before_check_cart_price($cart_id)) + inicis_pro_json_response(array('success' => false, 'message' => '상품 가격이 변경되었습니다. 장바구니를 다시 확인해 주십시오.')); +} + +$settle_case_key = $is_personal ? 'pp_settle_case' : 'od_settle_case'; +$settle_case = isset($order_data[$settle_case_key]) ? $order_data[$settle_case_key] : ''; +$pay_type = inicis_pro_pay_type($settle_case); +$easypay_code = inicis_pro_easypay_code($settle_case); +$easypay_name = inicis_pro_easypay_name($settle_case); +$amount = inicis_pro_expected_amount($order_data, $temp_row); +$mid = inicis_pro_get_mid($pay_type); +$hashkey = inicis_pro_get_hashkey($pay_type); + +if ($pay_type === '' || $amount <= 0 || $amount > 999999999) + inicis_pro_json_response(array('success' => false, 'message' => '결제수단 또는 결제금액이 올바르지 않습니다.')); +if ($easypay_code !== '' && !inicis_pro_easypay_is_enabled($settle_case)) + inicis_pro_json_response(array('success' => false, 'message' => '선택한 간편결제는 현재 사용할 수 없습니다.')); +if ($mid === '' || $hashkey === '') + inicis_pro_json_response(array('success' => false, 'message' => 'KG이니시스 MID 또는 HashKey가 설정되지 않았습니다.')); +if (!function_exists('hash') || !in_array('sha512', hash_algos())) + inicis_pro_json_response(array('success' => false, 'message' => '서버에서 SHA-512 해시를 지원하지 않습니다.')); + +$timestamp = inicis_pro_timestamp(); +$buyer_name = $is_personal ? (isset($order_data['pp_name']) ? $order_data['pp_name'] : '') : (isset($order_data['od_name']) ? $order_data['od_name'] : ''); +$buyer_email = $is_personal ? (isset($order_data['pp_email']) ? $order_data['pp_email'] : '') : (isset($order_data['od_email']) ? $order_data['od_email'] : ''); +$buyer_phone = $is_personal ? (isset($order_data['pp_hp']) ? $order_data['pp_hp'] : '') : (!empty($order_data['od_hp']) ? $order_data['od_hp'] : (isset($order_data['od_tel']) ? $order_data['od_tel'] : '')); + +$reserved = array( + 'email' => inicis_pro_cut($buyer_email, 60), + 'phonenum' => preg_replace('/[^0-9]/', '', $buyer_phone) +); +if ($pay_type === 'CARD') { + $reserved['quotabase'] = '01:02:03:04:05:06:07:08:09:10:11:12'; + if ($easypay_code !== '') + $reserved['d_easypay'] = $easypay_code; +} +if ($pay_type === 'BANK') + $reserved['bank_receipt'] = 'N'; +if ($pay_type === 'VBANK') { + $reserved['vbank_receipt'] = 'N'; + $reserved['vbank_dt'] = date('Ymd', G5_SERVER_TIME + (86400 * 3)); + $reserved['vbank_tm'] = '2359'; +} +if (!empty($default['de_escrow_use']) && ($pay_type === 'BANK' || $pay_type === 'VBANK')) + $reserved['useescrow'] = 'Y'; + +$base_url = defined('G5_HTTPS_SHOP_URL') && G5_HTTPS_SHOP_URL ? G5_HTTPS_SHOP_URL : G5_SHOP_URL; +$noti = inicis_pro_make_noti($oid, $amount, $device, $hashkey); + +$request = array( + 'P_MID' => $mid, + 'P_OID' => $oid, + 'P_PAY_TYPE' => $pay_type, + 'P_DEVICE_TYPE' => $device, + 'P_IDCCODE' => 'Y', + 'P_AMT' => (string) $amount, + 'P_GOODS' => inicis_pro_goods_name($order_data, $temp_row), + 'P_UNAME' => inicis_pro_cut($buyer_name, 30), + 'P_NEXT_URL' => $base_url.'/inicis/pro/return.php', + 'P_CHARSET' => 'UTF-8', + 'P_NOTI' => $noti, + 'P_TIMESTAMP' => $timestamp, + 'P_CHKFAKE' => inicis_pro_hash($amount, $oid, $timestamp, $hashkey), + 'P_RESERVED' => inicis_pro_json_encode($reserved) +); + +if ($pay_type === 'VBANK') + $request['P_NOTI_URL'] = $base_url.'/inicis/pro/noti.php'; + +// PC 결제창은 iframe 모달이므로 닫기 URL을 전달하면 주문서 전체가 다시 +// 로드된다. 모바일은 현재 창에서 결제가 진행되므로 돌아갈 URL을 유지한다. +if ($device === 'MOBILE') { + if ($is_personal) + $request['P_CLOSE_URL'] = G5_SHOP_URL.'/personalpayform.php?pp_id='.$oid; + elseif (!empty($order_data['sw_direct'])) + $request['P_CLOSE_URL'] = G5_SHOP_URL.'/orderform.php?sw_direct=1'; + else + $request['P_CLOSE_URL'] = G5_SHOP_URL.'/orderform.php'; +} + +if ($pay_type === 'HPP') + $request['P_HPP_METHOD'] = '2'; +if (!empty($default['de_tax_flag_use'])) { + $request['P_TAX'] = isset($order_data['comm_vat_mny']) ? (string) (int) $order_data['comm_vat_mny'] : '0'; + $request['P_TAXFREE'] = isset($order_data['comm_free_mny']) ? (string) (int) $order_data['comm_free_mny'] : '0'; +} + +inicis_pro_audit_write($oid, 'request', 'request_ready', array( + 'mid' => $mid, + 'environment' => inicis_pro_environment(), + 'member_id' => isset($temp_row['mb_id']) ? $temp_row['mb_id'] : '', + 'amount' => $amount, + 'pay_type' => $pay_type, + 'easy_pay' => $easypay_name, + 'device' => $device, + 'order_type' => $is_personal ? 'personal' : 'order', + 'source' => $device === 'MOBILE' ? 'mobile' : 'web', + 'vbank_due_at' => $pay_type === 'VBANK' ? date('Y-m-d 23:59:00', G5_SERVER_TIME + (86400 * 3)) : '', + 'message' => $easypay_code !== '' ? $settle_case.' 결제창 요청정보 생성 완료' : '결제창 요청정보 생성 완료' +)); + +inicis_pro_json_response(array('success' => true, 'request' => $request)); diff --git a/shop/inicis/pro/return.php b/shop/inicis/pro/return.php new file mode 100644 index 000000000..20b1107d7 --- /dev/null +++ b/shop/inicis/pro/return.php @@ -0,0 +1,278 @@ + $item) { + if (!is_scalar($key)) + continue; + $result[$key] = inicis_pro_restore_order_value($item, false); + } + return $result; + } + + if (!is_scalar($value)) + return ''; + + $value = stripslashes((string) $value); + if ($memo) + return clean_xss_tags(strip_tags($value), 0, 0, 0, 0); + + return clean_xss_tags(strip_tags($value)); + } +} + +if (!isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'POST') + alert('올바른 결제 결과 요청이 아닙니다.', G5_SHOP_URL); + +if (!inicis_pro_is_enabled()) + alert('INIpay PRO 결제가 활성화되어 있지 않습니다.', G5_SHOP_URL); + +$status = isset($_POST['P_STATUS']) ? trim((string) $_POST['P_STATUS']) : ''; +$message = isset($_POST['P_RMESG']) ? inicis_pro_cut($_POST['P_RMESG'], 150) : ''; +$oid_raw = isset($_POST['P_OID']) ? trim((string) $_POST['P_OID']) : ''; +$oid = inicis_pro_clean_oid($oid_raw); +$mid = isset($_POST['P_MID']) ? trim((string) $_POST['P_MID']) : ''; +$auth_tid_raw = isset($_POST['P_AUTH_TID']) ? trim((string) $_POST['P_AUTH_TID']) : ''; +$auth_tid = inicis_pro_clean_tid($auth_tid_raw); +$amount_raw = isset($_POST['P_AMT']) ? trim((string) $_POST['P_AMT']) : ''; +$result_type = isset($_POST['P_TYPE']) ? strtoupper(trim((string) $_POST['P_TYPE'])) : ''; +$noti = isset($_POST['P_NOTI']) ? trim((string) $_POST['P_NOTI']) : ''; +$idc = isset($_POST['P_IDCNAME']) ? strtolower(trim((string) $_POST['P_IDCNAME'])) : ''; +$page_return_url = G5_SHOP_URL.'/orderform.php'; +$lock_name = ''; +$session_order_oid = inicis_pro_clean_oid(get_session('ss_order_id')); +$session_personal_oid = inicis_pro_clean_oid(get_session('ss_personalpay_id')); +$session_matches_oid = $oid !== '' && ($session_order_oid === $oid || $session_personal_oid === $oid); + +// 인증 실패 응답은 주문번호 등 일부 필드가 누락될 수 있으므로 결과코드를 먼저 처리한다. +if ($status !== '00') { + $error_text = $message !== '' ? $message : '결제 인증에 실패했습니다.'; + $error_code = substr(preg_replace('/[^A-Za-z0-9_-]/', '', $status), 0, 12); + if ($error_code === '') + $error_code = '확인불가'; + + $failure_return_url = $page_return_url; + $session_personal_id = inicis_pro_clean_oid(get_session('ss_personalpay_id')); + if ($session_personal_id !== '') + $failure_return_url = G5_SHOP_URL.'/personalpayform.php?pp_id='.$session_personal_id; + elseif (get_session('ss_direct')) + $failure_return_url .= '?sw_direct=1'; + + inicis_pro_return_error('결제 인증 실패: '.$error_text.' (코드 '.$error_code.')', $failure_return_url, '', $session_matches_oid ? $oid : '', 'authentication', 'authentication_failed', $error_code, array( + 'mid' => $mid, + 'auth_tid' => $auth_tid, + 'amount' => preg_match('/^[0-9]+$/', $amount_raw) ? (int) $amount_raw : 0, + 'pay_type' => $result_type, + 'device' => defined('G5_IS_MOBILE') && G5_IS_MOBILE ? 'MOBILE' : 'WEB', + 'source' => defined('G5_IS_MOBILE') && G5_IS_MOBILE ? 'mobile' : 'web' + )); +} + +if ($oid === '' || $oid !== $oid_raw) + alert('결제 주문번호가 올바르지 않습니다.', G5_SHOP_URL); + +$temp_row = sql_fetch(" select * from {$g5['g5_shop_order_data_table']} + where od_id = '".sql_escape_string($oid)."' + and dt_pg = 'inicis' + order by dt_time desc + limit 1 "); +if (empty($temp_row['od_id'])) { + $exists_order = get_shop_order_data($oid); + if (!empty($exists_order['od_tno'])) { + inicis_pro_audit_order_saved($oid, $exists_order['od_tno'], 'order', 'system'); + set_session('ss_order_id', $oid); + exists_inicis_shop_order($oid, array(), $exists_order['od_time'], $exists_order['od_ip']); + exit; + } + + $exists_personal = get_shop_order_data($oid, 'personal'); + if (!empty($exists_personal['pp_tno'])) { + inicis_pro_audit_order_saved($oid, $exists_personal['pp_tno'], 'personal', 'system'); + set_session('ss_personalpay_id', $oid); + set_session('ss_personalpay_hash', md5($exists_personal['pp_id'].$exists_personal['pp_price'].$exists_personal['pp_time'])); + exists_inicis_shop_order($oid, $exists_personal, $exists_personal['pp_time'], $exists_personal['pp_ip']); + exit; + } + + inicis_pro_return_error('임시 주문정보가 없어서 결제를 승인할 수 없습니다.', G5_SHOP_URL, '', $oid, 'validation', 'validation_failed', 'TEMP_ORDER_MISSING', array('source' => 'web')); +} + +$order_data = @unserialize(base64_decode($temp_row['dt_data'])); +if (!is_array($order_data)) + inicis_pro_return_error('임시 주문정보를 확인할 수 없습니다.', G5_SHOP_URL, '', $oid, 'validation', 'validation_failed', 'TEMP_ORDER_INVALID', array('source' => 'web')); + +$is_personal = !empty($order_data['pp_id']); +if ($is_personal) + $page_return_url = G5_SHOP_URL.'/personalpayform.php?pp_id='.$oid; +elseif (!empty($order_data['sw_direct'])) + $page_return_url .= '?sw_direct=1'; + +$settle_key = $is_personal ? 'pp_settle_case' : 'od_settle_case'; +$settle_case = isset($order_data[$settle_key]) ? $order_data[$settle_key] : ''; +// MID·HashKey는 PG가 생략할 수 있는 P_TYPE 대신 주문의 결제수단으로 확정해 +// 결제요청 시점의 서명과 일치시킨다. +$pay_type = inicis_pro_pay_type($settle_case); +$expected_mid = inicis_pro_get_mid($pay_type); +$hashkey = inicis_pro_get_hashkey($pay_type); +$expected_amount = inicis_pro_expected_amount($order_data, $temp_row); +$device = inicis_pro_check_noti($noti, $oid, $expected_amount, $hashkey); +$audit_source = $device === 'MOBILE' ? 'mobile' : 'web'; +$audit_values = array( + 'mid' => $mid, + 'auth_tid' => $auth_tid, + 'member_id' => isset($temp_row['mb_id']) ? $temp_row['mb_id'] : '', + 'amount' => $expected_amount, + 'pay_type' => $result_type !== '' ? $result_type : inicis_pro_pay_type($settle_case), + 'environment' => inicis_pro_environment(), + 'easy_pay' => inicis_pro_easypay_name($settle_case), + 'device' => $device, + 'order_type' => $is_personal ? 'personal' : 'order', + 'source' => $audit_source +); +$validation_audit_oid = ($device !== '' || $session_matches_oid) ? $oid : ''; + +if ($expected_mid === '' || $hashkey === '' || $mid !== $expected_mid) + inicis_pro_return_error('가맹점 결제정보가 일치하지 않아 주문을 중단했습니다.', $page_return_url, '', $validation_audit_oid, 'validation', 'validation_failed', 'MID_MISMATCH', $audit_values); +if ($expected_amount <= 0 || !preg_match('/^[0-9]+$/', $amount_raw) || (int) $amount_raw !== (int) $expected_amount) + inicis_pro_return_error('결제금액이 주문정보와 일치하지 않아 주문을 중단했습니다.', $page_return_url, '', $validation_audit_oid, 'validation', 'validation_failed', 'AMOUNT_MISMATCH', $audit_values); +if ($auth_tid === '' || $auth_tid !== $auth_tid_raw || inicis_pro_idc_host($idc) === '') + inicis_pro_return_error('결제 승인 정보가 올바르지 않아 주문을 중단했습니다.', $page_return_url, '', $validation_audit_oid, 'validation', 'validation_failed', 'AUTH_INFO_INVALID', $audit_values); +if ($device === '') + inicis_pro_return_error('주문 검증정보가 일치하지 않아 주문을 중단했습니다.', $page_return_url, '', $validation_audit_oid, 'validation', 'validation_failed', 'ORDER_SIGNATURE_MISMATCH', $audit_values); +// 일반 인증결과에서는 P_TYPE이 생략될 수 있으며 최종 결제수단은 승인결과에서 검증한다. +if ($result_type !== '' && !inicis_pro_result_type_matches($settle_case, $result_type)) + inicis_pro_return_error('결제수단이 주문정보와 일치하지 않아 주문을 중단했습니다.', $page_return_url, '', $validation_audit_oid, 'validation', 'validation_failed', 'PAY_TYPE_MISMATCH', $audit_values); + +inicis_pro_audit_write($oid, 'authentication', 'authentication_received', $audit_values); + +$lock_name = inicis_pro_lock($oid); +if ($lock_name === '') + inicis_pro_return_error('주문 처리가 진행 중입니다. 잠시 후 주문내역을 확인해 주십시오.', $page_return_url, '', $oid, 'order', 'order_saving', 'ORDER_LOCK_BUSY', $audit_values); + +$exists_order = get_shop_order_data($oid); +if (!empty($exists_order['od_tno'])) { + inicis_pro_audit_order_saved($oid, $exists_order['od_tno'], 'order', 'system'); + inicis_pro_unlock($lock_name); + set_session('ss_order_id', $oid); + exists_inicis_shop_order($oid, array(), $exists_order['od_time'], $exists_order['od_ip']); + exit; +} +$exists_personal = get_shop_order_data($oid, 'personal'); +if (!empty($exists_personal['pp_tno'])) { + inicis_pro_audit_order_saved($oid, $exists_personal['pp_tno'], 'personal', 'system'); + inicis_pro_unlock($lock_name); + set_session('ss_personalpay_id', $oid); + set_session('ss_personalpay_hash', md5($exists_personal['pp_id'].$exists_personal['pp_price'].$exists_personal['pp_time'])); + exists_inicis_shop_order($oid, $exists_personal, $exists_personal['pp_time'], $exists_personal['pp_ip']); + exit; +} + +$_POST = array(); +foreach ($order_data as $key => $value) { + if (!is_string($key) || !preg_match('/^[A-Za-z0-9_]+$/', $key)) + continue; + $_POST[$key] = inicis_pro_restore_order_value($value, $key === 'od_memo'); +} + +$_POST['inicis_pro'] = '1'; +$_POST['inicis_pro_oid'] = $oid; +$_POST['res_cd'] = '00'; +// 모바일 주문/개인결제의 결제등록 완료 확인(P_HASH 존재 여부)을 PRO 흐름에서도 통과시킨다. +// 실제 서명값은 승인 완료 후 pay_result.php에서 다시 채워 넣는다. +$_POST['P_HASH'] = '1'; + +if ($is_personal) { + $pp_row = get_shop_order_data($oid, 'personal'); + if (empty($pp_row['pp_id'])) + inicis_pro_return_error('개인결제 정보를 확인할 수 없습니다.', $page_return_url, $lock_name, $oid, 'validation', 'validation_failed', 'PERSONALPAY_MISSING', $audit_values); + + set_session('ss_personalpay_id', $oid); + set_session('ss_personalpay_hash', md5($pp_row['pp_id'].$pp_row['pp_price'].$pp_row['pp_time'])); + set_session('ss_order_id', $oid); +} else { + set_session('ss_order_id', $oid); + set_session('ss_order_inicis_id', $oid); + set_session('ss_direct', !empty($order_data['sw_direct']) ? 1 : 0); + if (!empty($order_data['sw_direct'])) + set_session('ss_cart_direct', $temp_row['cart_id']); + else + set_session('ss_cart_id', $temp_row['cart_id']); +} + +if (!empty($temp_row['mb_id'])) { + $member = get_member($temp_row['mb_id']); + $is_member = !empty($member['mb_id']); + if (!$is_member) + inicis_pro_return_error('주문 회원정보를 확인할 수 없습니다.', $page_return_url, $lock_name, $oid, 'validation', 'validation_failed', 'MEMBER_MISSING', $audit_values); +} else { + if (!is_array($member)) + $member = array(); + $member['mb_id'] = ''; + $is_member = false; +} + +$field_names = shop_order_data_fields($is_personal ? 1 : 0); +foreach ($field_names as $field_name) + $$field_name = isset($_POST[$field_name]) ? $_POST[$field_name] : ''; + +// 승인은 주문 저장 직전(inicis/pro/pay_result.php)에서 실행하도록 필요한 값을 넘긴다. +$GLOBALS['inicis_pro_approval_ctx'] = array( + 'oid' => $oid, + 'auth_tid' => $auth_tid, + 'idc' => $idc, + 'expected_mid' => $expected_mid, + 'expected_amount' => $expected_amount, + 'pay_type' => $pay_type, + 'result_type' => $result_type, + 'noti' => $noti, + 'device' => $device, + 'settle_case' => $settle_case, + 'is_personal' => $is_personal, + 'lock_name' => $lock_name, + 'page_return_url' => $page_return_url, + 'audit_values' => $audit_values +); + +inicis_pro_audit_write($oid, 'order', 'order_saving', $audit_values); + +if ($is_personal) { + $pp_id = $oid; + $good_mny = $expected_amount; + $_POST['pp_id'] = $oid; + $_POST['good_mny'] = $expected_amount; + if ($device === 'MOBILE') + include G5_MSHOP_PATH.'/personalpayformupdate.php'; + else + include G5_SHOP_PATH.'/personalpayformupdate.php'; +} else { + $od_send_cost = isset($_POST['od_send_cost']) ? (int) $_POST['od_send_cost'] : 0; + $od_send_cost2 = isset($_POST['od_send_cost2']) ? (int) $_POST['od_send_cost2'] : 0; + if ($device === 'MOBILE') + include G5_MSHOP_PATH.'/orderformupdate.php'; + else + include G5_SHOP_PATH.'/orderformupdate.php'; +} + +inicis_pro_unlock($lock_name); +exit; diff --git a/shop/orderform.sub.php b/shop/orderform.sub.php index 317b5109c..51a2d6148 100644 --- a/shop/orderform.sub.php +++ b/shop/orderform.sub.php @@ -4,7 +4,7 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가 require_once(G5_SHOP_PATH.'/settle_'.$default['de_pg_service'].'.inc.php'); require_once(G5_SHOP_PATH.'/settle_kakaopay.inc.php'); -if( $default['de_inicis_lpay_use'] || $default['de_inicis_kakaopay_use'] ){ //이니시스 Lpay 또는 이니시스 카카오페이 사용시 +if( empty($inicis_pro_use) && ($default['de_inicis_lpay_use'] || $default['de_inicis_kakaopay_use']) ){ //이니시스 Lpay 또는 이니시스 카카오페이 사용시 require_once(G5_SHOP_PATH.'/inicis/lpay_common.php'); } @@ -15,7 +15,7 @@ if(function_exists('is_use_easypay') && is_use_easypay('global_nhnkcp')){ // // 결제대행사별 코드 include (스크립트 등) require_once(G5_SHOP_PATH.'/'.$default['de_pg_service'].'/orderform.1.php'); -if( $default['de_inicis_lpay_use'] || $default['de_inicis_kakaopay_use'] ){ //이니시스 L.pay 사용시 +if( empty($inicis_pro_use) && ($default['de_inicis_lpay_use'] || $default['de_inicis_kakaopay_use']) ){ //이니시스 L.pay 사용시 require_once(G5_SHOP_PATH.'/inicis/lpay_form.1.php'); } @@ -579,7 +579,7 @@ if($is_kakaopay_use) { $escrow_title = "에스크로
    "; } - if ($is_kakaopay_use || $default['de_bank_use'] || $default['de_vbank_use'] || $default['de_iche_use'] || $default['de_card_use'] || $default['de_hp_use'] || $default['de_easy_pay_use'] || $default['de_inicis_lpay_use'] || $default['de_inicis_kakaopay_use']) { + if ($is_kakaopay_use || $default['de_bank_use'] || $default['de_vbank_use'] || $default['de_iche_use'] || $default['de_card_use'] || $default['de_hp_use'] || $default['de_easy_pay_use'] || (empty($inicis_pro_use) && ($default['de_inicis_lpay_use'] || $default['de_inicis_kakaopay_use'])) || (!empty($inicis_pro_use) && is_inicis_simple_pay())) { echo '
    '; echo '결제방법 선택'; } @@ -711,14 +711,23 @@ if($is_kakaopay_use) { echo run_replace('shop_orderform_easypay_buttons', implode(PHP_EOL, $easypay_prints), $easypay_prints, $multi_settle); } + //이니시스 PRO 삼성페이 + if(!empty($inicis_pro_use) && $default['de_samsung_pay_use']) { + $multi_settle++; + echo ' '.PHP_EOL; + $checked = ''; + } + //이니시스 Lpay if($default['de_inicis_lpay_use']) { + $multi_settle++; echo ' '.PHP_EOL; $checked = ''; } //이니시스 카카오페이 if(isset($default['de_inicis_kakaopay_use']) && $default['de_inicis_kakaopay_use']) { + $multi_settle++; echo ' '.PHP_EOL; $checked = ''; } @@ -786,7 +795,7 @@ if($is_kakaopay_use) { echo ''; } - if ($is_kakaopay_use || $default['de_bank_use'] || $default['de_vbank_use'] || $default['de_iche_use'] || $default['de_card_use'] || $default['de_hp_use'] || $default['de_easy_pay_use'] || $default['de_inicis_lpay_use'] || $default['de_inicis_kakaopay_use'] ) { + if ($is_kakaopay_use || $default['de_bank_use'] || $default['de_vbank_use'] || $default['de_iche_use'] || $default['de_card_use'] || $default['de_hp_use'] || $default['de_easy_pay_use'] || (empty($inicis_pro_use) && ($default['de_inicis_lpay_use'] || $default['de_inicis_kakaopay_use'])) || (!empty($inicis_pro_use) && is_inicis_simple_pay())) { echo '
    '; } @@ -818,7 +827,7 @@ if($is_kakaopay_use) { ){ //이니시스 L.pay 또는 이니시스 카카오페이 이면 ( 이니시스의 삼성페이는 모바일에서만 단독실행 가능함 ) form_order_method = 'samsungpay'; } else if(settle_method == "간편결제") { if(jQuery("input[name='od_settle_case']:checked" ).attr("data-pay") === "naverpay"){ @@ -1593,6 +1602,10 @@ function forderform_check(f) f.gopaymethod.value = "Card"; f.acceptmethod.value = f.acceptmethod.value.replace(":useescrow", ""); break; + case "삼성페이": + f.gopaymethod.value = "Card"; + f.acceptmethod.value = f.acceptmethod.value.replace(":useescrow", ""); + break; case "간편결제": f.gopaymethod.value = "Kpay"; break; @@ -1802,10 +1815,14 @@ function forderform_check(f) return false; } + + return inicis_pro_pay("", "WEB"); + if(!make_signature(f)) return false; - + paybtn(f); + } else { f.submit(); } @@ -1875,4 +1892,4 @@ $(function(){ $("#od_hope_date").datepicker({ changeMonth: true, changeYear: true, dateFormat: "yy-mm-dd", showButtonPanel: true, yearRange: "c-99:c+99", minDate: "+d;", maxDate: "+d;" }); }); - \ No newline at end of file + diff --git a/shop/orderformupdate.php b/shop/orderformupdate.php index cb58bdc63..28d89af85 100644 --- a/shop/orderformupdate.php +++ b/shop/orderformupdate.php @@ -353,7 +353,10 @@ else if ($od_settle_case == "계좌이체") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; break; case 'nicepay': include G5_SHOP_PATH.'/nicepay/nicepay_result.php'; @@ -387,7 +390,10 @@ else if ($od_settle_case == "가상계좌") $od_receipt_time = '0000-00-00 00:00:00'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; $od_app_no = $app_no; break; case 'nicepay': @@ -418,7 +424,10 @@ else if ($od_settle_case == "휴대폰") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; break; case 'nicepay': include G5_SHOP_PATH.'/nicepay/nicepay_result.php'; @@ -448,7 +457,10 @@ else if ($od_settle_case == "신용카드") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; break; case 'nicepay': include G5_SHOP_PATH.'/nicepay/nicepay_result.php'; @@ -470,7 +482,7 @@ else if ($od_settle_case == "신용카드") if($od_misu == 0) $od_status = '입금'; } -else if ($od_settle_case == "간편결제" || (($od_settle_case == "lpay" || $od_settle_case == "inicis_kakaopay") && $default['de_pg_service'] === 'inicis') ) +else if ($od_settle_case == "간편결제" || (in_array($od_settle_case, array("삼성페이", "lpay", "inicis_kakaopay")) && $default['de_pg_service'] === 'inicis') ) { switch($default['de_pg_service']) { case 'lg': @@ -480,7 +492,10 @@ else if ($od_settle_case == "간편결제" || (($od_settle_case == "lpay" || $od include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; break; case 'nicepay': include G5_SHOP_PATH.'/nicepay/nicepay_result.php'; @@ -705,6 +720,9 @@ if(!$result) { die('

    고객님의 주문 정보를 처리하는 중 오류가 발생해서 주문이 완료되지 않았습니다.

    '.strtoupper($od_pg).'를 이용한 전자결제(신용카드, 계좌이체, 가상계좌 등)은 자동 취소되었습니다.'); } +if (!empty($_POST['inicis_pro']) && function_exists('inicis_pro_audit_order_saved')) + inicis_pro_audit_order_saved($od_id, $od_tno, 'order', 'web'); + // 회원이면서 포인트를 사용했다면 테이블에 사용을 추가 // 동시 주문 race condition 방지 — 회원 단위 GET_LOCK + 잔액 재조회 if ($is_member && $od_receipt_point) { diff --git a/shop/orderinquirycancel.php b/shop/orderinquirycancel.php index be380ecc1..2d03498aa 100644 --- a/shop/orderinquirycancel.php +++ b/shop/orderinquirycancel.php @@ -27,10 +27,30 @@ $ct = sql_fetch($sql); $uid = function_exists('get_shop_uid') ? get_shop_uid('order', $od['od_id'], $od['od_time'], $od['od_ip']) : md5($od['od_id'].$od['od_time'].$od['od_ip']); +// 비회원은 주문조회 인증을 통과한 본인 주문만 취소할 수 있다. +if (empty($member['mb_id']) && (string) $uid !== (string) get_session('ss_orderview_uid')) + alert("본인 주문만 취소할 수 있습니다.", G5_SHOP_URL); + if($od['od_cancel_price'] > 0 || $od['od_status'] != '주문' || $ct['od_count1'] != $ct['od_count2']) { alert("취소할 수 있는 주문이 아닙니다.", G5_SHOP_URL."/orderinquiryview.php?od_id=$od_id&uid=$uid"); } +// INIpay PRO 가상계좌 입금통보와 주문자 취소가 동시에 처리되지 않도록 +// PG 취소부터 로컬 주문 취소 완료까지 동일 주문 잠금을 유지한다. +$inicis_pro_order_lock = ''; +if (!empty($od['od_tno']) && $od['od_pg'] === 'inicis') { + include_once(G5_SHOP_PATH.'/inicis/pro/inicis_pro.lib.php'); + $inicis_pro_tables = inicis_pro_audit_tables(); + $inicis_pro_order = sql_fetch(" select ip_id from `{$inicis_pro_tables['summary']}` + where ip_oid = '".sql_escape_string($od['od_id'])."' + and ip_tid = '".sql_escape_string($od['od_tno'])."' ", false); + if (!empty($inicis_pro_order['ip_id'])) { + $inicis_pro_order_lock = inicis_pro_lock($od['od_id']); + if ($inicis_pro_order_lock === '') + alert('동일 주문의 결제 또는 통보 처리가 진행 중입니다. 잠시 후 다시 취소해 주십시오.', G5_SHOP_URL."/orderinquiryview.php?od_id=$od_id&uid=$uid"); + } +} + // PG 결제 취소 if($od['od_tno']) { switch($od['od_pg']) { @@ -75,6 +95,8 @@ if($od['od_tno']) { $args = array( 'paymethod' => get_type_inicis_paymethod($od['od_settle_case']), 'tid' => $od['od_tno'], + 'audit_oid' => $od['od_id'], + 'audit_source' => 'web', 'msg' => $cancel_msg ); @@ -187,4 +209,7 @@ if ($od['od_receipt_point'] > 0 && $affected) { insert_point($member['mb_id'], $od['od_receipt_point'], "주문번호 $od_id 본인 취소", '@shop_order', $od_id, 'cancel'); } +if ($inicis_pro_order_lock !== '') + inicis_pro_unlock($inicis_pro_order_lock); + goto_url(G5_SHOP_URL."/orderinquiryview.php?od_id=$od_id&uid=$uid"); diff --git a/shop/personalpayform.sub.php b/shop/personalpayform.sub.php index 4d37d8387..86486d8e9 100644 --- a/shop/personalpayform.sub.php +++ b/shop/personalpayform.sub.php @@ -421,10 +421,14 @@ function forderform_check(f) return false; } + + return inicis_pro_pay("", "WEB"); + if(!make_signature(f)) return false; paybtn(f); + } else { f.submit(); } @@ -470,4 +474,4 @@ function forderform_check(f) } - \ No newline at end of file + diff --git a/shop/personalpayformupdate.php b/shop/personalpayformupdate.php index e3e857c48..bc1c86c21 100644 --- a/shop/personalpayformupdate.php +++ b/shop/personalpayformupdate.php @@ -49,7 +49,10 @@ if ($pp_settle_case == "계좌이체") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; break; case 'nicepay': include G5_SHOP_PATH.'/nicepay/nicepay_result.php'; @@ -77,7 +80,10 @@ else if ($pp_settle_case == "가상계좌") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; break; case 'nicepay': include G5_SHOP_PATH.'/nicepay/nicepay_result.php'; @@ -105,7 +111,10 @@ else if ($pp_settle_case == "휴대폰") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; break; case 'nicepay': include G5_SHOP_PATH.'/nicepay/nicepay_result.php'; @@ -131,7 +140,10 @@ else if ($pp_settle_case == "신용카드") include G5_SHOP_PATH.'/toss/toss_result.php'; break; case 'inicis': - include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; + if (!empty($_POST['inicis_pro'])) + include G5_SHOP_PATH.'/inicis/pro/pay_result.php'; + else + include G5_SHOP_PATH.'/inicis/inistdpay_result.php'; break; case 'nicepay': include G5_SHOP_PATH.'/nicepay/nicepay_result.php'; @@ -215,7 +227,7 @@ if(!$result) { break; } - die("

    $sql

    " . sql_error_info() . "

    error file : {$_SERVER['SCRIPT_NAME']}"); + die('개인결제 정보를 처리하는 중 오류가 발생했습니다. 결제 취소 여부를 관리자에게 문의해 주십시오.'); } // 주문번호가 있으면 결제정보 반영 @@ -259,7 +271,7 @@ if($pp_receipt_price > 0 && $pp['pp_id'] && $pp['od_id']) { break; } - die("

    $sql

    " . sql_error_info() . "

    error file : {$_SERVER['SCRIPT_NAME']}"); + die('주문 결제정보를 처리하는 중 오류가 발생했습니다. 결제 취소 여부를 관리자에게 문의해 주십시오.'); } // 미수금 정보 업데이트 @@ -282,6 +294,9 @@ if($pp_receipt_price > 0 && $pp['pp_id'] && $pp['od_id']) { } // 개인결제번호제거 +if (!empty($_POST['inicis_pro']) && function_exists('inicis_pro_audit_order_saved')) + inicis_pro_audit_order_saved($pp['pp_id'], $pp_tno, 'personal', 'web'); + set_session('ss_personalpay_id', ''); set_session('ss_personalpay_hash', ''); @@ -315,4 +330,4 @@ goto_url(G5_SHOP_URL.'/personalpayresult.php?pp_id='.$pp['pp_id'].'&uid='.$u document.onkeydown = noRefresh ; - \ No newline at end of file + diff --git a/shop/settle_inicis.inc.php b/shop/settle_inicis.inc.php index 829b2e865..415255801 100644 --- a/shop/settle_inicis.inc.php +++ b/shop/settle_inicis.inc.php @@ -2,6 +2,7 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가 $useescrow = ''; +$inicis_pro_use = isset($default['de_inicis_pro_use']) && (int) $default['de_inicis_pro_use'] === 1; if ($default['de_card_test']) { if ($default['de_escrow_use'] == 1) { @@ -42,6 +43,8 @@ else { require_once(G5_SHOP_PATH.'/inicis/libs/INIStdPayUtil.php'); require_once(G5_SHOP_PATH.'/inicis/libs/sha256.inc.php'); require_once(G5_SHOP_PATH.'/inicis/libs/inicis_youngcart_fn.php'); +if ($inicis_pro_use) + require_once(G5_SHOP_PATH.'/inicis/pro/inicis_pro.lib.php'); $mid = $default['de_inicis_mid']; $signKey = $default['de_inicis_sign_key']; @@ -134,4 +137,4 @@ $PAY_METHOD = array( 'DirectBank' => '계좌이체', 'HPP' => '휴대폰', 'VBank' => '가상계좌' -); \ No newline at end of file +);