Skip to content
isdnetworks
Go back

결제 영수증을 확인해야 했다

Android 앱 안에서 아이템을 사는 기능을 붙였다. In-app Billing 으로 구매를 요청하면 마켓 화면이 뜨고 성공하면 아이템을 준다.

Table of contents

Open Table of contents

앱이 판단하고 있었다

성공 여부를 앱이 PURCHASE_STATE_CHANGED 로 받아 판단하고 Servlet 에 지급을 요청하는 구조였다.

if (result == SUCCESS) {
    서버에_지급_요청(itemId);
}

그러면 결제 없이 지급 요청만 보내도 아이템이 들어온다. 서버에는 앱이 한 말 말고 근거가 없다. curl 로 그 주소에 직접 쏴 보니 그대로 들어왔다.

서버가 확인하게 했다

문서를 보니 그 방송에 signedDatasignature 가 같이 온다. 앞은 거래 내용이 담긴 JSON 이고 뒤는 그것에 대한 서명이다. 둘 다 서버로 보내 서버가 확인하게 했다.

Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
boolean ok = sig.verify(Base64.decode(signature));

공개 키는 개발자 콘솔에서 받은 RSA 키다. 위조하려면 비밀 키가 필요한데 그건 마켓에만 있다. 키를 APK 에 두면 앱에서 확인하게 되는데 APK 는 뜯을 수 있으니 서버에 뒀다.

구글이 준 예제의 Security.java 도 앱에서 같은 확인을 한다. 그건 화면을 빨리 넘기기 위한 편의로 두고 실제 판단은 서버 것만 믿었다. 둘 다 하되 무엇을 믿을지를 정해 뒀다.

중복 지급과 소비 처리

같은 signedData 를 여러 번 보내면 여러 번 지급되는 문제가 남았다. 그 JSON 안에 orderId 가 있다. MySQL 에 기록하고 UNIQUE 를 걸었다. 두 번째 요청은 1062 에서 걸린다.

지급을 마친 뒤에는 마켓에 CONFIRM_NOTIFICATIONS 를 보내야 한다. 그 순서가 문제였다. 확인을 먼저 보내고 지급이 실패하면 돈만 나간다. 지급하고 확인이 실패하면 마켓이 그 거래를 계속 다시 알려 준다.

뒤쪽을 골랐다. 다시 알려 오는 것은 orderId 로 걸러지니 사용자가 손해를 보지 않는다. 확인이 실패한 것은 목록으로 남기고 다시 시도하게 했다.

반복해서 살 수 있어야 하는 소모품은 애초에 비관리 상품으로 등록했다. 관리 상품으로 등록하면 한 번 산 사람은 다시 못 산다.

환불은 기록만 했다

사용자가 결제를 환불하면 아이템은 그대로 남는다. 환불도 같은 방송으로 purchaseState 값이 달리 와서 알 수 있다. 받고 나서 회수할지가 문제였다.

아직 안 쓴 것은 회수할 수 있다. 이미 쓴 것은 잔액이 마이너스가 된다. 그 마이너스를 어떻게 할지가 안 정해져 있었다.

그래서 환불 건을 기록만 하고 자동 회수는 안 했다. 규칙이 정해진 것은 자동화하고 안 정해진 것은 사람이 본다. 애매한 것을 자동으로 하면 잘못된 처리가 쌓인다. 그것을 되돌리는 일이 더 커진다.

정리


Share this post on:

Previous Post
고쳤다고 했는데 확인을 안 했다
Next Post
한 자리에 계산값과 손으로 넣은 값이 섞였다