토스페이먼츠 v2 결제 모듈 연동

This commit is contained in:
chym1217
2025-09-16 16:34:15 +09:00
parent 1eee11e433
commit 46ea2d03b5
44 changed files with 2614 additions and 20 deletions

2
shop/toss/_common.php Normal file
View File

@ -0,0 +1,2 @@
<?php
include_once('../../common.php');

111
shop/toss/orderform.1.php Normal file
View File

@ -0,0 +1,111 @@
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
// 전자결제를 사용할 때만 실행
if($default['de_iche_use'] || $default['de_vbank_use'] || $default['de_hp_use'] || $default['de_card_use'] || $default['de_easy_pay_use']) {
?>
<script language="javascript" src="https://js.tosspayments.com/v2/standard"></script>
<script type="text/javascript">
/*
* 수정불가.
*/
const clientKey = "<?php echo $config['cf_toss_client_key']; ?>";
const customerKey = "<?php echo isset($member['mb_id']) ? $member['mb_id'] : ''; ?>";
const tossPayments = TossPayments(clientKey);
const payment = tossPayments.payment({ customerKey });
/*
* 수정불가
*/
async function launchCrossPlatform(frm) {
// 필수 값들 체크
if (!frm.amountValue || !frm.amountValue.value) {
alert('결제 금액이 설정되지 않았습니다.');
return;
}
const amount = parseInt(frm.amountValue.value);
if (isNaN(amount) || amount <= 0) {
alert('올바른 결제 금액을 입력해주세요.');
return;
}
// 기본 결제 옵션
const paymentOptions = {
method: frm.method.value,
amount: {
currency: "KRW",
value: parseInt(frm.amountValue.value),
},
taxFreeAmount: parseInt(frm.taxFreeAmount.value),
orderId: frm.orderId.value, // 고유 주문번호
orderName: frm.orderName.value,
successUrl: "<?php echo G5_SHOP_URL;?>/toss/returnurl.php", // 결제 요청이 성공하면 리다이렉트되는 URL
failUrl: "<?php echo G5_SHOP_URL;?>/toss/returnurl.php?mode=fail", // 결제 요청이 실패하면 리다이렉트되는 URL
customerEmail: frm.customerEmail.value,
customerName: frm.customerName.value,
customerMobilePhone: frm.customerMobilePhone.value,
};
// escrowProducts 추가 함수
function addEscrowProducts(paymentMethodOptions) {
if (frm.cardUseEscrow.value === "true") {
if (frm.escrowProducts && frm.escrowProducts.value) {
paymentMethodOptions.escrowProducts = JSON.parse(frm.escrowProducts.value);
}
}
}
// 결제 방법에 따른 추가 옵션
if (frm.method.value == 'CARD') {
// 신용카드
paymentOptions.card = {
flowMode: frm.cardflowMode.value, // 통합결제창 여는 옵션
easyPay: frm.cardeasyPay.value,
useCardPoint: frm.cardUseCardPoint.value == "true" ? true : false,
useAppCardOnly: frm.cardUseAppCardOnly.value == "true" ? true : false,
useEscrow: frm.cardUseEscrow.value == "true" ? true : false,
};
// escrowProducts 추가
addEscrowProducts(paymentOptions.card);
} else if (frm.method.value == 'VIRTUAL_ACCOUNT') {
// 가상계좌
paymentOptions.virtualAccount = {
cashReceipt: {
type: "소득공제",
},
useEscrow: frm.cardUseEscrow.value == "true" ? true : false,
validHours: 168,
};
// escrowProducts 추가
addEscrowProducts(paymentOptions.virtualAccount);
} else if (frm.method.value == 'TRANSFER') {
// 계좌이체
paymentOptions.transfer = {
cashReceipt: {
type: "소득공제",
},
useEscrow: frm.cardUseEscrow.value == "true" ? true : false,
};
// escrowProducts 추가
addEscrowProducts(paymentOptions.transfer);
}
await payment.requestPayment(paymentOptions);
}
/*
* FORM 명만 수정 가능
*/
function getFormObject() {
return document.getElementById("forderform");
}
</script>
<?php }

31
shop/toss/orderform.2.php Normal file
View File

@ -0,0 +1,31 @@
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
?>
<input type="hidden" name="method" value="">
<input type="hidden" name="orderId" value="<?php echo isset($od_id) ? $od_id : ''; ?>">
<input type="hidden" name="orderName" value="<?php echo isset($goods) ? $goods : ''; ?>">
<input type="hidden" name="customerName" value="<?php echo isset($od_name) ? $od_name : ''; ?>">
<input type="hidden" name="customerEmail" value="<?php echo isset($od_email) ? $od_email : ''; ?>">
<input type="hidden" name="customerMobilePhone" value="<?php echo isset($od_hp) ? $od_hp : ''; ?>">
<input type="hidden" name="cardUseEscrow" value="false">
<input type="hidden" name="escrowProducts" value=''>
<input type="hidden" name="cardflowMode" value="DEFAULT">
<input type="hidden" name="cardeasyPay" value="PAYCO">
<input type="hidden" name="cardUseCardPoint" value="false">
<input type="hidden" name="cardUseAppCardOnly" value="false">
<input type="hidden" name="amountCurrency" value="KRW">
<input type="hidden" name="amountValue" value="<?php echo isset($tot_price) ? $tot_price : 0; ?>">
<input type="hidden" name="taxFreeAmount" value="<?php echo isset($comm_free_mny) ? $comm_free_mny : 0; ?>">
<input type="hidden" name="windowTarget" value="iframe">
<input type="hidden" name="good_mny" value="<?php echo $tot_price; ?>">
<?php
if($default['de_tax_flag_use']) {
?>
<input type="hidden" name="comm_tax_mny" value="<?php echo isset($comm_tax_mny) ? $comm_tax_mny : 0; ?>"> <!-- 과세금액 -->
<input type="hidden" name="comm_vat_mny" value="<?php echo isset($comm_vat_mny) ? $comm_vat_mny : 0; ?>"> <!-- 부가세 -->
<input type="hidden" name="comm_free_mny" value="<?php echo isset($comm_free_mny) ? $comm_free_mny : 0; ?>"> <!-- 비과세 금액 -->
<?php
}
?>

