본문으로 건너뛰기
페이싱크 개발자센터
Esc
↑↓이동↵열기⌘J미리보기
이 페이지에서

Java·Kotlin SDK

페이싱크 Java SDK와 Kotlin 확장으로 주문, 입금, 현금영수증 API를 호출하고 웹훅 서명을 검증하는 방법이에요.

Java SDK는 v2 API의 주문, 입금, 현금영수증 API와 웹훅 서명 검증을 지원해요. Kotlin에서는 코루틴과 Flow를 쓰는 확장 모듈을 함께 쓸 수 있어요.

모듈 설명
kr.paysync:paysync-sdk Java SDK예요. Java 17 이상에서 써요.
kr.paysync:paysync-sdk-kotlin Kotlin 확장이에요. Kotlin 2.2 이상에서 써요. Java SDK도 함께 들어와요.

설치

repositories {
    mavenCentral()
    maven("https://forgejo.verchase.com/api/packages/paysync/maven")
}

dependencies {
    implementation("kr.paysync:paysync-sdk:1.0.0")
    // Kotlin이라면 대신 이것을 넣어요.
    implementation("kr.paysync:paysync-sdk-kotlin:1.0.0")
}
<repositories>
  <repository>
    <id>paysync</id>
    <url>https://forgejo.verchase.com/api/packages/paysync/maven</url>
  </repository>
</repositories>

<dependency>
  <groupId>kr.paysync</groupId>
  <artifactId>paysync-sdk</artifactId>
  <version>1.0.0</version>
</dependency>

클라이언트 만들기

API 키는 API 키를 따라 준비해요. 클라이언트는 하나만 만들어 같이 쓰세요.

PaySyncClient client = PaySyncClient.create(System.getenv("PAYSYNC_API_KEY"));
val client = SuspendingPaySyncClient(System.getenv("PAYSYNC_API_KEY")) {
    timeout(10.seconds)
}

빌더로 설정을 바꿀 수 있어요.

설정 기본값 설명
apiKey API 키예요. 필수예요.
baseUrl https://api.paysync.kr API 서버 주소예요.
timeout 30초 응답을 기다리는 시간이에요.
connectTimeout 10초 연결을 기다리는 시간이에요.
maxRetries 2 조회 요청이 실패했을 때 다시 시도할 횟수예요.
appInfo User-Agent 헤더에 붙일 애플리케이션 이름이에요.

API

서비스 메서드 API
invoices() create, get, list, listAll, update, markPaid, cancel 주문
transactions() get, list, listAll 입금
cashReceipts() issue, get, list, listAll, cancel, reissue, urls, sendEmail 현금영수증

Java에서는 메서드마다 CompletableFuture를 돌려주는 ...Async 버전이 있어요. Kotlin의 SuspendingPaySyncClient에서는 모두 suspend 함수예요.

주문 만들기

Invoice invoice = client.invoices().create(InvoiceCreateParams.builder()
        .customerName("홍길동")
        .customerEmail("hong@example.com")
        .amount(50_000)
        .expireAfterDays(3)
        .putMetadata("orderId", "ORDER-2026-0001")
        .build());

System.out.println(invoice.guideUrl());
val invoice = client.invoices.create {
    customerName("홍길동")
    customerEmail("hong@example.com")
    amount(50_000)
    expireAfterDays(3)
    putMetadata("orderId", "ORDER-2026-0001")
}

주문 수정

넣은 값만 바뀌어요. 값을 지우려면 clearCustomerEmail(), clearCustomerPhoneNumber(), clearExpiresAt(), clearCashReceipt()를 써요.

client.invoices().update(invoice.id(), InvoiceUpdateParams.builder()
        .amount(55_000)
        .clearCustomerPhoneNumber()
        .resendGuideEmail(true)
        .build());
client.invoices.update(invoice.id()) {
    amount(55_000)
    clearCustomerPhoneNumber()
    resendGuideEmail(true)
}

목록 조회

list는 한 페이지를 돌려주고, listAll은 조건에 맞는 항목을 마지막 페이지까지 모두 조회해요. Java에서는 Iterable, Kotlin 코루틴 클라이언트에서는 Flow예요.

