g 프로젝트를 진행할 때 다른 사람의 프로젝트 import 시 가끔 아래의 에러가 뜨는 경우가 있다.

내 경우에는 아래와 같은 방법으로 해결하였다.

Description Resource Path Location Type

cvc-id.3: A field of identity constraint 'web-app-filter-name-uniqueness' matched element 'web-app', but this element does not have a simple type. web.xml /test_project/src/main/webapp/WEB-INF line 41 Language Servers

Description Resource Path Location Type

cvc-id.3: A field of identity constraint 'web-app-servlet-name-uniqueness' matched element 'web-app', but this element does not have a simple type. web.xml /test_project/src/main/webapp/WEB-INF line 26 Language Servers

Description Resource Path Location Type

There are '37' errors in 'jsp_2_1.xsd'. web.xml /test_project/src/main/webapp/WEB-INF line 2 Language Servers

web.xml 파일의

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

metadata-complete="true">

부분을

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://JAVA.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

metadata-complete="true">

이렇게 변경해주면 해결 된다.

변경 전

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

변경 후

xsi:schemaLocation="http://JAVA.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

Spring 으로 구축된 서비스에 네이버 클라우드 플랫폼의 SMS API 를 이용해서 SMS 를 보내는 방법입니다. 

정식 서비스 명칭은 Simple & Easy Notification Service 이고 줄여서 SENS 라고 부릅니다. 

네이버 클라우드 플랫폼 메인 페이지 > 서비스 > Application Service > Simple & Easy Notification Service 에서 확인 가능합니다. ​

 

개인적으로 테스트 하고 싶으시면 네이버 클라우드 플랫폼에 가입하신 후

네이버 클라우드 플랫폼 메인 페이지 > 서비스 > Application Service > Simple & Eash Notification Service

에서 이용 신청 하신 후 프로젝트 생성 하셔야 이용 가능합니다. ​

 

개인 계정으로 가입해서 테스트 시에는 발송 전화번호를 기입해야 하고, 아래 코드의 sendSMS() 에서 발신번호 항목에 이용 신청한 전화번호를 넣어야 오류 없이 SMS 가 전송됩니다.

법인 계정으로 가입 및 SMS 발송 서비스 이용시에는 별도의 법인 인증 절차 등이 필요합니다. ​

 

제 소스코드에서 사용한 import 내용들입니다. 참고하시기 바랍니다.

signature 를 만들 때 Access Key, Secret Key, Service ID 가 필요한데, 아래 SMS API설명서에는 어디에 있는 내용들이라는 설명 따위는 없는 것 같습니다. 몇 번 찾아 봤는데 그런 설명 못 찾겟네요. ㅋ;;; ​

 

Access Key, Secret Key, Service ID 는 아래 경로에서 확인 가능합니다.

// Access Key : https://www.ncloud.com/mypage/manage/info > 인증키 관리 > Access Key ID 

// Secret Key : https://www.ncloud.com/mypage/manage/info > 인증키 관리 > Secret Key 

// Service ID : https://console.ncloud.com/sens/project > Simple & Easy Notification Service > Project > 서비스 ID ​ 

 

아래 코드에 있는 값들은 실제 제가 사용하는 값들에서 몇 몇 문자들을 다른 문자로 임의로 변경한 것입니다. 해당 Key 값의 길이나 형태 등 참고하시라고 비슷한 형태로 놔뒀습니다.

 

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

 

