Square Terminal API 연동기

최근 해외 결제를 처리해야 하는 업무가 생기면서 Square Terminal을 직접 다뤄볼 기회가 생겼습니다.
사실 Square라는 서비스 자체를 이전부터 알고 있긴 했지만, 직접 결제 단말기를 만져볼 기회는 흔하지 않았습니다.
마침 회사 창고에 이전 개발자가 테스트용으로 사용했던 Square Terminal이 있다는 이야기를 듣고 찾아봤는데, 정말 있더군요.
그래서 이번에는 직접 단말기를 꺼내서 Square API를 이용한 결제 연동부터 실제 결제까지 한번 테스트해봤습니다.
Square Terminal 이란?
Square Terminal은 Square에서 제공하는 결제 단말기입니다.
Square에서는 여러 종류의 결제 단말기를 제공하고 있는데, 이번에 제가 사용하게 된 제품은 그중 Square Terminal이었습니다.
국내에서 흔히 사용하는 KICC 같은 결제 시스템과 비슷한 부분도 있지만, 개발자 입장에서 API와 SDK 문서를 찾아보면서 느낀 점은 상당히 달랐습니다.
특히 개발자 문서가 꽤 잘 되어 있습니다.
제가 국내 결제 연동을 진행할 때 접했던 문서들과 비교하면, API의 구조나 예제 코드 등을 확인하기가 상당히 편했습니다.
다만 Square Terminal은 한국에서는 사용할 수 없는 제품입니다. 이 부분이 제일 아쉽긴 합니다.
현재 Square가 지원하는 국가에는 다음과 같은 곳들이 있습니다.
- 일본
- 미국
- 호주
- 캐나다
- 영국
- 아일랜드
- 프랑스
- 스페인
국내에서 일반적인 결제 시스템을 개발한다면 접할 기회가 많지는 않을 것 같습니다.
제 경우처럼 해외 법인을 통해 결제를 처리해야 하는 상황이라면 이야기가 달라지겠죠.
어쨋든 저에게는 꽤 재미있는 경험입니다.
Square Developer Console
이번에 제가 전달받은 것은 크게 두 가지였습니다.
- Square Terminal 실물 기기
- 해외 법인에서 생성해 준 Team User 계정
문제는 저에게 Square의 개발자 계정이나 설정 방법을 자세히 알려줄 사람이 없었다는 점입니다.
해외 업체 담당자분도 개발자가 아니었기 때문에 "Square Developer Console에서 알아서 설정하면 됩니다." 정도의 안내만 받은 상태였습니다.
https://developer.squareup.com/apps
Sign up for Square
app.squareup.com
그래서 일단 직접 찾아봐야할 상황이였습니다.
로그인 후 [+] 버튼을 눌러 테스트용 Application을 하나 생성했습니다.

Application을 만들자마자 App ID가 발급됩니다.
생성된 Application을 확인해보니 Sandbox와 Production 환경이 별도로 제공하고 있었습니다.

Sandbox는 말 그대로 테스트 환경이고, Production은 실제 결제에 사용하는 환경입니다.
처음에는 Sandbox와 Production이 거의 동일한 방식으로 제공될 것이라고 생각했습니다.
그런데 여기서 첫 번째 문제가 발생했습니다.
Production Access Token을 받을 수 없다?
Sandbox에서는 정상적으로 Access Token 관련 기능을 확인할 수 있었는데, Production에서는 권한이 없다는 메시지가 나타났습니다.
위 그림과 같이 권한이 없다고 뜨네요.

Sandbox에서는 테스트용 환경이기 때문에 접근이 가능했지만, Production의 실제 결제 기능을 사용하려면 별도의 권한이 필요했던 것입니다.
처음에는 단순히 개발자 계정 설정 문제라고 생각했습니다.
그런데 조금 더 찾아보니 Square의 계정 권한 구조가 관련되어 있었습니다.
Square에서는 계정의 소유자를 `Owner`라고 부르고, 그 계정에 속한 사용자를 `Team User`라고 구분합니다.
대략적인 개념은 다음과 같습니다.
Owner : 모든 권한을 가진 계정
Team User : Owner에 속해있는 User
제가 전달받은 계정은 Owner가 아니라 Team User였습니다.
이 차이가 생각보다 컸습니다.
Owner와 Team User의 차이
해외 법인의 Square 계정을 관리하는 Owner가 따로 있고, 저는 그 계정에 추가된 Team User였습니다.
문제는 제가 직접 Production Access Token을 발급하려고 하니 관련 메뉴가 활성화되지 않았다는 것입니다.