Page<Invoice> page = client.invoices().list(InvoiceListParams.builder()
        .status(InvoiceStatus.PENDING)
        .dateFrom(LocalDate.of(2026, 4, 1))
        .limit(50)
        .build());

for (Transaction transaction : client.transactions().listAll(TransactionListParams.builder()
        .status(TransactionStatus.UNMATCHED)
        .build())) {
    System.out.println(transaction.depositorName());
}
val page = client.invoices.list {
    status(InvoiceStatus.PENDING)
    limit(50)
}

client.transactions.listAll { status(TransactionStatus.UNMATCHED) }
    .collect { println(it.depositorName) }

현금영수증

발급이 거절되면 예외 대신 status가 FAILED인 현금영수증이 와요. 이유는 failure()에서 확인해요.

CashReceipt receipt = client.cashReceipts().issue(CashReceiptIssueParams.builder()
        .type(CashReceiptType.PERSONAL)
        .identifier("01012345678")
        .amount(50_000)
        .invoiceId(invoice.id())
        .build());

if (receipt.status() == CashReceiptStatus.FAILED) {
    System.out.println(receipt.failure().message());
}

응답 값

  • 시각은 모두 Instant예요.
  • 상태, 은행, 매칭 방식 같은 값은 enum이에요. SDK가 모르는 새 값은 UNKNOWN으로 와요.
  • 모델은 Java record예요. Kotlin에서는 invoice.id()나 invoice.customer().name처럼 써요.

오류 처리

페이싱크가 오류를 응답하면 PaySyncApiException을 던져요. 에러 코드는 code()로 구분하고, 자주 쓰는 값은 ErrorCode에 있어요.

예외 HTTP
AuthenticationException 401
PermissionDeniedException 403
NotFoundException 404
ConflictException 409
InvalidRequestException 400, 422 등
RateLimitException 429
ServerException 5xx
PaySyncConnectionException 연결 실패, 응답 시간 초과

일부 오류는 data에 처리에 필요한 값을 담아요. SDK에서는 메서드로 꺼내요.

메서드 code
duplicateInvoice() 220010
cancelBlocked() 240006
failedCashReceipt() 240008
providerError() 240009
existingCashReceiptId() 240012
try {
    client.invoices().create(params);
} catch (ConflictException e) {
    if (ErrorCode.INVOICE_ALREADY_EXISTS.equals(e.code())) {
        String existingId = e.duplicateInvoice().invoiceId();
    }
}

웹훅

웹훅 서명 키(whsec_...)로 Webhook을 만들고 verify에 받은 본문과 헤더를 넣어요. 본문은 파싱하기 전의 문자열 그대로 넣어야 해요. 서명이 맞지 않으면 WebhookVerificationException을 던져요.

private final Webhook webhook = new Webhook(System.getenv("PAYSYNC_WEBHOOK_SECRET"));

@PostMapping("/paysync/webhook")
public ResponseEntity<Void> handle(@RequestBody String rawBody, @RequestHeader HttpHeaders headers) {
    WebhookEvent event;
    try {
        event = webhook.verify(rawBody, headers.asMultiValueMap());
    } catch (WebhookVerificationException e) {
        return ResponseEntity.badRequest().build();
    }

    switch (event) {
        case WebhookEvent.InvoiceEvent e when e.type() == WebhookEventType.INVOICE_PAID -> fulfill(e.invoice());
        case WebhookEvent.InvoiceEvent e -> { }
        case WebhookEvent.TransactionEvent e -> { }
        case WebhookEvent.UnknownEvent e -> { }
    }
    return ResponseEntity.ok().build();
}
val webhook = Webhook(System.getenv("PAYSYNC_WEBHOOK_SECRET"))

when (val event = webhook.verify(rawBody, headers)) {
    is WebhookEvent.InvoiceEvent -> if (event.type() == WebhookEventType.INVOICE_PAID) fulfill(event.invoice())
    is WebhookEvent.TransactionEvent -> log(event.transaction())
    is WebhookEvent.UnknownEvent -> Unit
}

Java 17에서는 switch (event.type())로 나눠요. 같은 이벤트가 여러 번 올 수 있으니 event.id()로 중복을 거르세요. 응답 규칙과 재시도는 웹훅 개요를 참고하세요.

이 페이지가 도움이 되었나요?