// https://api.ncloud-docs.com/docs/common-ncpapi
	private String makeSignature(String url, String timestamp, String method, String accessKey, String secretKey) throws NoSuchAlgorithmException, InvalidKeyException {
	    String space = " ";                    // one space
	    String newLine = "\n";                 // new line
	    

	    String message = new StringBuilder()
	        .append(method)
	        .append(space)
	        .append(url)
	        .append(newLine)
	        .append(timestamp)
	        .append(newLine)
	        .append(accessKey)
	        .toString();

	    SecretKeySpec signingKey;
	    String encodeBase64String;
		try {
			
			signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
			Mac mac = Mac.getInstance("HmacSHA256");
			mac.init(signingKey);
			byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
		    encodeBase64String = Base64.getEncoder().encodeToString(rawHmac);
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			encodeBase64String = e.toString();
		}
	    

	  return encodeBase64String;
	}

 

	/*
	 * https://api.ncloud-docs.com/docs/ko/ai-application-service-sens-smsv2
		{
		    "type":"(SMS | LMS | MMS)",
		    "contentType":"(COMM | AD)",
		    "countryCode":"string",
		    "from":"string",
		    "subject":"string",
		    "content":"string",
		    "messages":[
		        {
		            "to":"string",
		            "subject":"string",
		            "content":"string"
		        }
		    ],
		    "files":[
		        {
		            "name":"string",
		            "body":"string"
		        }
		    ],
		    "reserveTime": "yyyy-MM-dd HH:mm",
		    "reserveTimeZone": "string",
		    "scheduleCode": "string"
		}
	 */
	private void sendSMS() {
		String hostNameUrl = "https://sens.apigw.ntruss.com";     		// 호스트 URL
		String requestUrl= "/sms/v2/services/";                   		// 요청 URL
		String requestUrlType = "/messages";                      		// 요청 URL
		String accessKey = "QWALu3XgiCxABC2aynAf";                     	// 네이버 클라우드 플랫폼 회원에게 발급되는 개인 인증키			// Access Key : https://www.ncloud.com/mypage/manage/info > 인증키 관리 > Access Key ID
		String secretKey = "bXGAcyQw1FG9Zjq6f1U8SD5CHMFVsvumivXoP194";  // 2차 인증을 위해 서비스마다 할당되는 service secret key	// Service Key : https://www.ncloud.com/mypage/manage/info > 인증키 관리 > Access Key ID	
		String serviceId = "ncp:sms:kr:178053617394:projectname";       // 프로젝트에 할당된 SMS 서비스 ID							// service ID : https://console.ncloud.com/sens/project > Simple & ... > Project > 서비스 ID
		String method = "POST";											// 요청 method
		String timestamp = Long.toString(System.currentTimeMillis()); 	// current timestamp (epoch)
		requestUrl += serviceId + requestUrlType;
		String apiUrl = hostNameUrl + requestUrl;
		
		// JSON 을 활용한 body data 생성
		JSONObject bodyJson = new JSONObject();
		JSONObject toJson = new JSONObject();
	    JSONArray  toArr = new JSONArray();

	    //toJson.put("subject","");							// Optional, messages.subject	개별 메시지 제목, LMS, MMS에서만 사용 가능
	    //toJson.put("content","sms test in spring 111");	// Optional, messages.content	개별 메시지 내용, SMS: 최대 80byte, LMS, MMS: 최대 2000byte
	    toJson.put("to","01012345678");						// Mandatory(필수), messages.to	수신번호, -를 제외한 숫자만 입력 가능
	    toArr.put(toJson);
	    
	    bodyJson.put("type","SMS");							// Madantory, 메시지 Type (SMS | LMS | MMS), (소문자 가능)
	    //bodyJson.put("contentType","");					// Optional, 메시지 내용 Type (AD | COMM) * AD: 광고용, COMM: 일반용 (default: COMM) * 광고용 메시지 발송 시 불법 스팸 방지를 위한 정보통신망법 (제 50조)가 적용됩니다.
	    //bodyJson.put("countryCode","82");					// Optional, 국가 전화번호, (default: 82)
	    bodyJson.put("from","01012345678");					// Mandatory, 발신번호, 사전 등록된 발신번호만 사용 가능		
	    //bodyJson.put("subject","");						// Optional, 기본 메시지 제목, LMS, MMS에서만 사용 가능
	    bodyJson.put("content","sms test in spring 222");	// Mandatory(필수), 기본 메시지 내용, SMS: 최대 80byte, LMS, MMS: 최대 2000byte
	    bodyJson.put("messages", toArr);					// Mandatory(필수), 아래 항목들 참조 (messages.XXX), 최대 1,000개
	    
	    //String body = bodyJson.toJSONString();
	    String body = bodyJson.toString();
	    
	    System.out.println(body);
	    
        try {
            URL url = new URL(apiUrl);

            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setUseCaches(false);
            con.setDoOutput(true);
            con.setDoInput(true);
            con.setRequestProperty("content-type", "application/json");
            con.setRequestProperty("x-ncp-apigw-timestamp", timestamp);
            con.setRequestProperty("x-ncp-iam-access-key", accessKey);
            con.setRequestProperty("x-ncp-apigw-signature-v2", makeSignature(requestUrl, timestamp, method, accessKey, secretKey));
            con.setRequestMethod(method);
            con.setDoOutput(true);
            DataOutputStream wr = new DataOutputStream(con.getOutputStream());
            
            wr.write(body.getBytes());
            wr.flush();
            wr.close();

            int responseCode = con.getResponseCode();
            BufferedReader br;
            System.out.println("responseCode" +" " + responseCode);
            if(responseCode == 202) { // 정상 호출
                br = new BufferedReader(new InputStreamReader(con.getInputStream()));
            } else { // 에러 발생
                br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
            }

            String inputLine;
            StringBuffer response = new StringBuffer();
            while ((inputLine = br.readLine()) != null) {
                response.append(inputLine);
            }
            br.close();
            
            System.out.println(response.toString());

        } catch (Exception e) {
            System.out.println(e);
        }
	}

 