해외 법인 담당자에게 확인을 요청했고, 필요한 권한을 활성화해 달라고 부탁했습니다.
제가 요청했던 것은 대략 다음과 같은 권한이었습니다.
- Access all Developer tools
- Manage and view personal access tokens

그런데 담당자에게서 돌아온 답변은 해당 옵션 자체를 활성화할 수 없다는 것이었습니다.
결과적으로 Owner가 아닌 Team User 계정에서는 제가 원하는 방식으로 Production Access Token을 직접 발급받을 수 없는 상황이었습니다.
이쯤 되니 처음 생각했던 방법을 그대로 사용할 수 없게 됐습니다.
그냥 API 정도 되는건 맞는지 Response 정도 확인하는 용도로 확인되었습니다.
Square API 연동을 위한 두가지 방법
Square의 개발자 문서와 SDK를 살펴보면서 단말기와 서버를 연결하는 방법을 확인해봤습니다.
제가 확인한 방식은 크게 두 가지였습니다.
1. Access Token을 직접 사용
이 방법은 Square Developer에서 발급받은 Access Token을 서버에 저장하고 Square API를 호출하는 방식입니다.
서버에서 Square API를 호출하면 Square 서버가 페어링된 Terminal에 결제 요청을 전달하고, 단말기에서 사용자에게 결제를 유도합니다.
구조가 단순합니다.
내 서버 -> Square API -> Square Terminal -> 카드 결제개발하는 입장에서는 상당히 편합니다.
Access Token 자체가 중요한 인증 정보이기 때문에 토큰이 외부로 유출되지 않도록 관리해야 합니다. 사실 Server to Server 로만 쓸거기에 유출된다는게 이미 저희 운영서버도 해킹당한 상태일겁니다.
개인적으로는 실제 서비스에 적용하기 전에 우선 이 방식으로 단말기가 제대로 동작하는지부터 확인해보고 싶었습니다.
하지만 앞에서 설명한 것처럼 Team User 권한 문제 때문에 Production Access Token을 직접 발급받을 수 없었습니다.
그래서 두 번째 방법으로 넘어갔습니다.
2. OAuth를 이용하는 방법
두 번째 방법은 **OAuth**를 이용하는 방식입니다.
사용자가 Square 인증을 진행하면 OAuth 과정을 통해 Access Token을 발급받고, 서버에서는 이 Token을 저장해 Square API를 호출합니다.
그리고 Access Token이 만료되면 Refresh Token을 이용해 새로운 Access Token을 발급받는 방식입니다.
대략적인 흐름은 다음과 같습니다.
Square OAuth 인증 URL 생성 -> Owner가 인증 -> Redirect URL 호출 ->
Authorization Code 전달 -> Square API에 Code 전달 -> Access Token + Refresh Token 발급 ->
서버에 Token 저장 -> Square API 호출아 .. 너무 일반적인 구조지만.... 그냥... 싫다....
처음에는 로컬에서 OAuth를 테스트하려고 했습니다.
그런데 OAuth를 처리하려면 외부에서 접근 가능한 Redirect URL이 필요했고, HTTPS까지 적용하는 것이 편했습니다.
Ngrok을 사용해서 로컬 서버를 외부에 공개하는 방법도 생각해봤습니다.
하지만 어차피 실제 서비스에 적용할 코드이기도 하고, OAuth 승인 과정까지 확인하려면 실 서버에서 진행하는 편이 낫겠다고 판단했습니다.
마침 행사 때 잠시 사용하는 EC2 서버가 하나 있었기 때문에 그곳에 아주 간단한 Node.js 서버를 하나 올렸습니다.
AWS EC2 + Node.js + Nginx로 OAuth 서버 만들기
구성 자체는 굉장히 단순하게 만들었습니다.
사용자 -> Square OAuth -> Redirect URL -> Nginx -> Node.js -> Access Token 저장Square에서 제공하는 예제 코드를 참고해서 Callback을 처리했습니다.

Node.js 서버는 특정 포트에서 실행하고, Nginx에서는 외부 도메인 요청을 해당 Node.js 서버로 Reverse Proxy하도록 설정했습니다.
OAuth 승인이 완료되면 Callback으로 전달된 값을 이용해 Access Token을 발급받고, 테스트를 위해 .env 파일에 저장하도록 구성했습니다.
Router53 서브도메인 하나 만들고 DNS 레코드 설정까지 완료하고 잠시 기다린 뒤 서버를 실행했습니다.

이제 OAuth에 사용할 URL이 만들어졌습니다.
Owner의 승인이 필요
생성된 OAuth URL을 해외 법인의 Owner에게 전달하고 승인을 요청했습니다.
그런데 여기서도 Team User 계정으로 직접 처리할 수 없는 부분이 있었습니다.