16
shop/toss/orderform.3.php Normal file
View File

@ -0,0 +1,16 @@
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
?>
<div id="display_pay_button" class="btn_confirm" style="display:none">
<input type="button" value="주문하기" class="btn_submit" onclick="forderform_check(this.form);"/>
<a href="javascript:history.go(-1);" class="btn01">취소</a>
</div>
<div id="display_pay_process" style="display:none">
<img src="<?php echo G5_URL; ?>/shop/img/loading.gif" alt="">
<span>주문완료 중입니다. 잠시만 기다려 주십시오.</span>
</div>
<script>
document.getElementById("display_pay_button").style.display = "" ;
</script>

View File

@ -0,0 +1,3 @@
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가;
?>

View File

@ -0,0 +1,61 @@
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
if($od['od_pg'] != 'toss') return;
// 토스 공통 설정
require_once(G5_SHOP_PATH.'/toss/toss.inc.php');
if (empty($mod_memo)) {
alert('취소사유를 입력해 주세요.');
}
$toss = new TossPayments(
$config['cf_toss_client_key'],
$config['cf_toss_secret_key'],
$config['cf_lg_mid']
);
$toss->setPaymentHeader();
$od_id = isset($od['od_id']) ? $od['od_id'] : (isset($pp['pp_id']) ? $pp['pp_id'] : '');
if (!$toss->getPaymentByOrderId($od_id)) {
alert('결제정보를 가져올 수 없습니다.');
}
$toss->setCancelData(array(
'paymentKey' => $toss->responseData['paymentKey'],
'cancelReason' => $mod_memo,
'cancelAmount' => (int)$tax_mny + (int)$free_mny,
'taxFreeAmount' => (int)$free_mny,
));
if (!$toss->cancelPayment()) {
$msg = '결제 부분취소 요청이 실패하였습니다.\\n\\n';
if (isset($toss->responseData['message'])) {
$msg .= '사유 : ' . $toss->responseData['message'] . '\\n';
}
if (isset($toss->responseData['code'])) {
$msg .= '코드 : ' . $toss->responseData['code'];
}
alert($msg);
}
// 환불금액 기록
$mod_mny = (int)$tax_mny + (int)$free_mny;
$sql = " update {$g5['g5_shop_order_table']}
set od_refund_price = od_refund_price + '$mod_mny',
od_shop_memo = concat(od_shop_memo, \"$mod_memo\")
where od_id = '{$od['od_id']}'";
sql_query($sql);
// 미수금 등의 정보 업데이트
$info = get_order_info($od_id);
$sql = " update {$g5['g5_shop_order_table']}
set od_misu = '{$info['od_misu']}',
od_tax_mny = '{$info['od_tax_mny']}',
od_vat_mny = '{$info['od_vat_mny']}',
od_free_mny = '{$info['od_free_mny']}'
where od_id = '$od_id' ";
sql_query($sql);

67
shop/toss/returnurl.php Normal file
View File

@ -0,0 +1,67 @@
<?php
include_once('./_common.php');
// 결제 실패 처리인 경우
if (isset($_REQUEST['mode']) && $_REQUEST['mode'] === 'fail') {
$code = isset($_REQUEST['code']) ? trim($_REQUEST['code']) : '';
$message = isset($_REQUEST['message']) ? trim($_REQUEST['message']) : '';
alert('결제에 실패하였습니다.\\n\\n[' . $code . '] ' . $message, G5_SHOP_URL . '/orderform.php');
exit;
}
$g5['title'] = '토스페이먼츠 결제인증 완료처리';
$g5['body_script'] = ' onload="setTossResult();"';
include_once(G5_PATH.'/head.sub.php');
// 토스페이먼츠 결제인증 성공시 인증키 주문 임시데이터에 업데이트
$paymentKey = isset($_REQUEST['paymentKey']) ? trim($_REQUEST['paymentKey']) : '';
$orderId = isset($_REQUEST['orderId']) ? trim($_REQUEST['orderId']) : '';
$amount = isset($_REQUEST['amount']) ? trim($_REQUEST['amount']) : '';
if (empty($paymentKey) || empty($orderId)) {
alert('결제정보가 올바르지 않습니다.', G5_SHOP_URL);
exit;
}
$sql = " select * from {$g5['g5_shop_order_data_table']} where od_id = '$orderId' ";
$row = sql_fetch($sql);
$data = isset($row['dt_data']) ? unserialize(base64_decode($row['dt_data'])) : array();
// 주문 임시데이터에 paymentKey 업데이트
$data['paymentKey'] = $paymentKey;
$data_new = base64_encode(serialize($data));
$sql = " update {$g5['g5_shop_order_data_table']} set dt_data = '$data_new' where od_id = '$orderId' limit 1 ";
sql_query($sql);
if(isset($data['pp_id']) && $data['pp_id']) {
$order_action_url = G5_HTTPS_SHOP_URL.'/personalpayformupdate.php';
} else {
$order_action_url = G5_HTTPS_SHOP_URL.'/orderformupdate.php';
}
?>
<?php
$exclude = array();
echo '<form name="forderform" method="post" action="'.$order_action_url.'" autocomplete="off">'.PHP_EOL;
echo make_order_field($data, $exclude);
echo '</form>'.PHP_EOL;
?>
<script type="text/javascript">
function setTossResult() {
try {
document.forderform.submit();
} catch (e) {
alert(e.message);
}
}
</script>
<?php
include_once(G5_PATH.'/tail.sub.php');

197
shop/toss/taxsave_form.php Normal file
View File

@ -0,0 +1,197 @@
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
?>
<script>
// 현금영수증 MAIN FUNC
function jsf__pay_cash( form )
{
jsf__show_progress(true);
if ( jsf__chk_cash( form ) == false )
{
jsf__show_progress(false);
return;
}
form.submit();
}
// 진행 바
function jsf__show_progress( show )
{
if ( show == true )
{
window.show_pay_btn.style.display = "none";
window.show_progress.style.display = "inline";
}
else
{
window.show_pay_btn.style.display = "inline";
window.show_progress.style.display = "none";
}
}
// 포맷 체크
function jsf__chk_cash( form )
{
if ( form.tr_code[0].checked )
{
if ( form.id_info.value.length != 10 &&
form.id_info.value.length != 11 &&
form.id_info.value.length != 13 )
{
alert("주민번호 또는 휴대폰번호를 정확히 입력해 주시기 바랍니다.");
form.id_info.select();
form.id_info.focus();
return false;
}
}
else if ( form.tr_code[1].checked )
{
if ( form.id_info.value.length != 10 )
{
alert("사업자번호를 정확히 입력해 주시기 바랍니다.");
form.id_info.select();
form.id_info.focus();
return false;
}
}
return true;
}
function jsf__chk_tr_code( form )
{
var span_tr_code_0 = document.getElementById( "span_tr_code_0" );
var span_tr_code_1 = document.getElementById( "span_tr_code_1" );
if ( form.tr_code[0].checked )
{
span_tr_code_0.style.display = "inline";
span_tr_code_1.style.display = "none";
}
else if (form.tr_code[1].checked )
{
span_tr_code_0.style.display = "none";
span_tr_code_1.style.display = "inline";
}
}
</script>
<div id="scash" class="new_win">
<h1 id="win_title"><?php echo $g5['title']; ?></h1>
<section>
<h2>주문정보</h2>
<div class="tbl_head01 tbl_wrap">
<table>
<colgroup>
<col class="grid_3">
<col>
</colgroup>
<tbody>
<tr>
<th scope="row">주문 번호</th>
<td><?php echo $od_id; ?></td>
</tr>
<tr>
<th scope="row">상품 정보</th>
<td><?php echo $goods_name; ?></td>
</tr>
<tr>
<th scope="row">주문자 이름</th>
<td><?php echo $od_name; ?></td>
</tr>
<tr>
<th scope="row">주문자 E-Mail</th>
<td><?php echo $od_email; ?></td>
</tr>
<tr>
<th scope="row">주문자 전화번호</th>
<td><?php echo $od_tel; ?></td>
</tr>
</tbody>
</table>
</div>
</section>
<section>
<h2>현금영수증 발급 정보</h2>
<form method="post" action="<?php echo G5_SHOP_URL; ?>/toss/taxsave_result.php">
<input type="hidden" name="tx" value="<?php echo $tx; ?>">
<input type="hidden" name="od_id" value="<?php echo $od_id; ?>">
<div class="tbl_head01 tbl_wrap">
<table>
<colgroup>
<col class="grid_3">
<col>
</colgroup>
<tbody>
<tr>
<th scope="row">원 거래 시각</th>
<td><?php echo $trad_time; ?></td>
</tr>
<tr>
<th scope="row">발행 용도</th>
<td>
<input type="radio" name="tr_code" value="0" id="tr_code1" onClick="jsf__chk_tr_code( this.form )" checked>
<label for="tr_code1">소득공제용</label>
<input type="radio" name="tr_code" value="1" id="tr_code2" onClick="jsf__chk_tr_code( this.form )">
<label for="tr_code2">지출증빙용</label>
</td>
</tr>
<tr>
<th scope="row">
<label for="id_info">
<span id="span_tr_code_0" style="display:inline">주민(휴대폰)번호</span>
<span id="span_tr_code_1" style="display:none">사업자번호</span>
</label>
</th>
<td>
<input type="text" name="id_info" id="id_info" class="frm_input" size="16" maxlength="13"> ("-" 생략)
</td>
</tr>
<tr>
<th scope="row"><label for="buyeremail">이메일</label></th>
<td><input type="text" name="buyeremail" id="buyeremail" value="<?php echo $od_email; ?>" required class="required frm_input" size="30"></td>
</tr>
<tr>
<th scope="row"><label for="buyertel">휴대폰</label></th>
<td><input type="text" name="buyertel" id="buyertel" value="" required class="required frm_input" size="20"></td>
</tr>
<tr>
<th scope="row">거래금액 총합</th>
<td><?php echo number_format($amt_tot); ?>원</td>
</tr>
<tr>
<th scope="row">공급가액</th>
<td><?php echo number_format($amt_sup); ?>원<!-- ((거래금액 총합 * 10) / 11) --></td>
</tr>
<tr>
<th scope="row">봉사료</th>
<td><?php echo number_format($amt_svc); ?>원</td>
</tr>
<tr>
<th scope="row">부가가치세</th>
<td><?php echo number_format($amt_tax); ?>원<!-- 거래금액 총합 - 공급가액 - 봉사료 --></td>
</tr>
</tbody>
</table>
</div>
<div id="scash_apply">
<span id="show_pay_btn">
<button type="button" onclick="jsf__pay_cash( this.form )">등록요청</button>
</span>
<span id="show_progress" style="display:none">
<b>등록 진행중입니다. 잠시만 기다려주십시오</b>
</span>
</div>
</form>
</section>
</div>

View File

@ -0,0 +1,192 @@
<?php
include_once('./_common.php');
require_once(G5_SHOP_PATH.'/settle_toss.inc.php');
$od_id = isset($_REQUEST['od_id']) ? safe_replace_regex($_REQUEST['od_id'], 'od_id') : '';
$tx = isset($_REQUEST['tx']) ? clean_xss_tags($_REQUEST['tx'], 1, 1) : '';
if($tx == 'personalpay') {
$od = sql_fetch(" select * from {$g5['g5_shop_personalpay_table']} where pp_id = '$od_id' ");
if (!$od)
die('<p id="scash_empty">개인결제 내역이 존재하지 않습니다.</p>');
$od_tno = $od['pp_tno'];
$goods_name = $od['pp_name'].'님 개인결제';
$settle_case = $od['pp_settle_case'];
$order_price = $od['pp_receipt_price'];
$od_casseqno = $od['pp_casseqno'];
$od_name = $od['pp_name'];
$od_email = $od['pp_email'];
$od_tel = $od['pp_hp'];
} else {
$od = sql_fetch(" select * from {$g5['g5_shop_order_table']} where od_id = '$od_id' ");
if (!$od)
die('<p id="scash_empty">주문서가 존재하지 않습니다.</p>');
$od_tno = $od['od_tno'];
$goods = get_goods($od['od_id']);
$goods_name = $goods['full_name'];
$settle_case = $od['od_settle_case'];
$order_price = $od['od_tax_mny'] + $od['od_vat_mny'] + $od['od_free_mny'];
$od_casseqno = $od['od_casseqno'];
$od_name = $od['od_name'];
$od_email = $od['od_email'];
$od_tel = $od['od_tel'];
}
switch($settle_case) {
case '가상계좌':
case '계좌이체':
case '무통장':
// 토스페이먼츠는 결제수단 구분 없이 현금영수증 발급 가능
break;
default:
die('<p id="scash_empty">현금영수증은 무통장, 가상계좌, 계좌이체에 한해 발급요청이 가능합니다.</p>');
break;
}
// 토스페이먼츠 현금영수증 발급 요청
$orderId = $od_id;
$amount = $order_price;
$type = ($_POST['tr_code'] == '0') ? '소득공제' : '지출증빙';
$customerIdentityNumber = $_POST['id_info'];
$orderName = $od_name;
$customerEmail = $_POST['buyeremail'] ?: $od_email;
$customerMobilePhone = $_POST['buyertel'] ?: $od_tel;
// 토스페이먼츠 현금영수증 발급 API 호출
$toss->setCashReceiptsData([
'orderId' => $orderId,
'amount' => $amount,
'type' => $type,
'customerIdentityNumber' => $customerIdentityNumber,
'orderName' => $goods_name,
]);
$toss_result = $toss->issueCashReceipt();
/*
* 토스페이먼츠 현금영수증 발급 요청 결과처리
*/
if ($toss_result && isset($toss->responseData['receiptKey'])) {
// 현금영수증 발급 성공
$data = $toss->responseData;
$receiptKey = $data['receiptKey']; // 현금영수증 발급 키
$cash_no = $data['issueNumber']; // 현금영수증 발급 번호
$approvedAt = $data['requestedAt'];
$cash = array();
$cash['receiptKey'] = $receiptKey;
$cash['approvedAt'] = $approvedAt;
$cash['orderId'] = $data['orderId'];
$cash['amount'] = $data['amount'];
$cash['type'] = $data['type'];
$cash['receiptUrl'] = $data['receiptUrl'];
$cash_info = serialize($cash);
if($tx == 'personalpay') {
$sql = " update {$g5['g5_shop_personalpay_table']}
set pp_cash = '1',
pp_cash_no = '$cash_no',
pp_cash_info = '$cash_info'
where pp_id = '$orderId' ";
} else {
$sql = " update {$g5['g5_shop_order_table']}
set od_cash = '1',
od_cash_no = '$cash_no',
od_cash_info = '$cash_info'
where od_id = '$orderId' ";
}
$result = sql_query($sql, false);
if(!$result) { // DB 정보갱신 실패시 취소
$cancel_result = $toss->cancelCashReceipt($receiptKey, 'DB 업데이트 실패로 인한 취소');
if (!$cancel_result) {
$msg = '현금영수증 취소 요청처리가 정상적으로 완료되지 않았습니다.\\n'. $toss->responseData['message'];
alert_close($msg);
}
}
} else {
// API 요청 실패 화면처리
$msg = '현금영수증 발급 요청처리가 정상적으로 완료되지 않았습니다.\\n'. $toss->responseData['message'];
alert($msg);
}
$g5['title'] = '';
include_once(G5_PATH.'/head.sub.php');
?>
<script>
function showreceipt() // 현금 영수증 출력
{
var showreceiptUrl = "https://dashboard.tosspayments.com/receipt/mids/si_<?php echo $config['cf_lg_mid']; ?>/orders/<?php echo $orderId; ?>/cash-receipt?ref=dashboard";
window.open(showreceiptUrl,"showreceipt","width=380,height=540, scrollbars=no,resizable=no");
}
</script>
<div id="toss_req_tx" class="new_win">
<h1 id="win_title">현금영수증 - 토스페이먼츠</h1>
<div class="tbl_head01 tbl_wrap">
<table>
<colgroup>
<col class="grid_4">
<col>
</colgroup>
<tbody>
<?php if ($toss_result && isset($toss->responseData['receiptKey'])): ?>
<tr>
<th scope="row">결과</th>
<td>발급 완료</td>
</tr>
<tr>
<th scope="row">현금영수증 발급번호</th>
<td><?php echo $toss->responseData['issueNumber']; ?></td>
</tr>
<tr>
<th scope="row">주문번호</th>
<td><?php echo $toss->responseData['orderId']; ?></td>
</tr>
<tr>
<th scope="row">발급 유형</th>
<td><?php echo $toss->responseData['type']; ?></td>
</tr>
<tr>
<th scope="row">금액</th>
<td><?php echo number_format($toss->responseData['amount']); ?>원</td>
</tr>
<tr>
<th scope="row">승인시간</th>
<td><?php echo date('Y-m-d H:i:s', strtotime($toss->responseData['requestedAt'])); ?></td>
</tr>
<tr>
<th scope="row">현금영수증 확인</th>
<td>
<button type="button" name="receiptView" class="btn_frmline" onClick="javascript:showreceipt();">영수증 확인</button>
<p>영수증 확인은 실 등록의 경우에만 가능합니다.</p>
</td>
</tr>
<?php else: ?>
<tr>
<th scope="row">결과</th>
<td>발급 실패</td>
</tr>
<tr>
<th scope="row">오류 메시지</th>
<td><?php echo isset($toss_result['error']) ? $toss_result['error'] : '알 수 없는 오류가 발생했습니다.'; ?></td>
</tr>
<?php endif; ?>
<tr>
<td colspan="2"></td>
</tr>
</tbody>
</table>
</div>
</div>
<?php
include_once(G5_PATH.'/tail.sub.php');

373
shop/toss/toss.inc.php Normal file
View File

@ -0,0 +1,373 @@
<?php
class TossPayments {
// 클라이언트, 시크릿 키, 상점아이디
public string $clientKey;
public string $secretKey;
public string $mId;
public string $headerSecretKey = "";
public string $paymentUrl = "https://api.tosspayments.com/v1/payments/orders/{orderId}";
public string $acceptUrl = "https://api.tosspayments.com/v1/payments/confirm";
public string $cancelUrl = "https://api.tosspayments.com/v1/payments/{paymentKey}/cancel";
public string $cashReceiptsUrl = "https://api.tosspayments.com/v1/cash-receipts";
// 결제데이터
public array $headers = array();
public array $paymentData = array();
public array $cancelData = array();
public array $cashReceiptsData = array();
public array $responseData = array();
public array $bankCode = array(
// 은행
'02' => '한국산업은행',
'03' => 'IBK기업은행',
'06' => 'KB국민은행',
'07' => 'Sh수협은행',
'11' => 'NH농협은행',
'12' => '단위농협(지역농축협)',
'20' => '우리은행',
'23' => 'SC제일은행',
'27' => '씨티은행',
'31' => 'iM뱅크(대구)',
'32' => '부산은행',
'34' => '광주은행',
'35' => '제주은행',
'37' => '전북은행',
'39' => '경남은행',
'45' => '새마을금고',
'48' => '신협',
'50' => '저축은행중앙회',
'54' => '홍콩상하이은행',
'64' => '산림조합',
'71' => '우체국예금보험',
'81' => '하나은행',
'88' => '신한은행',
'89' => '케이뱅크',
'90' => '카카오뱅크',
'92' => '토스뱅크',
// 증권
'S0' => '유안타증권',
'S2' => '신한금융투자',
'S3' => '삼성증권',
'S4' => 'KB증권',
'S5' => '미래에셋증권',
'S6' => '한국투자증권',
'S8' => '교보증권',
'S9' => '아이엠증권',
'SA' => '현대차증권',
'SB' => '키움증권',
'SD' => 'SK증권',
'SE' => '대신증권',
'SG' => '한화투자증권',
'SH' => '하나금융투자',
'SI' => 'DB금융투자',
'SJ' => '유진투자증권',
'SK' => '메리츠증권',
'SM' => '부국증권',
'SN' => '신영증권',
'SO' => 'LIG투자증권',
'SP' => 'KTB투자증권(다올투자증권)',
'SQ' => '카카오페이증권',
'SR' => '펀드온라인코리아(한국포스증권)',
'ST' => '토스증권'
);
public array $cardCode = array(
'3K' => '기업 BC',
'46' => '광주은행',
'71' => '롯데카드',
'30' => '한국산업은행',
'31' => 'BC카드',
'51' => '삼성카드',
'38' => '새마을금고',
'41' => '신한카드',
'62' => '신협',
'36' => '씨티카드',
'33' => '우리BC카드(BC 매입)',
'W1' => '우리카드(우리 매입)',
'37' => '우체국예금보험',
'39' => '저축은행중앙회',
'35' => '전북은행',
'42' => '제주은행',
'15' => '카카오뱅크',
'3A' => '케이뱅크',
'24' => '토스뱅크',
'21' => '하나카드',
'61' => '현대카드',
'11' => 'KB국민카드',
'91' => 'NH농협카드',
'34' => 'Sh수협은행',
'PCP' => '페이코',
'KBS' => 'KB증권'
);
// 간편결제 제공업체 코드
public array $easyPayCode = array(
'TOSSPAY' => '토스페이',
'NAVERPAY' => '네이버페이',
'SAMSUNGPAY' => '삼성페이',
'APPLEPAY' => '애플페이',
'LPAY' => '엘페이',
'KAKAOPAY' => '카카오페이',
'PINPAY' => '핀페이',
'PAYCO' => '페이코',
'SSG' => 'SSG페이'
);
public function __construct(string $clientKey, string $secretKey, string $mId) {
$this->clientKey = $clientKey;
$this->secretKey = $secretKey;
$this->mId = $mId;
}
/**
* 헤더 시크릿 키 설정
* @return void
*/
private function setHeaderSecretKey(): void
{
$this->headerSecretKey = base64_encode($this->secretKey . ':');
}
/**
* 헤더 설정
* @return void
*/
public function setPaymentHeader(): void
{
$this->setHeaderSecretKey();
$this->headers = array(
'Authorization: Basic ' . $this->headerSecretKey,
'Content-Type' => 'Content-Type: application/json'
);
}
/**
* 결제 데이터 설정
*
* @param array $request
* @return void
*/
public function setPaymentData(array $request): void
{
$this->paymentData = array(
'amount' => $request['amount'],
'orderId' => $request['orderId'],
'paymentKey' => $request['paymentKey'],
);
}
/**
* 주문번호로 결제정보 조회
*
* @param string $orderId
* @return bool
*/
public function getPaymentByOrderId(string $orderId): bool
{
if (empty($orderId)) {
return false;
}
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, str_replace('{orderId}', $orderId, $this->paymentUrl));
curl_setopt($curl, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($curl, CURLOPT_SSLVERSION, 6);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 20);
$response = curl_exec($curl);
$return_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$this->responseData = json_decode($response, true);
curl_close($curl);
// 결제 실패 상황인 경우
if ($return_status != 200) {
return false;
}
return true;
}
/**
* 결제 승인
*
* @return bool
*/
public function approvePayment(): bool {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->acceptUrl);
curl_setopt($curl, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($this->paymentData));
curl_setopt($curl, CURLOPT_SSLVERSION, 6);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 20);
$response = curl_exec($curl);
$return_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$this->responseData = json_decode($response, true);
curl_close($curl);
// 결제 실패 상황인 경우
if ($return_status != 200 || ($this->responseData['status'] != 'DONE' && $this->responseData['status'] != 'WAITING_FOR_DEPOSIT')) {
return false;
}
return true;
}
/**
* 결제 취소 데이터 설정
*
* @param array $request
* @return void
*/
public function setCancelData(array $request): void
{
$this->cancelData = array(
'paymentKey' => $request['paymentKey'],
'cancelReason' => $request['cancelReason'],
);
// 부분취소 금액이 있는 경우
if (isset($request['cancelAmount']) && $request['cancelAmount'] > 0) {
$this->cancelData['cancelAmount'] = $request['cancelAmount'];
}
// 면세금액이 있는 경우
if (isset($request['taxFreeAmount']) && $request['taxFreeAmount'] > 0) {
$this->cancelData['taxFreeAmount'] = $request['taxFreeAmount'];
}
// 환불 계좌정보가 있는 경우 (가상계좌)
if (isset($request['refundReceiveAccount']) && is_array($request['refundReceiveAccount'])) {
$this->cancelData['refundReceiveAccount'] = array(
'bank' => $request['refundReceiveAccount']['bank'],
'accountNumber' => $request['refundReceiveAccount']['accountNumber'],
'holderName' => $request['refundReceiveAccount']['holderName'],
);
}
}
/**
* 결제 취소
*
* @return bool
*/
public function cancelPayment(): bool
{
// 취소에 필요한 결제 키가 있는지 여부
if (empty($this->cancelData['paymentKey'])) {
return false;
}
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, str_replace('{paymentKey}', $this->cancelData['paymentKey'], $this->cancelUrl));
curl_setopt($curl, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($this->cancelData));
curl_setopt($curl, CURLOPT_SSLVERSION, 6);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 20);
$response = curl_exec($curl);
$return_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$this->responseData = json_decode($response, true);
curl_close($curl);
// 결제 실패 상황인 경우
if ($return_status != 200) {
return false;
}
return true;
}
/**
* 현금영수증 발급 데이터 설정
*/
public function setCashReceiptsData(array $request): void
{
$this->cashReceiptsData = array(
'amount' => $request['amount'],
'orderId' => $request['orderId'],
'type' => $request['type'],
'customerIdentityNumber' => $request['customerIdentityNumber'],
'orderName' => $request['orderName'],
);
}
/**
* 현금영수증 발급
*/
public function issueCashReceipt(): bool
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->cashReceiptsUrl);
curl_setopt($curl, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($this->cashReceiptsData));
curl_setopt($curl, CURLOPT_SSLVERSION, 6);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_ENCODING, "");
curl_setopt($curl, CURLOPT_MAXREDIRS, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 20);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
$response = curl_exec($curl);
$return_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$this->responseData = json_decode($response, true);
curl_close($curl);
// 결제 실패 상황인 경우
if ($return_status != 200) {
return false;
}
return true;
}
/**
* 현금영수증 발급 취소
*/
public function cancelCashReceipt($receiptKey): bool
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->cashReceiptsUrl."/".$receiptKey."/cancel");
curl_setopt($curl, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($curl, CURLOPT_SSLVERSION, 6);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_ENCODING, "");
curl_setopt($curl, CURLOPT_MAXREDIRS, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 20);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST");
$response = curl_exec($curl);
$return_status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$this->responseData = json_decode($response, true);
curl_close($curl);
// 결제 실패 상황인 경우
if ($return_status != 200) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,79 @@
<?php
include_once('./_common.php');
// 토스페이먼츠 class
require_once(G5_SHOP_PATH.'/toss/toss.inc.php');
$toss = new TossPayments(
$config['cf_toss_client_key'],
$config['cf_toss_secret_key'],
$config['cf_lg_mid']
);
$orderId = isset($_REQUEST['orderId']) ? $_REQUEST['orderId'] : '';
$amount = isset($_REQUEST['amount']) ? $_REQUEST['amount'] : '';
$paymentKey = isset($_REQUEST['paymentKey']) ? $_REQUEST['paymentKey'] : '';
$toss->setPaymentData([
'orderId' => $orderId,
'amount' => $amount,
'paymentKey' => $paymentKey,
]);
// 장바구니 ID 설정 (바로구매 여부 확인)
$ss_cart_id = get_session('ss_direct') ? get_session('ss_cart_direct') : get_session('ss_cart_id');
// 임시데이터에 결제 데이터 저장
$addQuery = "";
if (isset($orderId)) {
$addQuery .= " AND od_id = '$orderId'";
}
if (isset($ss_cart_id)) {
$addQuery .= " AND cart_id = '$ss_cart_id'";
}
if (isset($member['mb_id'])) {
$addQuery .= " AND mb_id = '{$member['mb_id']}'";
}
if (empty($orderId) && empty($ss_cart_id)) {
alert('주문정보가 올바르지 않습니다.');
exit;
}
// 기존 dt_data 가져오기
$sql = "
SELECT * FROM {$g5['g5_shop_order_data_table']}
WHERE 1=1
{$addQuery}
LIMIT 1
";
$res = sql_fetch($sql);
$dt_data = [];
if (isset($res['dt_data'])) {
$dt_data = unserialize(base64_decode($res['dt_data']));
}
// dt_data 에 결제 키 추가
if (isset($paymentKey)) {
$dt_data['paymentKey'] = $paymentKey;
$dt_data_new = base64_encode(serialize($dt_data));
// 업데이트
$sql = "
UPDATE {$g5['g5_shop_order_data_table']} SET
dt_data = '".$dt_data_new."'
WHERE od_id = '$orderId'
{$addQuery}
";
sql_query($sql);
}
if(isset($payReqMap['pp_id']) && $payReqMap['pp_id']) {
$page_return_url = G5_SHOP_URL.'/personalpayform.php?pp_id='.$payReqMap['pp_id'];
} else {
$page_return_url = G5_SHOP_URL.'/orderform.php';
if ($_SESSION['ss_direct']) {
$page_return_url .= '?sw_direct=1';
}
}
?>

