안녕하세요. 아는형입니다.
얼마 전 프로젝트에서 As-Is 분석을 하던 중에 개선요구사항 하나가 올라왔습니다. 펌뱅킹으로 외화 송금을 처리하는데 은행에서 정한 최대 한도가 100억으로 걸려 있다는 거였어요. 문제가 된 건은 금융상품 차입 상환 건이었는데, 등록된 금액을 그대로 지급전표로 생성하다 보니 타행이체 한도 때문에 지급 자체가 안 되는 상황이었습니다.
그래서 현업에서는 어쩔 수 없이 금융상품의 차입 데이터를 건드려서 100억 기준으로 상환전표를 여러 개 나눠 만드는 방식으로 대응해왔다고 하더라고요. 근데 이렇게 데이터를 쪼개다 보니 원래 입력해둔 금융상품 데이터가 틀어지는 부작용이 생겼습니다. 상환 스케줄이나 잔액 관리가 꼬이는 거죠. 고객사 입장에서는 매번 이렇게 수작업으로 쪼개는 것도 번거롭고 데이터 정합성도 깨지니, 외화 지급건에 한해서는 100억 단위로 자동 분할해달라는 요구사항을 절실하게 이야기해주셨습니다.
이걸 어떻게 처리했는지, 그리고 처리하다가 제가 어디서 삽질을 했는지 한번 풀어보려고 합니다.
외화 펌뱅킹 타행이체, 왜 한도가 있을까
결론부터 말씀드리면 이건 SAP 자체의 제한이 아닙니다. 대부분 은행 또는 펌뱅킹(Firm Banking) 서비스 쪽 채널 한도예요. 외화 타행이체는 당행계좌 간 이체와 다르게 송금은행 → 국내외 결제망 → 상대은행으로 자금이 넘어가기 때문에, 금액이 커질수록 리스크도 같이 커집니다.
정리하면 이런 이유들이 있습니다.
- 대형 금융사고 방지 — 펌뱅킹은 ERP에서 대량·자동으로 지급되는 경우가 많습니다. 프로그램 오류나 계좌번호 오류가 났을 때 500억, 1,000억이 한 번에 나가버리면 피해 규모가 너무 커지죠. 그래서 1회 한도를 걸어 사고의 최대 손실 규모를 통제하는 겁니다.
- 보안·Fraud/AML 통제 — 고액 외화송금은 자금세탁방지(AML), 이상거래탐지(FDS), 제재대상자 Screening 같은 추가 점검 대상이 될 수 있습니다. 일정 금액 이상은 자동 펌뱅킹 처리 대신 은행 담당자 확인이나 별도 승인 절차를 요구하도록 설계되는 경우가 있어요.
- 은행 간 결제·유동성 리스크 — 타행송금은 실제 은행 간 자금결제가 수반됩니다. 특히 USD 같은 경우 Correspondent Bank나 Nostro 계좌를 거칠 수도 있어서, 큰 금액을 한 건으로 처리하면 은행 입장에서도 결제·유동성 관리 부담이 커집니다.
- 펌뱅킹 채널 자체의 운영 한도 — 은행 창구나 별도 Deal 방식으로는 더 큰 금액도 처리할 수 있지만, 펌뱅킹·Host-to-Host·CMS 같은 자동화 채널에는 건당·일 한도가 별도로 걸려 있습니다. 그러니까 “회사가 100억 이상 송금할 수 없다”가 아니라 “펌뱅킹 한 건으로 100억을 넘길 수 없다”는 의미인 경우가 많습니다.
그럼 왜 어떤 법인은 10억, 어떤 법인은 100억일까
문득 예전에 운영했던 타 계열사는 10억이 한도였던 것 같았는데, 이번 고객사의 경우는 100억이더라고요. 똑같은 은행, 똑같은 서비스인데 한 곳은 타행이체 1회 한도가 10억 원이고, 다른 한 곳은 100억 원이었습니다. “왜 이렇게 다르지?” 싶어서 궁금함에 아는 은행 담당자에게 물어봤던 기억이 있습니다.
같은 은행, 같은 서비스인데 계열사마다 한도가 다르다면 높은 확률로 법인별 펌뱅킹 계약 당시 설정한 1회 송금한도, 혹은 SAP/펌뱅킹 채널의 법인별 설정값 차이입니다. 몇 가지로 나눠서 볼 수 있어요.
① 회사가 은행에 신청한 한도
처음 펌뱅킹 계약할 때 회사가 굳이 높은 한도를 요청하지 않았다면, 기본 한도 그대로 계약돼 있을 수 있습니다.
② 과거 업무 요구사항
A사는 과거 최대 외화지급이 5억 정도였다면 10억 한도로도 충분했을 거고, B사는 해외 자재대금 등으로 50~80억 원 지급이 자주 발생해서 100억으로 증액했을 수 있습니다. 실무에서는 이런 이력이 그대로 한도 설정에 남아있는 경우가 은근히 많더라고요.
③ 펌뱅킹 상품·서비스 자체가 다름
겉으로는 다 “펌뱅킹”이라고 부르지만, 실제 은행 상품은 다를 수 있습니다. 기업인터넷뱅킹 / Host-to-Host / ERP 펌뱅킹 / 대량외화송금 / 기업전용송금 등 서비스에 따라 적용되는 이체한도 정책이 다르거든요.
실제 한도를 확인하고 싶다면 은행에 이렇게 물어보세요
지금 담당 법인이 얼마까지 타행이체가 가능한지 정확히 확인하려면, 은행 담당자에게 직접 물어보는 게 제일 빠릅니다. 이런 식으로 질문하시면 됩니다.
“외화 펌뱅킹 타행이체 건당 한도가 A법인은 10억원, B법인은 100억원으로 설정되어 있는데, 해당 한도가 은행 상품의 기본 한도인지 아니면 법인별 펌뱅킹 계약 시 신청한 약정 한도인지 확인 부탁드립니다. 또한 건당 한도와 일 한도, 외화 환산 기준도 함께 확인 부탁드립니다.”
특히 다음 5가지는 꼭 받아두시는 걸 추천합니다.
- 1회 이체한도
- 1일 이체한도
- 타행/당행 한도 차이
- 원화환산 외화 한도 적용 기준
- 한도 초과 시 전문 Reject인지, 분할 송금이 가능한지
자, 여기까지가 배경이고 — 이제 진짜 문제가 시작되는 지점입니다.
한도 넘으면 에러? 100억 분할 로직을 개발한 이야기
한도가 왜 있는지, 왜 계열사마다 다른지는 확인이 됐는데, 그렇다고 ERP에서 “한도 넘으면 그냥 에러 띄우고 끝” 할 수는 없잖아요. 프로젝트를 진행하면서 이 부분을 실제로 개발했던 이야기를 해보려고 합니다. 100억을 초과하는 건은 100억 단위로 쪼개서 여러 건으로 전문을 만들어 보내는, 이른바 100억 분할 로직입니다.
여기서 저도 한 번 삽질을 했는데요. 처음 설계 후 프로그램을 만들었을때 생성된 금액이 1억 단위로 쪼개져서 엇 뭐지 했던 기억이 있습니다. 코드를 다시 봤을 때 IF GT_MAIN-RWBTR > 100000000. 이 조건문을 보고 “어? 이거 100억이 아니라 1억 아닌가?” 하고 순간 헷갈렸습니다. 자릿수를 세어보면 100,000,000은 분명 1억이거든요.
근데 알고 보니 이게 SAP 금액 필드의 저장 방식 때문이었습니다. 해당 테이블 필드는 소수점 이하 2자리 기준으로 저장되는 구조라서, 예를 들어 실제 표시 금액이 10.00이면 테이블 내부값은 1000으로 저장됩니다. 반대로 말하면 내부 저장값에서 뒤 두 자리(00)를 떼어낸 게 실제 표시 금액인 셈이죠. 그래서 코드 상의 100000000이 화면·전문상으로는 정확히 100억을 의미하게 되는 겁니다.
이 필드 성격을 모르고 코드를 짜다보면 저처럼 헷갈릴 수 있으니, 혹시 비슷한 코드 리뷰하시는 분들은 참고하시면 좋을 것 같습니다.
로직 흐름 요약
| 단계 | 처리 내용 |
|---|---|
| 1 | 계좌 승인 건(ZBKONT = ‘Y’)만 대상으로 선별 |
| 2 | 지급 금액(RWBTR)이 100억 초과인지 확인 |
| 3 | 초과 시, 잔여 금액(LV_RWBTR)을 100억씩 잘라내며 DO ~ ENDDO 루프로 분할 전문 생성 |
| 4 | 분할 건마다 순번(ZNO)과 전문번호(DOCUNO)를 별도 채번 |
| 5 | 100억 이하 건은 기존 로직 그대로, ZNO = ’01’ 고정 |
우선 다음과 같이 흐름이 진행되도록 고려를 했구요

