결제 취소 후 재결제 시 카드결제 정보 선택 에러

결제를 취소 한 후에 재결제를 바로 시도했을 때 위젯이 렌더됨과 동시에 카드 결제 정보를 선택해달라는 메세지와 함께 결제 실패가 됩니다. code : NEED_CARD_PAYMENT_DETAIL message : 카드 결제 정보를 선택해주세요. orderId: 9f24bdc4-a7f8-4e47-b6bb-11fb044a9b19 재결제 시도는 최초 결제 시도와 똑같은 로직을 사용하고 있는데 왜 결제 취소 후 재결제 했을 때, 결제 위젯에서 카드사를 선택하기도 전에 카드 결제 정보를 선택해달라는 에러 메세지가 뜨고 결제가 취소되는지 모르겠습니다.... 제발 도와주세요
13 Replies
토스페이먼츠 BOT
⏳ 잠시만 기다려주세요! 곧 답변드리겠습니다
오류 문의일 경우 아래 정보를 미리 전달해주시면, 빠른 답변에 도움이 됩니다.
- 주문번호(orderId) : - 문의 내용 :
(img를 함께 첨부해주시면 도움이됩니다)
* 계약관련 내용은 1544-7772로 문의주세요. * 주말/공휴일에는 답변이 늦을 수 있어요.
이실장
이실장2y ago
정확한 상황을 파악하기 어려운데요! 혹시 영상으로 한번 남겨주실 수 있을까요?
Kimoon Lee
Kimoon Lee2y ago
재결제 시도시에 혹시 자동으로 requestpayments 를 호출하시나요?(고객의 액션없이)
yangnyeomcikin_37196
@이실장 @냥과장 영상과 같이 최초 결제 시도시에는 위젯이 잘 뜨고 취소를 할 경우에 취소 메세지와 코드를 바탕으로 재결제 모달을 띄웁니다! 이때 재 결제를 시도하면 카드 선택과 무관하게 (위젯이 뜨지만 보이지는 않는 것 같습니다) 바로 카드 선택을 하지 않았다는 NEED_CARD_PAYMENT_DETAIL 에러가 뜹니다... ㅜㅜ
Kimoon Lee
Kimoon Lee2y ago
결제연동을 잘못하신것 같습니다. 결제위젯이 정상적으로 렌더링이 되지 않은것 같은데요. 혹시 최초 결제창 띄우기 전에 브라우저의 콘솔상에 에러 없으신가요?
yangnyeomcikin_37196
@냥과장 @이실장 네 최초 결제창 띄우기 전에는 브라우저의 콘솔상 에러는 없습니다..ㅜㅜ 코드는
const fetchRenderPayment = async () => {
const paymentWidget = await loadPaymentWidget(clientKey, customerKey)
const paymentMethodsWidget = paymentWidget.renderPaymentMethods(selector, {
value: price,
currency: "KRW",
country: "KR",
})

paymentWidgetRef.current = paymentWidget
paymentMethodsWidgetRef.current = paymentMethodsWidget

triggerPaymentWidget()
}

// payment widget 렌더링
useEffect(() => {
if (result && price) {
fetchRenderPayment()
}
}, [result, price])

const handleClickPayButton = async () => {
try {
const res = await getStoredPaymentInfo(paymentInfo)
if (res.status === 201) {
await setResult(res.data.data)
await setPrice(res.data.data.finalAmount)
fetchRenderPayment()
}
} catch (error) {
console.error("Payment error:", error)
}
}
const fetchRenderPayment = async () => {
const paymentWidget = await loadPaymentWidget(clientKey, customerKey)
const paymentMethodsWidget = paymentWidget.renderPaymentMethods(selector, {
value: price,
currency: "KRW",
country: "KR",
})

paymentWidgetRef.current = paymentWidget
paymentMethodsWidgetRef.current = paymentMethodsWidget

triggerPaymentWidget()
}

// payment widget 렌더링
useEffect(() => {
if (result && price) {
fetchRenderPayment()
}
}, [result, price])