* Reference

-. 서비스 안내 : https://www.ncloud.com/product/applicationService/sens

-. 사용 가이드 : https://guide.ncloud-docs.com/docs/ko/sens-sens-1-1

-. SMS API 사용 설명서 : https://api.ncloud-docs.com/docs/ko/ai-application-service-sens

-. 인증키 생성 예제 : https://api.ncloud-docs.com/docs/common-ncpapi

-. SMS 요청 내용(Json) : https://api.ncloud-docs.com/docs/ko/ai-application-service-sens-smsv2

2021년 8월 2일부터 모든 신규 앱은 결제 라이브러리 버전 3 이상을 사용해야 합니다. 2021년 11월 1일까지 기존 앱의 모든 업데이트는 결제 라이브러리 버전 3 이상을 사용해야 합니다.

bemind.tistory.com/24 의 내용과 기본적인 흐름은 같습니다.

위 링크에서 작성된 내용을 기반으로 결제 라이브러리 버전 3의 내용을 적용하였습니다.

기존에 작성된 내용에서 int responseCode 는 BillingResult billingResult 으로 변경되었고,

그에 따라 구매 결과 확인을 billingResult.getResponseCode() 를 이용해 확인합니다.

구매 내역 처리시

ConsumeParams consumeParams =

ConsumeParams.newBuilder()

.setPurchaseToken(purchase.getPurchaseToken())

.build();

부분이 추가 되었습니다.

Reference : https://developer.android.com/google/play/billing/integrate#java

 

1. app 수준 build.gradle 에 dependencies 추가

dependencies {
    ...
    def billing_version = "3.0.0"
    implementation "com.android.billingclient:billing:$billing_version"
}

 

2. 소스코드
결제기능을 구현하고자 하는 화면에 아래 구문 추가.

public class BuyPointActivity extends AppCompatActivity implements PurchasesUpdatedListener {
    Context mContext;

    // create new Person
    private BillingClient mBillingClient;

    SkuDetails skuDetails700, skuDetails2100;
    String skuID700 = "point_700", skuID2100 = "point_2100";  //제품 ID
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_buy_point);
        mContext = BuyPointActivity.this;
        ...
        mBillingClient = BillingClient.newBuilder(mContext).setListener(this).build();
        mBillingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingResponse.OK) {
                    // The billing client is ready. You can query purchases here.
                    List<String> skuList = new ArrayList<> ();
                    skuList.add(skuID700);
                    skuList.add(skuID2100);
                    SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
                    params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
                    mBillingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
                        @Override
                        public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                            // Process the result.
                            if (billingResult.getResponseCode() == BillingClient.BillingResponse.OK && skuDetailsList != null) {
                                for (SkuDetails skuDetails : skuDetailsList) {
                                    String sku = skuDetails.getSku();
                                    String price = skuDetails.getPrice();

                                    if(skuID700.equals(sku)) {
                                        skuDetails700 = skuDetails;
                                    } else if(skuID2100.equals(sku)) {
                                        skuDetails2100 = skuDetails;
                                    } 
                                }
                            }
                        }});
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
            }
        });
    }

    private void doBillingFlow(SkuDetails skuDetails) {
        BillingFlowParams flowParams;
        int responseCode;

        // Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
        flowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();
        responseCode = mBillingClient.launchBillingFlow(BuyPointActivity.this, flowParams);
    }

    private void handlePurchase(Purchase purchase) {
        String purchaseToken;
        ConsumeParams consumeParams =
        ConsumeParams.newBuilder()
            .setPurchaseToken(purchase.getPurchaseToken())
            .build();
        mBillingClient.consumeAsync(consumeParams, consumeListener);
    }

    ConsumeResponseListener consumeListener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponse.OK) {
                // Handle the success of the consume operation.
                // For example, increase the number of coins inside the user's basket.
            }
        }
    };

    /**
     * Handle a callback that purchases were updated from the Billing library
     */
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        if (billingResult.getResponseCode() == BillingClient.BillingResponse.OK
                && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponse.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
        } else {
            // Handle any other error codes.
        }
    }

    Button.OnClickListener mClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int id = v.getId();

            switch (id) {
                case R.id.btn1000:
                    doBillingFlow(skuDetails700);
                    break;
                ...
            }
        }
    };
    ...
}

 

 