38
shop/toss/toss_cancel.php Normal file
View File

@ -0,0 +1,38 @@
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
// 토스 공통 설정
require_once(G5_SHOP_PATH.'/toss/toss.inc.php');
if (empty($cancel_msg)) {
alert('취소사유를 입력해 주세요.');
}
$toss = new TossPayments(
$config['cf_toss_client_key'],
$config['cf_toss_secret_key'],
$config['cf_lg_mid']
);
$toss->setPaymentHeader();
$od_id = isset($od['od_id']) ? $od['od_id'] : (isset($pp['pp_id']) ? $pp['pp_id'] : '');
if (!$toss->getPaymentByOrderId($od_id)) {
alert('결제정보를 가져올 수 없습니다.');
}
$toss->setCancelData(array(
'paymentKey' => $toss->responseData['paymentKey'],
'cancelReason' => $cancel_msg,
));
if (!$toss->cancelPayment()) {
$msg = '결제 취소에 실패하였습니다.\\n';
if (isset($toss->responseData['message'])) {
$msg .= '사유 : ' . $toss->responseData['message'] . '\\n';
}
if (isset($toss->responseData['code'])) {
$msg .= '코드 : ' . $toss->responseData['code'];
}
alert($msg);
}

108
shop/toss/toss_result.php Normal file
View File

