Feat: 토스페이 전환 및 서비스 정책 추가 [JDDEV-73] - #107
Conversation
📝 WalkthroughWalkthroughThe PR adds PortOne payment handling, confirmation feedback, credit validity messaging, and policy pages with terms/privacy navigation. ChangesCredit payment and policy experience
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ModalPurchase
participant PortOne
participant CreditContent
participant Router
ModalPurchase->>PortOne: requestPayment with card configuration
PortOne-->>ModalPurchase: payment result or error
ModalPurchase->>ModalPurchase: display toast and reset loading
CreditContent->>CreditContent: read payment query parameters
CreditContent->>CreditContent: confirm purchase
CreditContent->>Router: replace /credit
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@jobdri/src/app/credit/page.tsx`:
- Around line 47-54: Update the confirmation flow around confirmPurchase and the
finally block so paymentKey, orderId, and amount are preserved when confirmation
fails; only redirect with router.replace("/credit") after successful
confirmation, while retaining the callback parameters and enabling an idempotent
retry or recovery path for failures.
In `@jobdri/src/components/common/modal/ModalPurchase.tsx`:
- Around line 52-55: Update ModalPurchase so TOSSPAY requests no longer use
flowMode "DIRECT" without consent: present the required payment terms, capture
and validate the user’s explicit agreement, and only then call
payment.requestPayment(...). Preserve the existing payment flow for users who
have not consented by preventing the request until agreement is recorded.
- Around line 51-67: Update the payment flow in ModalPurchase, including
requestPayment and its successUrl/failUrl handling, so redirect failures are
processed on the failure route and display the existing payment-error toast. Do
not rely on requestPayment returning a result or catch block for
cancellations/declines; keep the submit action locked through navigation by
passing the loading/disabled state through ButtonCtaModal, adding the necessary
prop support there since it currently does not forward or receive it.
In `@jobdri/src/constants/policy.ts`:
- Around line 2-3: Replace every legal-policy placeholder in
jobdri/src/constants/policy.ts: finalize the terms effective and revision dates,
addendum dates, privacy-policy notice and effective dates, and populate both
이전받는 자 연락처 cells for Vercel Inc. and OpenAI, L.L.C. Update the affected sections
at lines 2-3, 119-121, 125-126, and 249-257; do not leave any template
placeholders unresolved.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5cda6b16-8d6c-4992-aa8d-3fc919a229f8
⛔ Files ignored due to path filters (1)
jobdri/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
jobdri/package.jsonjobdri/src/app/credit/page.tsxjobdri/src/app/policy/page.tsxjobdri/src/components/common/cards/CreditCard.tsxjobdri/src/components/common/footer/BusinessFooter.tsxjobdri/src/components/common/modal/ModalPurchase.tsxjobdri/src/constants/policy.ts
| alert("크레딧 충전이 완료되었습니다!"); | ||
| } catch (error) { | ||
| console.error("결제 승인 실패:", error); | ||
| alert("결제 승인에 실패했습니다. 다시 시도해 주세요."); | ||
| } finally { | ||
| setIsConfirming(false); | ||
| // 새로고침 시 중복 호출 방지 | ||
| router.replace("/credit"); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file =="
fd -a 'page\.tsx$' jobdri 2>/dev/null | rg '/credit/page\.tsx|credit/page\.tsx' || true
echo "== file outline =="
ast-grep outline jobdri/src/app/credit/page.tsx --view expanded || true
echo "== relevant lines =="
sed -n '1,180p' jobdri/src/app/credit/page.tsx | nl -ba
echo "== related payment confirm usages =="
rg -n "confirmPurchase|paymentKey|orderId|processConfirm|setIsConfirming|router\.replace\(\"/credit\"\)" jobdri/src/app/credit -S || trueRepository: JobDri-Developer/FrontEnd
Length of output: 501
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== git status/diff summary =="
git diff --stat || true
echo "== focused diff for credit page =="
git diff -- jobdri/src/app/credit/page.tsx | sed -n '1,220p' || trueRepository: JobDri-Developer/FrontEnd
Length of output: 228
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== credit page content =="
sed -n '1,180p' jobdri/src/app/credit/page.tsx | awk '{ printf "%5d\t%s\n", NR, $0 }'
echo "== payment-related searches in jobdri/src =="
rg -n "confirmPurchase|paymentKey|orderId|processConfirm|setIsConfirming|router\.replace\(\"/credit\"\)|토스페이먼츠|tosspayments" jobdri/src/app -S || trueRepository: JobDri-Developer/FrontEnd
Length of output: 5184
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== credit api file candidates =="
fd -a 'credit\.(ts|tsx|js)$|credit' jobdri/src/lib | sort | sed -n '1,80p'
echo "== confirmPurchase definitions =="
rg -n "function confirmPurchase|const confirmPurchase|export .*confirmPurchase|export async function confirmPurchase|confirmPurchase\s*=" jobdri/src -S || true
echo "== relevant API source snippets =="
while IFS= read -r f; do
echo "--- $f"
grep -n "confirmPurchase\|paymentKey\|orderId\|amount" "$f" | sed -n '1,120p' || true
done < <(rg -l "confirmPurchase" jobdri/src -S)
echo "== read credit api top sections =="
while IFS= read -r f; do
echo "--- $f"
sed -n '1,220p' "$f" | awk '{ printf "%5d\t%s\n", NR, $0 }'
done < <(rg -l "confirmPurchase" jobdri/src -S)Repository: JobDri-Developer/FrontEnd
Length of output: 438
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== credit api top sections =="
sed -n '1,180p' jobdri/src/lib/api/credit.ts | awk '{ printf "%5d\t%s\n", NR, $0 }'
echo "== related backend/reconciliation API candidates =="
rg -n "confirmPurchase|order\\.?id|payment\\.?key|credit|충전|충전내역|usage|내역" . -S --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,240p' || trueRepository: JobDri-Developer/FrontEnd
Length of output: 8792
Preserve payment callback data before redirecting.
router.replace("/credit") is called in finally, so any failure in confirmPurchase(paymentKey, orderId, amount) redirects to /credit without the authenticated payment callback parameters. A transient confirmation failure then leaves the user unable to retry or reconcile the payment. Call /credit successfully before redirecting, and allow idempotent retry/recovery using the same parameter set on failure.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@jobdri/src/app/credit/page.tsx` around lines 47 - 54, Update the confirmation
flow around confirmPurchase and the finally block so paymentKey, orderId, and
amount are preserved when confirmation fails; only redirect with
router.replace("/credit") after successful confirmation, while retaining the
callback parameters and enabling an idempotent retry or recovery path for
failures.
| failUrl: `${window.location.origin}/credit/fail`, | ||
| card: { | ||
| easyPay: "TOSSPAY", | ||
| flowMode: "DIRECT", | ||
| }, | ||
| }); | ||
| } catch { | ||
| } catch (error: unknown) { | ||
| const tossError = error as { code?: string; message?: string }; | ||
|
|
||
| if (tossError.code === "USER_CANCEL") { | ||
| setToastMessage("결제를 취소하였습니다."); | ||
| } else { | ||
| console.error("결제 창 호출 중 오류:", tossError.message || error); | ||
| setToastMessage("결제 진행 중 오류가 발생했습니다."); | ||
| } | ||
| } finally { | ||
| setIsLoading(false); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## locate file"
fd -a 'ModalPurchase\.tsx$' . || true
echo "## file excerpt"
if [ -f jobdri/src/components/common/modal/ModalPurchase.tsx ]; then
nl -ba jobdri/src/components/common/modal/ModalPurchase.tsx | sed -n '1,160p'
fi
echo "## locate ButtonCtaModal definitions/usages"
rg -n "ButtonCtaModal|ModalPurchase|requestPayment|easyPay|flowMode|successUrl|failUrl" jobdri/src -S
echo "## package/toss versions"
fd 'package\.json$|pnpm-lock\.yaml$|yarn\.lock$|package-lock\.json$' . -d 3 -x sh -c 'echo "--- $1"; rg -n "tosspayments|react|toss" "$1" -S || true' sh {}Repository: JobDri-Developer/FrontEnd
Length of output: 306
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## ModalPurchase excerpt"
if [ -f jobdri/src/components/common/modal/ModalPurchase.tsx ]; then
awk '{printf "%6d %s\n", NR, $0}' jobdri/src/components/common/modal/ModalPurchase.tsx | sed -n '1,180p'
fi
echo "## ButtonCtaModal definitions/usages"
rg -n "ButtonCtaModal|ModalPurchase|requestPayment|easyPay|flowMode|successUrl|failUrl|disabled|isLoading" jobdri/src -S || true
echo "## package files"
fd 'package\.json$|pnpm-lock\.yaml$|yarn\.lock$|package-lock\.json$' . -d 3 -x sh -c 'echo "--- $1"; rg -n "tosspayments|react|toss|`@toss`" "$1" -S || true' sh {} || trueRepository: JobDri-Developer/FrontEnd
Length of output: 34509
🌐 Web query:
Toss Payments Web SDK v2 requestPayment successUrl failUrl returns void
💡 Result:
When using the Toss Payments Web SDK v2, the requestPayment method's behavior depends on whether you provide successUrl and failUrl parameters [1]. 1. Redirect Method: If you include successUrl and failUrl in your requestPayment call, the SDK uses a redirect-based flow [1][2]. In this scenario, the browser redirects the user to the specified URL upon completion (success or failure), and the requestPayment method itself returns void [1]. 2. Promise Method: If you omit successUrl and failUrl, the SDK can return a Promise that resolves with the payment result [1][3][4]. This approach allows you to handle the result directly in your code using.then or await without a page redirection [3][4]. Note that the Promise method is primarily intended for desktop (PC) environments and is not supported in mobile environments [1][3]. If you pass successUrl or failUrl, the SDK explicitly does not support the Promise return style, as it assumes a redirect flow is required [1][3].
Citations:
- 1: https://docs.tosspayments.com/sdk/v2/js
- 2: https://docs.tosspayments.com/guides/v2/payment-widget/integration-window
- 3: https://techchat.tosspayments.com/m/1306122149312073768
- 4: https://velog.io/@tosspayments/%EC%98%88%EC%A0%9C%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-awaitasync-%EB%AC%B8%EB%B2%95
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## ButtonCtaModal implementation"
awk '{printf "%6d %s\n", NR, $0}' jobdri/src/components/common/buttons/ButtonCtaModal.tsx | sed -n '1,140p'
echo "## route files"
rg -n "credit/fail|failUrl|error|code|message" jobdri/src -S || true
fd -a 'page\.tsx$' jobdri/src/app | xargs rg -n "FAIL|fail|error|code|message" -S || trueRepository: JobDri-Developer/FrontEnd
Length of output: 36355
Handle redirect failures and submit locking.
With successUrl/failUrl, Toss Payments redirects on success/failure and requestPayment() returns void, so cancellations and declines won’t show this toast. Add failure handling to the failure route and keep the submit button locked until navigation starts; ButtonCtaModal does not forward/receive this locked state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@jobdri/src/components/common/modal/ModalPurchase.tsx` around lines 51 - 67,
Update the payment flow in ModalPurchase, including requestPayment and its
successUrl/failUrl handling, so redirect failures are processed on the failure
route and display the existing payment-error toast. Do not rely on
requestPayment returning a result or catch block for cancellations/declines;
keep the submit action locked through navigation by passing the loading/disabled
state through ButtonCtaModal, adding the necessary prop support there since it
currently does not forward or receive it.
| card: { | ||
| easyPay: "TOSSPAY", | ||
| flowMode: "DIRECT", | ||
| }, |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate ModalPurchase =="
fd -a 'ModalPurchase\.tsx$' . || true
file="$(fd 'ModalPurchase\.tsx$' . | head -n1 || true)"
if [ -n "$file" ]; then
echo "== outline =="
ast-grep outline "$file" || true
echo "== relevant file lines =="
nl -ba "$file" | sed -n '1,140p'
fi
echo "== search for TOSSPAY/flowMode/DIRECT/Toss consent terms =="
rg -n 'TOSSPAY|flowMode|"DIRECT"|TossPay|tosspayments|consent|PaymentTerms|가맹점거래약관|이용약관|정보수집동의|회원가입약관' -S .Repository: JobDri-Developer/FrontEnd
Length of output: 546
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read-only behavioral/static check of JSX inputs around selected easy-pay and consent UI.
python3 - <<'PY'
from pathlib import Path
p = list(Path('.').rglob('ModalPurchase.tsx'))[0] if list(Path('.').rglob('ModalPurchase.tsx')) else None
print("file", p)
if p:
text = p.read_text()
tokens = ["TOSSPAY", "flowMode", "DIRECT", "consent", "PaymentTerms", "동의", "약관", "Toss"]
for t in tokens:
print(t, text.find(t))
PYRepository: JobDri-Developer/FrontEnd
Length of output: 307
🌐 Web query:
Toss Payments integration flowMode DIRECT contract payment terms consent required
💡 Result:
In the Toss Payments integration, setting flowMode to DIRECT allows you to bypass the default hosted payment window and open a specific payment method's interface directly [1][2][3]. Regarding customer consent for payment terms when using flowMode: DIRECT: 1. Requirement: When using a direct integration (flowMode: DIRECT), you are responsible for obtaining the necessary customer consent for terms and conditions within your own checkout UI [1]. Because you are bypassing the standard Toss Payments hosted window—which typically handles the display and collection of legal and payment terms—you must ensure your implementation complies with local regulations by presenting these terms to the user and capturing their consent before initiating the payment request [1][2]. 2. Technical Implementation: When you call the requestPayment method with flowMode: DIRECT, you specify the desired payment method using the cardCompany or easyPay parameters [1][3]. Since the interface is now essentially "direct" or "native" to your checkout flow, Toss Payments does not automatically present the standard terms and conditions screens that would appear in the DEFAULT hosted flow [2]. Therefore, you must manage the UI/UX to include the required disclosures and a mechanism (such as a checkbox or button) for the user to confirm their agreement before the SDK is invoked [1]. 3. Context: Direct integration offers a more seamless experience but places the burden of compliance and UI design on the developer [2]. If you find managing individual payment terms and UI elements too complex, Toss Payments recommends using the Payment Widget, which provides a pre-built, compliant UI that manages these consent requirements automatically [2]. For specific legal language or compliance requirements regarding what terms must be displayed, refer to the Electronic Financial Transactions Act and your specific service contract with Toss Payments, as these govern your obligations as a merchant [4][5].
Citations:
- 1: https://docs.tosspayments.com/en/integration
- 2: https://docs.tosspayments.com/en/integration-types
- 3: https://docs.tosspayments.com/sdk/v2/js
- 4: https://docs.tosspayments.com/en/overview
- 5: https://pages.tosspayments.com/terms/en/paypal/general_terms_and_conditions
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file =="
cat -n jobdri/src/components/common/modal/ModalPurchase.tsx | sed -n '135,170p'
echo "== Toss payments requestPayment usages =="
rg -n "requestPayment\\(|card:\\{|easyPay:\s*" -S .Repository: JobDri-Developer/FrontEnd
Length of output: 399
Gate TossPayments requests behind explicit payment-term consent.
flowMode: "DIRECT" bypasses Toss’ hosted consent UI, so this component must present and capture agreement to the required terms before calling payment.requestPayment(...) for TOSSPAY.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@jobdri/src/components/common/modal/ModalPurchase.tsx` around lines 52 - 55,
Update ModalPurchase so TOSSPAY requests no longer use flowMode "DIRECT" without
consent: present the required payment terms, capture and validate the user’s
explicit agreement, and only then call payment.requestPayment(...). Preserve the
existing payment flow for users who have not consented by preventing the request
until agreement is recorded.
| 시행일: [YYYY년 MM월 DD일] | ||
| 최종 개정일: [YYYY년 MM월 DD일] |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '^jobdri/src/constants/policy\.ts$' || true
echo "== policy.ts relevant sections =="
if [ -f jobdri/src/constants/policy.ts ]; then
wc -l jobdri/src/constants/policy.ts
sed -n '1,20p;112,132p;235,265p' jobdri/src/constants/policy.ts
fi
echo "== placeholder occurrences =="
rg -n '\[YYYY년 MM월 DD일\]|\[확인 후 기재\]' jobdri/src/constants/policy.ts || true
echo "== overseas/recipient terms nearby =="
rg -n -C 4 'Oversea|overseas|해외|OpenAI|채용공고 텍스트|제3자 제공|통신요건|휴대폰|카카오톡' jobdri/src/constants/policy.ts || trueRepository: JobDri-Developer/FrontEnd
Length of output: 10484
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Korean Personal Information Protection Act overseas transfer statutory text =="
python3 - <<'PY'
from urllib.request import urlopen
urls = [
'https://www.law.go.kr/LSW/lsLinkCommonInfo.do?chrClsCd=010202&lsJoLnkSeq=1029334869',
]
for url in urls:
try:
data = urlopen(url, timeout=20).read().decode('utf-8', 'replace')[:5000]
print(url, len(data))
for needle in ['제17조', '국제전화', '수익자', '휴대폰 번호', '이메일', 'OpenViz']:
idx = data.find(needle)
if idx != -1:
print(f'--- found {needle} at {idx} ---')
print(data[max(0, idx-1200):idx+1600])
except Exception as e:
print('ERROR', url, type(e).__name__, e)
PYRepository: openlaw
Length of output: 2006
🌐 Web query:
Korean Personal Information Protection Act Article 17 cross-border transfer mandatory items recipient contact information
💡 Result:
Under the South Korean Personal Information Protection Act (PIPA), when a personal information controller transfers personal information to a third party overseas, they are required to notify the data subject of specific details and obtain their separate consent [1][2][3]. Regarding the recipient's information, the PIPA mandates the disclosure of: 1. The name of the recipient (or, if the recipient is a corporation, its name and contact information) [4][2]. 2. More generally, the "recipient" and "contact information of the person receiving the information" are required components of the mandatory notification items [1][2]. In practice, this means controllers must provide the data subject with the recipient's identity and sufficient contact information to enable the data subject to identify and reach the party receiving their data [1][4][2]. The broader list of mandatory items that must be notified to the data subject when seeking consent for a cross-border transfer typically includes [1][2]: - The identity of the recipient (name and contact details) [1][4][2]. - The country to which the personal information is being transferred [1][2][5]. - The timing and method of the transfer (or date/time and method of transmission) [4][2]. - The purposes for which the recipient will use the personal information [1][2][5]. - The items of personal information to be transferred [1][2][5]. - The period during which the recipient will retain and use the personal information [1][2][5]. - The right of the data subject to refuse consent and the potential consequences of such refusal [1][6][4][2]. These requirements ensure that data subjects are fully informed before their information leaves the jurisdiction, maintaining transparency about who will hold their data, where it will be stored, and how it will be used [1][5]. Controllers are also obligated to ensure that any contract entered into for such a transfer does not violate the provisions of the PIPA [7][8][6].
Citations:
- 1: https://bifrostindex.ai/guides/south-korea/international-data-transfers
- 2: https://www.dlapiperdataprotection.com/index.html?c=KR&t=transfer
- 3: https://law.asia/doing-business-in-korea-data-privacy-compliance/
- 4: https://www.multilaw.com/Multilaw/Data_Protection_Laws_Guide/DataProtection_Guide_Korea.aspx
- 5: https://www.startcompanykorea.com/posts/cross-border-data-transfer-pipa-korea-2026/
- 6: http://www.koreanlii.or.kr/w/images/b/b9/PIPAct1601en.pdf
- 7: https://elaw.klri.re.kr/kor_service/lawViewMultiContent.do?hseq=53044
- 8: https://assets.publishing.service.gov.uk/media/6373c3e7e90e0728553b561f/English_-_Supplementary_rules_for_the_interpretation_and_application_of_the_Personal_Information_Protection_Act.pdf
Finalize all legal-policy placeholders before release.
The terms/privacy policy templates still expose unresolved dates and overseas-transfer recipient contacts. For jobdri/src/constants/policy.ts, fill the terms effective/revision dates and addendum dates, the privacy-policy notice/effective dates, and both 이전받는 자 연락처 cells for Vercel Inc. and OpenAI, L.L.C.
📍 Affects 1 file
jobdri/src/constants/policy.ts#L2-L3(this comment)jobdri/src/constants/policy.ts#L119-L121jobdri/src/constants/policy.ts#L125-L126jobdri/src/constants/policy.ts#L249-L257
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@jobdri/src/constants/policy.ts` around lines 2 - 3, Replace every
legal-policy placeholder in jobdri/src/constants/policy.ts: finalize the terms
effective and revision dates, addendum dates, privacy-policy notice and
effective dates, and populate both 이전받는 자 연락처 cells for Vercel Inc. and OpenAI,
L.L.C. Update the affected sections at lines 2-3, 119-121, 125-126, and 249-257;
do not leave any template placeholders unresolved.
🔗 관련 이슈
JDDEV-73
⌨️ 작업 상세 내용
💡 코드 설명 및 참고사항
📸 스크린샷 (UI 변경 시)
🔍 리뷰 요구사항 (Reviewers)
Summary by CodeRabbit
New Features
Improvements