결국 Owner분이 해당 URL에 접속해서 승인을 해줘야 했습니다.
조금 귀찮기는 했지만 이 부분은 OAuth의 정상적인 인증 흐름이므로 그대로 진행했습니다.
잠시 후 Owner분으로부터 승인이 완료되었다는 연락을 받았습니다.
바로 서버 로그를 확인했습니다.

정상적으로 Token이 저장됐다는 메시지가 확인됐습니다.
오...
운 좋게도 별다른 오류 없이 한 번에 성공했습니다.
.env를 확인해봅니다.
cat .env
Access Token이 정상적으로 들어와 있는 것을 확인할 수 있었습니다.
일단 첫 번째 고비는 넘었습니다.
Square Terminal 단말기 연결
Access Token을 확보했으니 이제 Square Terminal을 서버에서 사용할 수 있도록 연결해야 합니다.
그런데 다행히도 여기서는 제가 할 일이 하나 줄었습니다.
확인해보니 이전 개발자가 이미 해당 Square Terminal을 Square 계정에 등록하고 Device Pairing까지 완료해 놓은 상태였습니다.
따라서 Device 등록 및 Pairing 과정은 이번 테스트에서는 생략할 수 있었습니다.
Square Dashboard에서 현재 등록되어 있는 Device를 확인할 수 있습니다.
https://app.squareup.com/dashboard
Square: Sign in to Your Dashboard & Manage your Business
app.squareup.com

이미 Terminal이 등록되어 있는 것을 확인할 수 있습니다.
원래라면 여기서 Location ID를 확인하고 해당 Location에 Device를 등록한 다음 Pairing 과정을 진행해야 합니다.
하지만 이미 이전에 등록되어 있었기 때문에 그대로 사용하기로 했습니다.
Device Code 확인
Dashboard에서 Device codes 메뉴로 들어가면 페어링에 사용할 Device Code를 확인할 수 있습니다.

Device Code를 복사합니다.
자 이제 Square Terminal 기기를 켜봅니다.
안드로이드로 되어있네요.
Square Terminal은 Android 기반으로 동작합니다.
개인적으로는 임베디드 시스템 특유의 폐쇄적인 느낌보다는 Android 기반이라는 점 때문에 조금 더 친숙하게 느껴졌습니다.
Square Terminal 세팅
단말기가 켜지면 아래와 같은 화면이 출력됩니다.

Sign in을 눌러줍니다.

여기서 Use a Device code를 선택합니다.
Square 계정으로 직접 로그인하면 더 많은 기능을 사용할 수 있는 것 같았지만, 제가 필요한 것은 단순했습니다.
서버에서 결제 요청을 보내고 Terminal이 해당 결제를 받아서 사용자에게 결제를 유도하면 됩니다.
Device Code 방식으로 진행하니 불필요한 다른 메뉴가 뜨지 않아 이 코드 방식으로 로그인하시는걸 추천드립니다.

Dashboard에서 확인한 Device Code를 단말기에 입력합니다.
그러면 Terminal이 Square 서버와 통신하면서 초기 설정을 진행합니다.

그리고 설정해둔 이미지가 단말기에 표시됩니다.
기기 로그인도 완료되었고 이제 결제를 테스트해봐야겠습니다.
단말기 로그인 및 연결까지 완료됐습니다.
이제 진짜 중요한 것을 테스트해볼 차례입니다.
결제가 실제로 될까?
실제 결제 테스트
Square API Explorer에서 제공하는 Checkout 예제를 참고해서 간단한 테스트 코드를 작성했습니다.
테스트 금액은 일본 엔화(JPY) 1엔으로 설정했습니다.
실제 운영에 사용할 금액을 넣을 필요는 없으니 가장 작은 금액으로 테스트했습니다.
API를 실행하니 콘솔에 다음과 같이 출력됩니다.
[production] Terminal 결제 요청 생성 중...
체크아웃 생성됨: bfBM*********** (기기에서 결제를 진행해주세요)
상태: PENDING
상태: IN_PROGRESS
상태: IN_PROGRESS
상태: IN_PROGRESS결제 상태를 계속 조회하는 Polling 방식으로 구현했습니다. 예제도 동일합니다.

코드를 실행하고 1~2초 정도 지나자 실제 Square Terminal 화면이 결제 화면으로 전환됐습니다.

서명을 요구하네요.