비소비성 구매를 확인하려면 결제 라이브러리의 BillingClient.acknowledgePurchase() 또는 Google Play Developer API의 Product.Purchases.Acknowledge를 사용합니다. 구매를 확인하기 전에 Google Play 결제 라이브러리의 isAcknowledged() 메서드 또는 Google Developer API의 acknowledgementState 필드를 사용하여 앱에서 이미 구매를 확인했는지 검토해야 합니다.

 

다음 예는 Google Play 결제 라이브러리를 사용하여 구매를 확인하는 방법을 보여줍니다.

BillingClient client = ...
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = ...

void handlePurchase(Purchase purchase) {
    if (purchase.getPurchaseState() == PurchaseState.PURCHASED) {
        if (!purchase.isAcknowledged()) {
            AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                    .setPurchaseToken(purchase.getPurchaseToken())
                    .build();
            client.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
        }
    }
}

 

 

Google Play 결제 라이브러리 통합 테스트  |  Google Play 결제 시스템  |  Android Developers

알림: 2021년 11월 1일부터는 기존 앱의 모든 업데이트에도 결제 라이브러리 버전 3 이상이 요구됩니다. 자세히 알아보기 Google Play 결제 라이브러리 통합 테스트 개발 과정 전체에서 통합을 테스트

developer.android.com

 

 

com.google.android.gms.common.api.ApiException: 12500 에러 처리


Firebase 와 Google Cloud Platform(GCP) 를 적절하게 잘 활용하면, jsp, asp, php 같은 서버 축 개발을 정말 1도 하지 않고도, 회원 관리 및 기타 디비 관리, RESTful API 구현 등이 가능합니다. 


Firebase 의 여러 서비스 중 Authentication 을 사용하기 위해, 로그인 방법으로 Google 을 선택하여 관련 기능을 구현 한 후 com.google.android.gms.common.api.ApiException: 12500 와 같은 에러가 뜨는 경우가 있는데, 

이런 경우 Google Cloud Platform 콘솔에 접속하여 해당 프로젝트를 선택한 후 'API 및 서비스' > 'OAuth 동의 화면' 진입 후 나타나는 화면에서 하단에 어플리케이션 홈페이지 링크, 어플리케이션 개인정보처리방침 링크에 관련 내용을 담은 홈페이지 주소를 링크해야 합니다. 

앱 개발 초기에는 만들어 놓은 페이지가 없는 경우가 대부분 이므로, 그 입력난 바로 위에 '승인된 도메인' 부분의 xxx.firebaseapp.com 을 그대로 복사하여, https://xxx.firebaseapp.com 주소를 그냥 써 넣으면 됩니다. 



개인정보보호 이슈 때문에 이런 제약사항이 최근에 생긴 듯 한데, 검색해도 해결 방법 찾기가 쉽지 않네요. 

Reference : https://developer.android.com/google/play/billing/billing_library_overview

 

1. app 수준 build.gradle 에 dependencies 추가

 

1
2
3
4
dependencies {
   ...
implementation 'com.android.billingclient:billing:1.2'
}
cs

 

 

 

2. 소스코드

결제기능을 구현하고자 하는 화면에 아래 구문 추가.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
public class BuyPointActivity extends AppCompatActivity implements PurchasesUpdatedListener {
    Context mContext;
 
    // create new Person
    private BillingClient mBillingClient;
 
