한국어
Digital Payment 튜토리얼
Ethereum Sepolia의 USDT를 이용해 오프라인 결제 PoC를 구현합니다. 고객은 USER 계정에서 가맹점의 BUSINESS 계정으로 결제하고, 가맹점은 모인 자금을 외부 주소로 정산합니다.
Digital Payment는 Wallet API 기능을 조합한 연동 시나리오입니다. 별도의 /payments API가 존재한다는 의미가 아닙니다.
이 예제는 테스트넷에서 실행합니다. 운영용 메인넷과 테스트 자산을 구분하고, 이용 가능 네트워크는 지원 네트워크에서 확인합니다.
고객 자금 충전→내부결제→가맹점 잔액→외부 정산→거래 대사
구현할 흐름
text
USDT 자산과 Sepolia 네트워크 조회
→ 고객 계정과 가맹점 결제 계정 생성
→ 고객 입금 주소 발급 및 자금 충전
→ 내부이체로 USDT 결제
→ 결제 결과 복구 및 확정
→ 가맹점 자금을 외부 주소로 정산
→ 양쪽 계정의 원장 기록 대사준비 사항
Node.js, curl, openssl, jq와 BPMG에서 전달받은 Base URL, API Key, HMAC Key를 준비합니다.
bash
export WALLET_BASE_URL='https://{environment-api-host}'
export WALLET_API_KEY='{issued-api-key}'
export WALLET_HMAC_KEY='{issued-hmac-key}'자격 증명을 Git이나 로그에 남기지 마십시오. 서명 요청의 시각이 서버 허용 범위 안에 있어야 하므로 호출 서버는 NTP로 시각을 동기화합니다. 예제를 실행하기 전에 HMAC 요청 서명을 확인하십시오.
wallet_request
wallet_request는 이 튜토리얼에서 Wallet API를 호출하기 위해 정의하는 셸 함수입니다. 아래 함수는 앞에서 설정한 환경 변수를 사용해 HMAC-SHA256 서명과 인증 헤더를 만들고, curl로 HTTP 요청을 전송합니다. 이후 단계의 코드 예제는 이 함수가 등록되어 있다고 가정합니다.
호출 형식은 다음과 같습니다.
text
wallet_request <HTTP 메서드> <API 경로> [JSON 요청 본문]| 인자 | 설명 | 예시 |
|---|---|---|
| HTTP 메서드 | 요청에 사용할 메서드 | GET, POST |
| API 경로 | Base URL을 제외한 경로. 조회 조건이 있으면 query string까지 포함합니다. | /asset-types?code=USDT&page=1&page_size=20 |
| JSON 요청 본문 | 본문이 필요한 요청에서만 전달하는 JSON 문자열 | "$user_account_body" |
함수 등록
앞에서 환경 변수를 설정한 셸에 아래 코드 블록 전체를 붙여 넣어 실행합니다. 호출할 때마다 새로운 timestamp, nonce, signature를 생성합니다. 새 터미널이나 셸을 열면 환경 변수 설정과 함수 등록을 다시 진행해야 합니다.
bash
wallet_request() {
local method="$1"
local request_path="$2"
local body="${3-}"
local timestamp nonce signature
timestamp="$(node -e 'process.stdout.write(String(Date.now()))')"
nonce="$(node -e 'process.stdout.write(require("crypto").randomUUID())')"
signature="$(printf '%s\n\n%s\n\n%s\n\n%s\n\n%s' \
"$method" "$request_path" "$timestamp" "$nonce" "$body" \
| openssl dgst -sha256 -hmac "$WALLET_HMAC_KEY" -binary \
| openssl base64 -A)"
if [ -n "$body" ]; then
curl -sS -X "$method" "$WALLET_BASE_URL$request_path" \
-H "X-API-KEY: $WALLET_API_KEY" \
-H "X-TIMESTAMP: $timestamp" \
-H "X-NONCE: $nonce" \
-H "X-SIGNATURE: $signature" \
-H 'Content-Type: application/json' \
--data-raw "$body"
else
curl -sS -X "$method" "$WALLET_BASE_URL$request_path" \
-H "X-API-KEY: $WALLET_API_KEY" \
-H "X-TIMESTAMP: $timestamp" \
-H "X-NONCE: $nonce" \
-H "X-SIGNATURE: $signature"
fi
}호출 예시
함수를 등록한 뒤 같은 셸에서 다음 조회 요청을 실행할 수 있습니다. | jq는 API의 JSON 응답을 읽기 쉽게 표시합니다.
bash
wallet_request GET '/asset-types?code=USDT&page=1&page_size=20' | jq이 호출은 $WALLET_BASE_URL/asset-types?code=USDT&page=1&page_size=20으로 서명된 GET 요청을 보냅니다. POST 요청에서는 wallet_request POST '/accounts' "$user_account_body"처럼 세 번째 인자로 JSON 본문을 전달합니다. user_account_body를 준비하는 예제는 다음 단계인 결제 계정 구조 설계에 나옵니다. 경로의 query string과 본문은 전달한 그대로 서명되므로, 경로와 본문 인자를 따옴표로 감싸 전달하십시오.
이 함수는 튜토리얼용입니다. 실제 클라이언트에는 응답 검증, timeout, 안전한 제한 재시도를 추가하십시오.
예제는 Sepolia USDT를 사용하지만 자산·네트워크·계정 ID는 항상 대상 환경의 API 응답에서 가져와야 합니다.
예제 실행과 응답 확인
각 페이지는 앞 단계에서 저장한 변수를 사용합니다.
| 변수 | 준비하는 단계 |
|---|---|
USER_ACCOUNT_ID, PAYMENT_ACCOUNT_ID | 결제 계정 구조 설계 |
ASSET_TYPE_ID, NETWORK_ID, ASSET_DECIMALS | 결제용 자산 충전 |
PAYMENT_AMOUNT, PAYMENT_REQUEST_ID, PAYMENT_WITHDRAWAL_ID | 내부 계정 간 자산 이동 |
TO_ADDRESS, WITHDRAW_AMOUNT, ASSET_ID, SETTLEMENT_REQUEST_ID, WITHDRAWAL_ID | 외부 주소로 정산·지급 |
| 원장 ID와 리소스별 cursor | 결제 거래 대사 |
curl -sS는 HTTP 오류에도 JSON을 출력할 수 있고, | jq는 응답 표시용입니다. 각 요청의 HTTP 상태와 success를 확인한 뒤 다음 단계를 실행합니다. 오류나 빈 목록에서 추출한 null을 ID로 사용하지 마십시오. 조회 결과가 예상 자산·네트워크·계정인지도 확인합니다.
셸 변수는 튜토리얼을 이어서 실행하기 위한 임시 저장소입니다. 운영에서는 계정 연결과 주문·정산 요청 ID를 DB에 저장합니다. 생성 요청 전에 업무 ID와 client_request_id를 연결해 저장하고, 응답을 받은 뒤 거래 ID를 추가합니다. 새 셸에서 결제 요청 ID를 다시 생성해 같은 주문을 제출하지 마십시오.
진행 순서
- 결제 계정 구조 설계
- 결제용 자산 충전
- 내부 계정 간 자산 이동
- 결제 결과와 거래 기록 처리
- 외부 주소로 정산·지급
- 자금 집금과 회수
- 결제 거래 대사
- Digital Payment 전체 흐름 구현
IMPORTANT
문서의 ID, 주소, 금액은 예시입니다. 실제 연동에서는 대상 환경의 API가 반환한 값과 선택한 네트워크의 올바른 주소를 사용하십시오.