3- 3. 콜백
주문이 처리되면 시스템은 판매자가 구성한 콜백 주소로 알림 메시지를 보냅니다.
콜백 주소 구성
주문 시 notifyUrl 매개변수를 통해 해당 주문에 대한 전용 콜백 주소를 지정할 수 있습니다. 이 주소는 판매자의 백엔드에 구성된 기본 콜백 주소보다 우선 적용됩니다.
param.put("notifyUrl", "http://{domain}/callback/notifyUrl");
주문 시 notifyUrl를 입력하지 않으면 시스템은 판매자의 백엔드에 구성된 기본 콜백 주소를 콜백합니다.
기본 콜백 주소는 가맹점 생성 시 제공되며, 운영관리 백그라운드에서 유지될 수 있습니다.
콜백 요청 방법
HTTP 방법
POST
콘텐츠 유형
application/json
콜백 데이터 예시
{
"amount": "100",
"bizType": "WITHDRAW_ANY_DIGITAL_WALLET",
"blockchain": {
"network": "TRON",
"receiverAddress": "THcJ2FeNBkuzd4PZBpRans6tx9QGg9KHRt",
"senderAddress": "TE35TrUfHjbGEBsVS6zVXdHS8HxXCWwC2y",
"txIndex": "2",
"txId": "d848e4b62ec6925b8b3ff49dbc4d839f3f88508497c3af525f0c8293cb303ab3"
},
"currency": "CNY",
"localOrderId": "TestOTM0625ROB016",
"merchantActualAmount": "138.12",
"merchantCurrency": "CNY",
"merchantId": 302992856974,
"merchantPaidAmount": "100",
"notifyTime": 1772173006425,
"orderCreateTime": 1772172958082,
"orderId": "472515854147845",
"status": "SUCCESS",
"type": "WITHDRAW",
"userAmount": "14.971722",
"userCurrency": "USDT",
"userMinerFee": "0",
"isReissue": false,
"userReceivableAmount": "14.971722",
"rate": "6.71080951",
"rateExpression": "1USDT≈6.7108CNY",
"sign": "2c27c7e8184cc709a64ad502ee42eab7",
"key": "9yUreYgTRtit39Dy"
}
**비즈니스 개발 요구 사항을 충족하기 위해 콜백 매개변수는 향후 새로운 필드를 추가할 수 있습니다. 새로 추가된 필드는 기본적으로 서명 계산에 참여합니다(서명 규칙에 특별히 명시된 필드 제외). 따라서 가맹점 시스템은 필드 확장으로 인한 서명 검증 실패를 방지하기 위해 향후 호환성을 갖추어야 합니다. **
콜백 매개변수 설명
| 매개변수 이름 | 유형 | 참여 서명 | 매개변수 의미 | 매개변수 설명 |
|---|---|---|---|---|
amount | decimal | 예 | 주문금액 | |
bizType | enum | 예 | 주문 유형 | 주문 유형 설명은 다음과 같습니다 |
blockchain | object | 예 | 체인거래 정보 | 온체인 거래 정보, isBlockchain=true |
| └네트워크 | String | 예 | 메인넷 | |
| └수신자주소 | String | 예 | 수신자 주소 | |
| └발신자주소 | decimal | 예 | 보내는 주소 | |
| └txID | String | 예 | 거래 ID | 블록체인 거래 해시 |
| └tx인덱스 | String | 예 | 거래지수 | 거래지수(일괄이체 시나리오) |
currency | String | 예 | 통화 | 주문 통화 |
key | String | 예 | 판매자 키 | |
localOrderId | String | 예 | 판매자 주문 번호 | |
merchantActualAmount | decimal | 예 | 가맹점 실제 결제 금액 | |
merchantCurrency | String | 예 | 가맹점 결제통화 | |
merchantId | String | 예 | 판매자 ID | |
merchantPaidAmount | decimal | 예 | 가맹점이 받거나 지불할 금액 | |
merchantUserId | String | 예 | 판매자 사용자 ID | |
notifyTime | long | 예 | 콜백 시간 | 콜백 알림 시간 |
orderCreateTime | long | 예 | 주문 생성 시간 | |
orderId | String | 예 | 주문번호 | 플랫폼 주문 번호(고유) |
status | String | 예 | 결제현황 | 성공, 실패(아래 설명) |
type | String | 예 | 주문 유형 | 지불, 인출(아래 설명) |
userAmount | decimal | 예 | 이용자가 실제로 받거나 지불한 금액 | |
userCurrency | String | 예 | 사용자 통화 | |
userMinerFee | String | 예 | 채굴 수수료 | |
userReceivableAmount | String | 예 | 이용자가 지급 또는 받을 금액 | |
isReissue | Boolean | 예 | 재발행 여부 | 콜백 재발행 여부 |
rate | string | 예 | 환율 | |
rateExpression | string | 예 | 환율 표현 | |
sign | String | 아니요 | 서명값 | md5 서명(자세한 내용은 서명 알고리즘 참조) |
상태 상태 설명
| 상태 값 | 설명 |
|---|---|
SUCCESS | 완료 |
FAIL | 실패 |
유형 유형 설명
| 유형 값 | 설명 |
|---|---|
PAYMENT | 결제 |
WITHDRAW | 출금 |
bizType 비즈니스 유형 설명
| 비즈니스 유형 | 설명 |
|---|---|
PAYMENT_WALLET_SCAN | 결제할 VPAY 지갑 스캔 코드 |
PAYMENT_TRANSFER | 디지털 화폐 바인딩 주소 직접 입금 |
PAYMENT_ANY_DIGITAL_SCAN | QR 코드를 스캔하여 원하는 금액의 디지털 화폐를 결제하세요 |
PAYMENT_FIXED_DIGITAL_SCAN | 디지털화폐 정액 스캔코드 결제 |
WITHDRAW_WALLET | VPAY 지갑으로 출금 |
WITHDRAW_ANY_DIGITAL_WALLET | 디지털 지갑으로 인출 |
BATCH_PAY | 일괄결제 |
콜백 응답 요구사항
판매자가 콜백을 성공적으로 처리한 후에는 다음 콘텐츠가 반환되어야 합니다.
success
시스템이 success 문자열을 수신하면 콜백 처리가 성공한 것으로 간주되어 다시 전송되지 않습니다.
콜백 재시도 메커니즘
만약:
- 응답을 받지 못했습니다.
- 반환된 콘텐츠는
success가 아닙니다. - HTTP 요청 예외
- 서비스 시간 초과
시스템은 콜백 알림 전송을 자동으로 다시 시도합니다.
최대 재시도 횟수
14次
재시도 간격
15s
15s
30s
180s
600s
1200s
1800s
1800s
1800s
3600s
10800s
10800s
21600s
21600s
반복적인 콜백으로 인한 비즈니스 데이터의 반복 처리를 피하기 위해 판매자 시스템에서는 orderId에 따라 멱등성 처리를 구현하는 것이 좋습니다.
서명 확인
콜백 알림을 받은 가맹점은 먼저 서명 검증을 수행한 후 검증을 통과한 후 비즈니스 로직을 실행해야 합니다. 서명 알고리즘은 주문 요청 서명 규칙과 완전히 일치합니다. "2. 서명방법"을 참고하시기 바랍니다.
서명 확인 절차
- 콜백 매개변수에서
sign를 가져옵니다. - 매개변수에서
sign를 제거합니다. - 매개변수에 판매자
key를 입력하세요. - 판매자
secret를 사용하여 서명 규칙에 따라 서명을 다시 계산합니다. - 계산 결과가 콜백의
sign와 일치하는지 비교
서명 확인이 성공한 후에야 주문 업무가 진행됩니다.
Java 서명 확인 예시
public void notify(JSONObject data) {
log.info("收到回调通知:{}", data.toJSONString());
String key = "your_key";
String secret = "your_secret";
String sign = data.getString("sign");
data.put("key", key);
data.remove("sign");
String calculatedSign = SignUtils.getSign(data, secret);
if (!calculatedSign.equals(sign)) {
throw new DxBizException("签名验证失败");
}
// 业务处理逻辑
}