    SkuDetails skuDetails700, skuDetails2100;
    String skuID700 = "point_700", skuID2100 = "point_2100";  //제품 ID
    ...
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_buy_point);
        mContext = BuyPointActivity.this;
        ...
        mBillingClient = BillingClient.newBuilder(mContext).setListener(this).build();
        mBillingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(@BillingResponse int billingResponseCode) {
                if (billingResponseCode == BillingResponse.OK) {
                    // The billing client is ready. You can query purchases here.
                    List<String> skuList = new ArrayList<> ();
                    skuList.add(skuID700);
                    skuList.add(skuID2100);
                    SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
                    params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
                    mBillingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
                        @Override
                        public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
                            // Process the result.
                            if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
                                for (SkuDetails skuDetails : skuDetailsList) {
                                    String sku = skuDetails.getSku();
                                    String price = skuDetails.getPrice();
 
                                    if(skuID700.equals(sku)) {
                                        skuDetails700 = skuDetails;
                                    } else if(skuID2100.equals(sku)) {
                                        skuDetails2100 = skuDetails;
                                    } 
                                }
                            }
                        }});
                }
            }
 
            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
            }
        });
    }
 
    /**
     * Handle a callback that purchases were updated from the Billing library
     */
    @Override
    public void onPurchasesUpdated(int responseCode, @Nullable List<Purchase> purchases) {
        if (responseCode == BillingClient.BillingResponse.OK
                && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        } else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
        } else {
            // Handle any other error codes.
        }
    }
 
    private void doBillingFlow(SkuDetails skuDetails) {
        BillingFlowParams flowParams;
        int responseCode;
 
        // Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
        flowParams = BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build();
        responseCode = mBillingClient.launchBillingFlow(BuyPointActivity.this, flowParams);
 
        /*if(responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED) {
            Purchase.PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
            onPurchasesUpdated(BillingClient.BillingResponse.OK, purchasesResult.getPurchasesList());
        }*/
    }
 
    private void handlePurchase(Purchase purchase) {
        String purchaseToken;
        purchaseToken = purchase.getPurchaseToken();
        mBillingClient.consumeAsync(purchaseToken, consumeListener);
    }
 
    ConsumeResponseListener consumeListener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(@BillingClient.BillingResponse int responseCode, String outToken) {
            if (responseCode == BillingClient.BillingResponse.OK) {
                // Handle the success of the consume operation.
                // For example, increase the number of coins inside the user's basket.
            }
        }
    };
 
    Button.OnClickListener mClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int id = v.getId();
 
            switch (id) {
                case R.id.btn700:
                    doBillingFlow(skuDetails700);
                    break;
 
                case R.id.btn2100:
                    doBillingFlow(skuDetails2100);
                    break;
                ...
            }
        }
    };
    ...
}
cs

 

 

 

 

doBillingFlow 안에 if(responseCode == BillingClient.BillingResponse.ITEM_ALREADY_OWNED)

코드는 처음에 테스트 구매 하다가 구매성공 후에 consume 처리를 안해서 다시 구매가 안 된 경우에 consume 시키려고 놔둔 코드 입니다. 구매 완료후 consume 를 제대로 하지 못한 경우 등에 이 코드를 쓰면 될 것 같습니다.

TrivialDrive v2 sample app 은 너무 복잡해서 잘 이해가 안 되고, anjlab 의 android-inapp-billing-v3 는 왠지 안내켜서 Google Play 결제 라이브러리 사용하여 구현해보았습니다. 인앱 결제 구현 구글 문서는 최신 내용이 아닌 듯 합니다. 업무에 참고 하시기 바랍니다.

ps.

-. 이 방법을 사용하면 Google 마켓 개발자 콘솔 설정에 있는 '서비스 및 API' 의 Base64 인코딩 RSA 공개 키 인 애플리케이션용 라이선스 키 를 입력하지 않아도 됩니다.

-. Manifest 에 <uses-permission android:name="com.android.vending.BILLING" /> 도 추가하지 않아도 됩니다. 다만 앱 등록시 인앱상품 생성을 위해 이 권한을 요구하긴 합니다.

-. 인앱 결제 테스트 및 디버깅(참고 : https://developer.android.com/google/play/billing/billing_testing?hl=ko)*인앱결제를 디버깅 모드에서도 테스트 하려면 알파 또는 베타 채널에 앱을 게시해야 하고, 게시하면 생기는 테스트 주소 https://play.google.com/apps/testing/com.android.test 와 같은 주소를 인터넷 앱등을 통해 접속한 후

테스트 참여 대상 관리 에 등록된

계정 주소를 입력해야 합니다. 이렇게 하고 나면 개발하는 앱의 디버깅 모드에서도 결제 테스트가 가능합니다. 테스트 참여 대상 관리 에 등록된 사용자만 테스트 주소(https://play.google.com/apps/testing/com.android.test)에 등록되는지는 확인해보지 못했습니다.
1. Google Play Console 접속2. 좌측 하단 '설정' 클릭3. 라이선스 테스트 추가테스트 하고자 하는 계정을 추가합니다. (구글 플레이에 설정된 계정 등)

 

 

 

 

 

 

 

+ Recent posts