const handleClickPayButton = async () => {
try {
const res = await getStoredPaymentInfo(paymentInfo)
if (res.status === 201) {
await setResult(res.data.data)
await setPrice(res.data.data.finalAmount)
fetchRenderPayment()
}
} catch (error) {
console.error("Payment error:", error)
}
}
const triggerPaymentWidget = async () => {
const paymentWidget = paymentWidgetRef.current
try {
if (result) {
await paymentWidget?.requestPayment({
orderId: result.orderId || "",
orderName: paymentInfo.orderName,
customerName: paymentInfo.customerName,
customerEmail: paymentInfo.customerEmail,
successUrl: `${process.env.NEXT_PUBLIC_PAYMENT_SUCCESS_URL}?couponId=${paymentInfo.couponId}`,
failUrl: `${process.env.NEXT_PUBLIC_PAYMENT_FAIL_URL}/${paymentInfo.courseId}`,
})
}
} catch (error: any) {
console.log(error.message, error.code)
// 토스 결제 요청 실패 에러 발생시 결제 취소 페이지로 라우팅
if (error.message && error.code) {
const { message, code } = error
router.replace(`/order/${paymentInfo.courseId}?message=${message}&code=${code}&orderId=${result.orderId}`)
}
}
}
return (
<>
<div id="payment-widget" />
<button
type="submit"
onClick={handleClickPayButton}
className={`rounded-md py-[18px] leading-[19px] text-white ${
!isDisabled ? "cursor-default bg-primary-customGray bg-opacity-[0.6]" : "cursor-pointer bg-primary-customPink"
}`}
disabled={!isDisabled}
>
강의 결제하기
</button>
</>
)
const triggerPaymentWidget = async () => {
const paymentWidget = paymentWidgetRef.current
try {
if (result) {
await paymentWidget?.requestPayment({
orderId: result.orderId || "",
orderName: paymentInfo.orderName,
customerName: paymentInfo.customerName,
customerEmail: paymentInfo.customerEmail,
successUrl: `${process.env.NEXT_PUBLIC_PAYMENT_SUCCESS_URL}?couponId=${paymentInfo.couponId}`,
failUrl: `${process.env.NEXT_PUBLIC_PAYMENT_FAIL_URL}/${paymentInfo.courseId}`,
})
}
} catch (error: any) {
console.log(error.message, error.code)
// 토스 결제 요청 실패 에러 발생시 결제 취소 페이지로 라우팅
if (error.message && error.code) {
const { message, code } = error
router.replace(`/order/${paymentInfo.courseId}?message=${message}&code=${code}&orderId=${result.orderId}`)
}
}
}
return (
<>
<div id="payment-widget" />
<button
type="submit"
onClick={handleClickPayButton}
className={`rounded-md py-[18px] leading-[19px] text-white ${
!isDisabled ? "cursor-default bg-primary-customGray bg-opacity-[0.6]" : "cursor-pointer bg-primary-customPink"
}`}
disabled={!isDisabled}
>
강의 결제하기
</button>
</>
)
위와 같습니다(코드가 길어서 나눠서 보냈습니다.)
yangnyeomcikin_37196
최초 결제 콘솔창과 함께 보여지는 영상 입니다!
Kimoon Lee
Kimoon Lee2y ago
결제위젯은 저렇게 별도로 토스페이먼츠 결제창이 뜨는 방식이 아닙니다. selector 에 어떤값이 들어가는지 알수 있을까요? 이 값에 html 페이지의 layer 가 있어야 합니다.
Kimoon Lee
Kimoon Lee2y ago
react 쓰시는 것으로 보이는데, https://github.com/tosspayments/payment-widget-sample/tree/main/react 여기 샘플을 참고해 주세요.
GitHub
payment-widget-sample/react at main · tosspayments/payment-widget-s...
토스페이먼츠 결제위젯 샘플 앱입니다. . Contribute to tosspayments/payment-widget-sample development by creating an account on GitHub.
yangnyeomcikin_37196
const selector = "#payment-widget"| ... 중략 return ( <> <div id="payment-widget" /> <button onClick={handleClickPayButton}> 강의 결제하기 </button> </> ) selector에는 payment-widget이 들어있고 버튼위의 div가 payment-widget입니다! 지금 웹 뷰에서는 최초 결제 시도시에는 결제창이 뜨고, 취소후 재결제 했을때는 안 뜨는것처럼 보이지만 결제창이 뜨는 것 같습니다. 취소후 재결제하고 모바일뷰로 바꿨을때는 결제창이 하단에 노출되어있습니다. 제가 원하는 동작(결제 취소 후 재결제 시도시 다시 결제 창이 뜨는것)이 모바일 뷰에서는 정상적으로 되는데, 웹 뷰에서만 안되고 있는데 이유를 잘 모르겠습니다.. 제가 모바일에서는 결제 위젯을 사용하고 웹에서는 결제창 방식을 사용하고 있는데, 웹에서 결제 위젯이 hidden 된 상태로 최초 결제시에는 정상 동작(결제창으로 결제)이 되지만 결제 취소 후에도 여전히 hidden 된 결제 위젯이 남아있어서 결제창으로 재결제를 시도하는게 아니라 hidden 된 결제 위젯으로 재결제가 시도되어서 카드를 선택하지 않고 결제하려고 했다는 에러를 받은 것 같습니다! 셀렉터를 인지하는 div의 id 값을 웹뷰 모바일뷰 분기처리해서 모바일뷰일때만 payment-widget을 줘서 위젯으로 렌더링하고 웹뷰에서는 결제창으로 렌더링해서 해결했습니다!
Kimoon Lee
Kimoon Lee2y ago
아 두개를 다 쓰시는 거군요.. 저는 위젯을 쓰시는데 결제창이 열리는 것으로 생각했네요.
yangnyeomcikin_37196
넵.... ㅎㅎ 감사합니다!!
토스페이먼츠 BOT
❤️ 기술문의 경험이 어떠셨나요?!
간단히 코멘트 남겨주세요! 제품 발전에 큰 힘이 됩니다.

Did you find this page helpful?