> For the complete documentation index, see [llms.txt](/ko/llms.txt)


# 8. 통신 오류와 연속 클릭에 대응하기

상점에서 연속 클릭, 코인 부족, 통신 오류가 발생했을 때를 확인합니다.



구매 버튼을 연속으로 누르거나 통신을 끊어 봅시다. 지금까지 사용한 플레이어로 진행합니다.

## 처리 중에 다시 눌러 보기

1. 코인을 20개 이상 준비합니다. 부족하면 퀘스트를 클리어하여 모읍니다.
2. **20코인으로 스태미나 10 회복** 을 누르고 곧바로 다시 눌러 봅니다.
3. 처리 중에는 버튼이 비활성화되어 다음 구매를 시작할 수 없는지 확인합니다.

코드는 조작을 시작할 때 `busy`를 `true`로, `finally`에서 `false`로 되돌립니다. 버튼 비활성화뿐 아니라 Studio의 `Run()`과 SDK의 `RunWrite()`도 처리 중 호출을 막습니다.

구매가 끝나 버튼이 다시 활성화된 뒤 누르면 새로운 구매가 됩니다.

## 코인 부족 메시지 확인하기

1. 구매를 반복하여 코인 잔액을 20 미만으로 만듭니다.
2. 구매 버튼을 다시 눌러 코인 부족 메시지가 나오는지 확인합니다.
3. 실패한 구매로 코인이 줄거나 스태미나가 회복되지 않았는지 확인합니다. 자연 회복으로 늘어난 양은 제외합니다.
4. 숲 탐험을 시작하여 퀘스트 조작은 계속할 수 있는지 확인한 뒤 클리어합니다.

코인이나 스태미나가 부족한 경우에는 이유를 표시하고 다음 조작을 받습니다.

## 같은 구매를 재시도할 수 있게 하기

통신이 끊겨도 서버에서는 구매가 완료되고 응답만 도착하지 않은 경우가 있습니다. 이때 새 구매를 보내면 코인을 한 번 더 쓰게 됩니다.

한 번의 구매에 하나의 키를 붙입니다. 같은 `duplicationAvoider` 키와 같은 내용으로 다시 보내면 GS2는 이미 완료한 구매를 반복하지 않고 그 결과를 반환합니다.

Unity 재생을 중지하고 진행 중인 과정의 파일로 같은 이름의 파일을 교체합니다. Inspector 참조는 그대로 사용합니다.


**Studio**

<a href="/samples/adventure/ko/08/AdventureStudioView.cs" download>AdventureStudioView.cs</a>를 다운로드합니다.

구매 메서드 `Buy()`는 다음과 같습니다. `purchaseId`는 클래스 필드입니다.

```csharp
private string purchaseId;

private async Task Buy()
{
    purchaseId ??= Guid.NewGuid().ToString();
    var request = Gs2ClientHolder.Instance.Gs2.Exchange.Namespace("EnergyProduct")
        .Me(Gs2GameSessionHolder.Instance.GameSession).Exchange();
    var transaction = await request.ExchangeAsync(
        "refill", 1, speculativeExecute: false, duplicationAvoider: purchaseId);
    if (transaction != null) await transaction.WaitAsync(all: true);
    purchaseId = null;
}
```


**SDK (Unity)**

<a href="/samples/adventure/ko/08/AdventurePlay.cs" download>AdventurePlay.cs</a>를 다운로드합니다. `AdventurePlayer.cs`와 `AdventureLogin.cs`는 그대로 사용합니다.

구매 메서드 `BuyRefill()`은 다음과 같습니다. `purchaseId`는 클래스 필드입니다.

```csharp
private string purchaseId;

private async UniTask BuyRefill()
{
    purchaseId ??= Guid.NewGuid().ToString();
    var transaction = await player.Gs2.Exchange.Namespace("adventure-shop").Me(player.Session)
        .Exchange().ExchangeAsync(
            "refill", 1, speculativeExecute: false, duplicationAvoider: purchaseId);
    awaitingResult = true;
    if (transaction != null) await transaction.WaitAsync(all: true);
    purchaseId = null;
}
```




`??=`는 값이 없을 때만 대입합니다. 통신에 실패해도 `purchaseId`가 남으므로 재시도에 같은 키를 사용합니다. 구매가 완료되면 비우고 다음 구매에서 새 키를 만듭니다. 코인 부족으로 거절된 경우에도 오류 처리에서 비웁니다.

통신 오류 후 버튼은 **같은 구매 재시도** 로 바뀝니다. 결과를 받기 전에는 다른 퀘스트나 새 구매를 시작하지 않고 이 구매를 다시 보낼 수 있게 합니다.

## 통신을 끊어 보기

1. Unity를 재생하고 로그인합니다. 코인 20개 이상과 스태미나 10 이하로 맞추고 구매 전 값을 기록합니다.
2. 컴퓨터의 네트워크 연결을 끊고 구매 버튼을 누릅니다.
3. 통신 오류가 표시되고 버튼이 **같은 구매 재시도** 로 바뀔 때까지 기다립니다.
4. 네트워크를 복구하고 Unity를 재생한 채 **같은 구매 재시도** 를 누릅니다.
5. 자연 회복량을 제외하고 코인이 20 줄고 스태미나가 10 회복되는지 확인합니다. 완료되면 버튼이 일반 구매 문구로 돌아옵니다.

이번에는 연결된 상태에서 구매를 누른 직후 통신을 끊어 봅니다. 응답이 먼저 도착해 구매가 끝났다면 일반 구매 버튼으로 돌아옵니다. 재시도 버튼이 나왔을 때만 연결을 복구하여 다시 보냅니다.

이 샘플은 Unity 재생 중에 키를 보관하므로 재생을 멈추지 않고 시험합니다. GS2의 중복 회피 결과 보관 기간은 1시간이므로 이 연습에서는 연결을 복구하면 바로 재시도합니다.

## 연습: 상황에 맞는 메시지 만들기

**처리 중입니다**, **코인이 부족합니다**, **연결을 확인해 주세요** 처럼 현재 상황을 알 수 있는 메시지로 바꿔 봅시다. Studio는 `Run()`, SDK는 `RunWrite()`와 `WriteError()`에서 문구를 수정합니다.

[다음: 응용편으로 이동하기]()