결제 완료.
결제가 성공적으로 완료되었습니다.
영수증 출력
결제가 끝난 후에는 여러 방법으로 영수증을 전달할 수 있습니다.
그런데 단말기에 Print가 있어서 눌러봤습니다.
단말기 상단에서 실제 영수증이 출력되기 시작했습니다.
생각보다 제대로 되어 있습니다.
영수증이 나오는 부분을 열어보니 용지를 교체할 수 있는 구조로 되어 있었습니다.

이런 방식의 영수증 용지는 범용으로 구매해서 사용할 수 있는지도 한번 찾아봐야겠습니다.
마무리
이렇게 해서 일단 Square Terminal 기본 설정부터 실제 결제 테스트까지 완료했습니다.
처음에는 단순히 해외 결제 단말기 하나 테스트해보자는 생각으로 시작했는데, 막상 해보니 개발자 입장에서는 꽤 재미있는 과정이었습니다.
특히 처음부터 순탄하지는 않았습니다.
개인적으로는 AccessToken을 얻기 위한 소통에서 시간을 많이 썼던 기억이 납니다.
차라리 Owner 계정을 접속해 확인해봤으면 좋았을 것 같은데 그럴 수 없으니 답답했던게 사실입니다.
OAuth도 결국 Owner가 해줘야하는 부분으로 되어있다보니 Owner가 아니면 진행하기가 매우 번거로운 부분이 많았던 것 같습니다.
Team User로 작업하는 경우 Owner 권한이 필요한 부분이 있기 때문에, 해외 법인의 Square 계정을 개발자가 직접 다루는 상황이라면 처음부터 Owner와 Team User의 권한 차이를 확인하는 것이 좋을 것 같습니다.
앞으로 남은 작업
실제 서비스에 적용하려면 아직 할 일이 남아 있습니다.
현재는 테스트를 위해 발급받은 Access Token을 로컬에서 우선 사용해보았지만, 실제 운영에서는 Token을 좀 더 제대로 관리해야 합니다.
특히 OAuth를 사용한다면 Refresh Token을 이용해 Access Token을 자동으로 갱신하는 로직이 필요하겠죠.
제가 확인한 흐름에서는 Access Token을 계속 새로 발급받는 것이 아니라 Refresh Token을 이용해 갱신하도록 구성해야 합니다.
따라서 앞으로는 다음과 같은 작업이 남아 있습니다.
- Refresh Token 기반 Access Token 갱신
- Token의 안전한 저장
- Token 만료 및 갱신 실패 처리
- 여러 클라이언트가 사용할 수 있는 서버 API 구성
- Terminal 결제 상태 처리
- 결제 실패 및 취소 처리
- 실제 서비스 환경에서의 예외 처리
일단 가장 중요한 서버에서 결제 요청 -> Square Terminal에서 결제 -> 실제 결제 완료까지 확인했으니, 나머지는 차근차근 정리하면 될 것 같습니다.
참고로 알아두면 좋은 것
Square API 환경별 Host
Square API를 사용할 때 Sandbox와 Production의 API Host가 다릅니다.
Sandbox
https://connect.squareupsandbox.com
Production
https://connect.squareup.com테스트 코드에서 Sandbox와 Production을 전환할 때 이 부분을 잘못 설정하면 엉뚱한 환경을 호출할 수 있으니 주의해야 합니다.
Idempotency Key는 뭘 넣어야 할까?
Square API 예제를 보다 보면 idempotencyKey라는 값이 자주 등장합니다.
이 값은 동일한 요청이 중복으로 처리되는 것을 방지하기 위한 키입니다.
따라서 매 요청마다 중복되지 않는 값을 생성해서 넣어주는 것이 좋습니다.
저는 테스트에서는 UUID를 생성해서 사용했습니다.
import { randomUUID } from "crypto";
const idempotencyKey = randomUUID();이렇게 생성한 UUID를 API 요청의 idempotencyKey로 사용하면 됩니다.
이번에 Square Terminal을 직접 사용해보면서 느낀 건,
"결제 API가 실제 물리적인 단말기까지 이렇게 깔끔하게 연결되는구나."
라는 점이었습니다.
국내 결제 모듈만 주로 다뤄봤다면 한번쯤 이런 해외 결제 시스템을 직접 붙여보는 것도 꽤 재미있는 경험이 될 것 같습니다.
'기타' 카테고리의 다른 글
| 천안 K컬처 박람회 - Be the K (나이비스) (1) | 2026.09.06 |
|---|---|
| 한그루 사진 일괄 다운로드 (0) | 2025.03.14 |
| Ventoy: USB 하나로 모든 OS 설치, 멀티부팅의 신세계 (1) | 2025.02.24 |
| 전원이 들어오는 순간 컴퓨터 전원을 키게 하는 설정 (0) | 2025.02.11 |