@ -0,0 +1,108 @@
<?php
if (!defined("_GNUBOARD_")) exit; // 개별 페이지 접근 불가
// 토스페이먼츠 v2 공통 설정
require_once(G5_SHOP_PATH.'/toss/toss.inc.php');
$orderId = isset($_REQUEST['orderId']) ? $_REQUEST['orderId'] : '';
$paymentKey = isset($_POST['paymentKey']) ? $_POST['paymentKey'] : '';
if (empty($orderId) || empty($paymentKey)) {
alert('주문정보가 올바르지 않습니다.', G5_SHOP_URL);
}
$sql = " select * from {$g5['g5_shop_order_data_table']} where od_id = '$orderId' limit 1 ";
$row = sql_fetch($sql);
$data = isset($row['dt_data']) ? unserialize(base64_decode($row['dt_data'])) : array();
$amount = isset($data['amountValue']) ? $data['amountValue'] : 0;
if ($amount <= 0) {
alert('결제금액이 올바르지 않습니다.', G5_SHOP_URL);
}
$toss = new TossPayments(
$config['cf_toss_client_key'],
$config['cf_toss_secret_key'],
$config['cf_lg_mid']
);
// 결제데이터 셋팅
$toss->setPaymentData([
'amount' => $amount,
'orderId' => $orderId,
'paymentKey' => $paymentKey
]);
$toss->setPaymentHeader();
// 결제승인 요청
$result = $toss->approvePayment();
if ($result) {
// 결제승인 성공시 처리
$status = isset($toss->responseData['status']) ? $toss->responseData['status'] : '';
$method = isset($toss->responseData['method']) ? $toss->responseData['method'] : '';
// 가상계좌(VIRTUAL_ACCOUNT)만 입금대기(WAITING_FOR_DEPOSIT) 상태 값을 가질 수 있음
if ($status === 'DONE' || ($status === 'WAITING_FOR_DEPOSIT' && $method === '가상계좌')) {
// 공통 DB처리 변수 설정
$tno = isset($toss->responseData['paymentKey']) ? $toss->responseData['paymentKey'] : '';
$amount = isset($toss->responseData['totalAmount']) ? $toss->responseData['totalAmount'] : 0;
$escw_yn = $toss->responseData['useEscrow'] === true ? 'Y' : 'N';
$app_time = isset($toss->responseData['approvedAt']) ? date('Y-m-d H:i:s', strtotime($toss->responseData['approvedAt'])) : '';
// 결제수단별 데이터 처리 (카드, 가상계좌, 계좌이체, 휴대폰, 간편결제 순)
if ($method === '카드') {
// 카드
$app_no = $od_app_no = isset($toss->responseData['card']['approveNo']) ? $toss->responseData['card']['approveNo'] : '00000000';
$card_name = isset($toss->cardCode[$toss->responseData['card']['issuerCode']]) ? $toss->cardCode[$toss->responseData['card']['issuerCode']] : '';
} else if ($method === '가상계좌') {
// 가상계좌
$bank_name = $bankname = isset($toss->bankCode[$toss->responseData['virtualAccount']['bankCode']]) ? $toss->bankCode[$toss->responseData['virtualAccount']['bankCode']] : '';
$depositor = isset($toss->responseData['virtualAccount']['customerName']) ? $toss->responseData['virtualAccount']['customerName'] : '';
$account = isset($toss->responseData['virtualAccount']['accountNumber']) ? $toss->responseData['virtualAccount']['accountNumber'] : '';
} else if ($method === '계좌이체') {
// 계좌이체
$bank_name = isset($toss->bankCode[$toss->responseData['transfer']['bankCode']]) ? $toss->bankCode[$toss->responseData['transfer']['bankCode']] : '';
// 현금영수증 데이터 처리
$cashReceiptType = isset($toss->responseData['cashReceipt']['type']) ? $toss->responseData['cashReceipt']['type'] : '';
$RcptType = $cashReceiptType === '소득공제' ? '1' : ($cashReceiptType === '지출증빙' ? '2' : '0');
$RcptTID = isset($toss->responseData['cashReceipt']['receiptKey']) ? $toss->responseData['cashReceipt']['receiptKey'] : ''; // 현금영수증 TID, 현금영수증 거래인 경우 리턴
$RcptAuthCode = isset($toss->responseData['cashReceipt']['issueNumber']) ? $toss->responseData['cashReceipt']['issueNumber'] : ''; // 현금영수증 승인번호, 현금영수증 거래인 경우 리턴
$RcptReceiptUrl = isset($toss->responseData['cashReceipt']['receiptUrl']) ? $toss->responseData['cashReceipt']['receiptUrl'] : ''; // 현금영수증 URL
// 현금영수증 발급시 1 또는 2 이면
if ($RcptType) {
$pg_receipt_infos['od_cash'] = 1; // 현금영수증 발급인것으로 처리
$pg_receipt_infos['od_cash_no'] = $RcptAuthCode; // 현금영수증 승인번호
$pg_receipt_infos['od_cash_info'] = serialize(array('TID'=>$RcptTID, 'ApplNum'=>$RcptAuthCode, 'receiptUrl'=>$RcptReceiptUrl));
}
} else if ($method === '휴대폰') {
// 휴대폰
$mobile_no = isset($toss->responseData['mobilePhone']['customerMobilePhone']) ? $toss->responseData['mobilePhone']['customerMobilePhone'] : '';
} else if ($method === '간편결제') {
// 간편결제
$provider = isset($toss->responseData['easyPay']['provider']) ? $toss->responseData['easyPay']['provider'] : '';
$card_name = isset($toss->easyPayCode[$provider]) ? $toss->easyPayCode[$provider] : $provider;
}
} else {
if(G5_IS_MOBILE) {
if(isset($_POST['pp_id']) && $_POST['pp_id']) {
$page_return_url = G5_SHOP_URL.'/personalpayform.php?pp_id='.get_session('ss_personalpay_id');
} else {
$page_return_url = G5_SHOP_URL.'/orderform.php';
if(get_session('ss_direct'))
$page_return_url .= '?sw_direct=1';
}
alert($toss->responseData['message'].' 코드 : '.$toss->responseData['code'], $page_return_url);
} else {
alert($toss->responseData['message'].' 코드 : '.$toss->responseData['code'], G5_SHOP_URL.'/orderform.php');
}
}
} else {
alert($toss->responseData['message'].' 코드 : '.$toss->responseData['code'], G5_SHOP_URL);
}