실제 개발한 코드는 다음과 같습니다. FORM 루틴 하나로 100억 초과 여부에 따라 분기하고, 초과 건은 루프를 돌면서 100억씩 떼어내는 구조예요.
FORM SAVE_SEND_RTN .
*>>> 100억분할 로직 도입
DATA: LT_030 LIKE ZSAPT0030 OCCURS 0 WITH HEADER LINE,
LV_RWBTR TYPE ZSAPT0030-RWBTR, " 분할 처리용 잔여 금액
LV_ZNO TYPE N LENGTH 2. " 분할 순번 (01~99)
LOOP AT GT_MAIN WHERE CHK = 'X'.
IF GT_MAIN-ZBKONT = 'Y'. " ← 계좌 승인 건만 처리
IF GT_MAIN-RWBTR > 100000000. " 100억 초과 여부 확인
LV_RWBTR = GT_MAIN-RWBTR.
LV_ZNO = 1.
DO.
IF LV_RWBTR <= 0. EXIT. ENDIF.
MOVE-CORRESPONDING GT_MAIN TO LT_030.
" 분할 금액 설정
IF LV_RWBTR > 100000000.
LT_030-RWBTR = 100000000.
ELSE.
LT_030-RWBTR = LV_RWBTR.
ENDIF.
" 분할 순번 설정
LT_030-ZNO = LV_ZNO.
" 전문번호 채번 (분할 건마다 별도 채번)
PERFORM DOCU_FILL_RTN.
LT_030-DOCUNO = GV_DOCUNO.
" 공통 필드 설정 (기존 로직과 동일)
LT_030-ZBNKN = GT_MAIN-ZBNKN_ENC.
LT_030-BKONT = GT_MAIN-ZBKONT.
LT_030-ZCONFIRM = 'N'.
LT_030-STATUS = 'M'.
LT_030-CDATE = SY-DATUM.
LT_030-CTIME = SY-UZEIT.
LT_030-CUSER = G_USER.
LT_030-LDATE = SY-DATUM.
LT_030-LTIME = SY-UZEIT.
LT_030-LUSER = G_USER.
APPEND LT_030.
LV_ZNO = LV_ZNO + 1.
LV_RWBTR = LV_RWBTR - 100000000. " 100억 차감
ENDDO.
ELSE. " 100억 미만 - 기존 로직 그대로
MOVE-CORRESPONDING GT_MAIN TO LT_030.
LT_030-ZNO = '01'. " ZNO = '01' 고정
*. 암호화
CALL FUNCTION 'CONVERSION_EXIT_ZKOIN_INPUT'
EXPORTING
INPUT = GT_MAIN-KOINH
IMPORTING
OUTPUT = LT_030-KOINH
EXCEPTIONS
NO_AUTHORITY = 1
OTHERS = 2.
LT_030-ZBNKN = GT_MAIN-ZBNKN_ENC.
LT_030-STCD1 = ''.
LT_030-BKONT = GT_MAIN-ZBKONT.
LT_030-ZCONFIRM = 'N'. "미승인
LT_030-STATUS = 'M'. "지급의뢰
LT_030-CDATE = SY-DATUM.
LT_030-CTIME = SY-UZEIT.
LT_030-CUSER = G_USER. "sy-uname.
LT_030-LDATE = SY-DATUM.
LT_030-LTIME = SY-UZEIT.
LT_030-LUSER = G_USER.
" 전문번호 채번 (당행/타행 구분)
PERFORM DOCU_FILL_RTN.
LT_030-DOCUNO = GV_DOCUNO.
APPEND LT_030.
ENDIF.
* *. 업데이트
GT_ITEM-BUKRS = LT_030-BUKRS.
GT_ITEM-BELNR = LT_030-VBLNR.
GT_ITEM-GJAHR = LT_030-LAUFD+0(4).
GT_ITEM-ZBSTAT = 'Y'. " 회계결재상태 'Y' 지급 예정
APPEND GT_ITEM.
DELETE GT_MAIN.
ENDIF.
ENDLOOP.
MODIFY ZSAPT0030 FROM TABLE LT_030.
IF SY-SUBRC = 0.
COMMIT WORK.
ELSE.
ROLLBACK WORK.
ENDIF.
*<<<<<<<<<<<<<<<< End of Modify <<<<
ENDFORM. " SAVE_SEND_RTN
여기서 눈여겨보실 부분은 분할된 각 건마다 PERFORM DOCU_FILL_RTN으로 전문번호를 따로 채번해준다는 점입니다. 하나의 지급 요청이 여러 건의 전문으로 쪼개지는 거라, 은행에 나가는 전문 입장에서는 완전히 독립된 건으로 인식돼야 하거든요. 이 채번 로직을 빼먹으면 분할 건들이 같은 전문번호로 나가면서 은행 쪽에서 중복이나 Reject로 처리될 수 있습니다.

결국 그림과 같이 기존 전문에 순번이 추가되고 별도 전문번호가 채번되어 자동 분할되도록 처리가 완료되었습니다.
이런 분할 로직은 결국 펌뱅킹 통신모듈 세팅이나 Firm Banking 인터페이스 설계 쪽 이야기와도 맞닿아 있는데, 그 부분은 나중에 다시 정리해서 다뤄보겠습니다.
정리하며
한도라는 게 별거 아닌 것 같아도, 막상 파고들면 은행 계약, 리스크 관리, 그리고 결국은 ERP 개발까지 다 이어지는 이야기더라고요. 특히 이번 건처럼 필드 하나의 저장 방식을 제대로 이해하지 못하면 코드 리뷰할 때 엉뚱한 곳에서 헷갈릴 수 있으니, 비슷한 이슈 만나시는 분들께 조금이라도 도움이 됐으면 합니다.