());
23 | choosePosition = -1;
24 | }
25 |
26 | @Override
27 | protected void convert(@NonNull BaseViewHolder helper, GooglePayPurchaseBean.InappproductBean item) {
28 | helper.setText(R.id.tv_id, "商品id:" + item.getSku());
29 | helper.setText(R.id.tv_type, "商品类型:" + item.getPurchaseType());
30 |
31 | if (choosePosition != -1 && getData().indexOf(item) == choosePosition) {
32 | helper.getView(R.id.ll_root).setBackgroundColor(Color.BLACK);
33 | } else {
34 | helper.getView(R.id.ll_root).setBackgroundColor(Color.GRAY);
35 | }
36 | }
37 |
38 |
39 | public void setChoosePosition(int choosePosition) {
40 | this.choosePosition = choosePosition;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/github_google_pay/GoogleAccsessTokenBean.java:
--------------------------------------------------------------------------------
1 | package com.googlepaytest.csx.github_google_pay;
2 |
3 | /**
4 | * Date: 2019/8/28
5 | * create by cuishuxiang
6 | * description:
7 | */
8 | public class GoogleAccsessTokenBean {
9 |
10 | /**
11 | * access_token : ya29.GltyB1aAtM7QEz0T0GVtNZk64bXldamoCWU32us0fe2zD8HvBNW3LMig4-T2p3EAc4oDfozqa6ZHIaNfCC19KFk4qFhPwAniSzy2r7OeunPHx8P6tzCwjKA1_H4F
12 | * expires_in : 3600// 访问令牌的剩余生命周期(以秒为单位)。
13 | * refresh_token : 1/ZSenrx4IL5iUnyA_P0TjDG9GpY6xENpEIv4LeQeo3mg
14 | * scope : https://www.googleapis.com/auth/androidpublisher
15 | * token_type : Bearer
16 | */
17 |
18 | private String access_token;
19 | private int expires_in;
20 | private String refresh_token;
21 | private String scope;
22 | private String token_type;
23 |
24 | public String getAccess_token() {
25 | return access_token;
26 | }
27 |
28 | public void setAccess_token(String access_token) {
29 | this.access_token = access_token;
30 | }
31 |
32 | public int getExpires_in() {
33 | return expires_in;
34 | }
35 |
36 | public void setExpires_in(int expires_in) {
37 | this.expires_in = expires_in;
38 | }
39 |
40 | public String getRefresh_token() {
41 | return refresh_token;
42 | }
43 |
44 | public void setRefresh_token(String refresh_token) {
45 | this.refresh_token = refresh_token;
46 | }
47 |
48 | public String getScope() {
49 | return scope;
50 | }
51 |
52 | public void setScope(String scope) {
53 | this.scope = scope;
54 | }
55 |
56 | public String getToken_type() {
57 | return token_type;
58 | }
59 |
60 | public void setToken_type(String token_type) {
61 | this.token_type = token_type;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/github_google_pay/GooglePayConstant.java:
--------------------------------------------------------------------------------
1 | package com.googlepaytest.csx.github_google_pay;
2 |
3 | /**
4 | * Date: 2019/8/13
5 | * create by cuishuxiang
6 | * description:
7 | */
8 | public class GooglePayConstant {
9 | //Google Pay key
10 | public static final String PayLicenseKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1ilVUxnLykxscnTJh+DFsaD2g1nW644d6801UxdW7Opfj01Uefq9oeoj/N2cmMNCC9XB9L0S6351kui3j0AsXTvrmFGUeg6C7YfBazSeDqqiUu4G16wF42DdzK4CRba+gz6J8Kb5I4eDvXnjc+8xDnblCkkFVSAFS8dW5uTe4+sQIFafwTQv6gq/POkv8/+wW1Sdxbhl5Q7yMYjh041yKvT+1DczgIqNjFRqvHlPkQMDKWQ2/lm8MEekAE/MFdGUfrVpSUB+A5DpcTZPfm6G8wgZ2S+FMo/DClMReuh+OXAXGVIYNmqqA6+6gHzYvTg9HAPBzOPK/sQHRu0bLpPUwwIDAQAB";
11 |
12 |
13 | //GET https://www.googleapis.com/androidpublisher/v3/applications/packageName/purchases/products/productId/tokens/token
14 | public static final String queryPurchaseState = "https://www.googleapis.com/androidpublisher/v3/applications/com.googlepay.client/purchases/products/productId/tokens/token";
15 |
16 |
17 | /**
18 | * Google 预留的静态测试id
19 | * 1,android.test.purchased
20 | * 当您使用此商品 ID 发送 Google Play 结算服务请求时,Google Play 会假定您成功购买了商品,并据此进行响应。
21 | * 响应包括一个 JSON 字符串,其中包含虚拟购买信息(例如虚拟订单 ID)。
22 | * 2,android.test.canceled
23 | * 当您使用此商品 ID 发送 Google Play 结算服务请求时,Google Play 会假定购买已被取消,并据此做出响应。
24 | * 如果订购流程出现问题(例如信用卡无效,或您在用户付款之前取消了订单),就会发生这种情况。
25 | * 3,android.test.item_unavailable
26 | * 当您使用此商品 ID 发送 Google Play 结算服务请求时,
27 | * Google Play 会假定所购商品在您应用的商品列表中不存在,并据此做出响应。
28 | */
29 | public static final String GooglePurchaseId = "android.test.purchased";
30 | public static final String GoogleCancelId = "android.test.canceled";
31 | public static final String GoogleUnAvailableId = "android.test.item_unavailable";
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.googlepaytest.csx"
7 | minSdkVersion 15
8 | targetSdkVersion 28
9 | versionCode 13
10 | versionName "13"
11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 |
20 | signingConfigs {
21 | release {
22 | storeFile file("../XindunKeyStore.jks")
23 | storePassword "xindun110"
24 | keyAlias "NewShieldSafeHouse"
25 | keyPassword "xindun110"
26 | v1SigningEnabled true
27 | v2SigningEnabled true
28 | }
29 | debug {
30 | storeFile file("../XindunKeyStore.jks")
31 | storePassword "xindun110"
32 | keyAlias 'NewShieldSafeHouse'
33 | keyPassword "xindun110"
34 | v1SigningEnabled true
35 | v2SigningEnabled true
36 | }
37 | }
38 | }
39 |
40 | dependencies {
41 | implementation fileTree(include: ['*.jar'], dir: 'libs')
42 | implementation 'com.android.support:appcompat-v7:28.+'
43 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
44 | testImplementation 'junit:junit:4.12'
45 | androidTestImplementation 'com.android.support.test:runner:1.0.2'
46 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
47 | implementation 'com.anjlab.android.iab.v3:library:1.1.0'
48 |
49 |
50 | implementation 'com.zhy:okhttputils:2.6.2'
51 | implementation 'com.google.code.gson:gson:2.8.5'
52 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.47'
53 | implementation 'com.android.support:support-v4:28.0.0'
54 | implementation 'com.android.support:design:28.0.0'
55 | implementation 'com.android.support:recyclerview-v7:28.0.0'
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_pay_test.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
27 |
28 |
34 |
35 |
41 |
47 |
48 |
54 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.googlepaytest.csx;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Intent;
5 | import android.content.pm.PackageInfo;
6 | import android.content.pm.PackageManager;
7 | import android.os.UserHandle;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.os.Bundle;
10 | import android.view.View;
11 | import android.widget.Button;
12 |
13 | import android.widget.TextView;
14 | import com.googlepaytest.csx.github_google_pay.GitPayTestActivity;
15 | import com.googlepaytest.csx.native_google_pay.NativePayTestActivity;
16 | import okhttp3.OkHttpClient;
17 |
18 | import static android.webkit.WebViewZygote.getPackageName;
19 |
20 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
21 |
22 | Button btn_native, btn_git;
23 | TextView tv_app_vs_code;
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(R.layout.activity_main);
29 |
30 | btn_native = findViewById(R.id.btn_native);
31 | btn_native.setOnClickListener(this);
32 |
33 | btn_git = findViewById(R.id.btn_git);
34 | btn_git.setOnClickListener(this);
35 |
36 | tv_app_vs_code = findViewById(R.id.tv_app_vs_code);
37 |
38 | //获取版本号
39 | try {
40 | PackageManager packageManager=getPackageManager();
41 | PackageInfo packageInfo= null;
42 | packageInfo = packageManager.getPackageInfo(getPackageName(),PackageManager.GET_CONFIGURATIONS);
43 |
44 | tv_app_vs_code.setText("App版本code = " + packageInfo.versionCode);
45 |
46 | } catch (PackageManager.NameNotFoundException e) {
47 | e.printStackTrace();
48 | }
49 |
50 | initData();
51 | }
52 |
53 | private void initData(){
54 |
55 | }
56 |
57 |
58 | @Override
59 | public void onClick(View v) {
60 | Intent intent = null;
61 |
62 | switch (v.getId()) {
63 | case R.id.btn_native:
64 | intent = new Intent(this, NativePayTestActivity.class);
65 | startActivity(intent);
66 | break;
67 | case R.id.btn_git:
68 | intent = new Intent(this, GitPayTestActivity.class);
69 | startActivity(intent);
70 | break;
71 | }
72 |
73 | }
74 |
75 | @Override
76 | public synchronized ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
77 | return null;
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/github_google_pay/GooglePayCheckBean.java:
--------------------------------------------------------------------------------
1 | package com.googlepaytest.csx.github_google_pay;
2 |
3 | /**
4 | * Date: 2019/8/29
5 | * create by cuishuxiang
6 | * description:
7 | */
8 | public class GooglePayCheckBean {
9 |
10 | /**
11 | * kind : androidpublisher#productPurchase
12 | * purchaseTimeMillis : 1567044758226
13 | * purchaseState : 0
14 | * consumptionState : 0
15 | * developerPayload : inapp:test1:365254b0-96f4-4c42-8dcc-450d5d6c967e
16 | * orderId : GPA.3373-2691-0666-58819
17 | * purchaseType : 0
18 | * acknowledgementState : 1
19 | */
20 |
21 | private String kind;
22 | private String purchaseTimeMillis;
23 | private int purchaseState;
24 | private int consumptionState;
25 | private String developerPayload;
26 | private String orderId;
27 | private int purchaseType;
28 | private int acknowledgementState;
29 |
30 | public String getKind() {
31 | return kind;
32 | }
33 |
34 | public void setKind(String kind) {
35 | this.kind = kind;
36 | }
37 |
38 | public String getPurchaseTimeMillis() {
39 | return purchaseTimeMillis;
40 | }
41 |
42 | public void setPurchaseTimeMillis(String purchaseTimeMillis) {
43 | this.purchaseTimeMillis = purchaseTimeMillis;
44 | }
45 |
46 | public int getPurchaseState() {
47 | return purchaseState;
48 | }
49 |
50 | public void setPurchaseState(int purchaseState) {
51 | this.purchaseState = purchaseState;
52 | }
53 |
54 | public int getConsumptionState() {
55 | return consumptionState;
56 | }
57 |
58 | public void setConsumptionState(int consumptionState) {
59 | this.consumptionState = consumptionState;
60 | }
61 |
62 | public String getDeveloperPayload() {
63 | return developerPayload;
64 | }
65 |
66 | public void setDeveloperPayload(String developerPayload) {
67 | this.developerPayload = developerPayload;
68 | }
69 |
70 | public String getOrderId() {
71 | return orderId;
72 | }
73 |
74 | public void setOrderId(String orderId) {
75 | this.orderId = orderId;
76 | }
77 |
78 | public int getPurchaseType() {
79 | return purchaseType;
80 | }
81 |
82 | public void setPurchaseType(int purchaseType) {
83 | this.purchaseType = purchaseType;
84 | }
85 |
86 | public int getAcknowledgementState() {
87 | return acknowledgementState;
88 | }
89 |
90 | public void setAcknowledgementState(int acknowledgementState) {
91 | this.acknowledgementState = acknowledgementState;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_git_test_pay.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
20 |
26 |
27 |
33 |
34 |
38 |
39 |
46 |
47 |
53 |
54 |
60 |
61 |
62 |
68 |
69 |
75 |
81 |
82 |
88 |
89 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_native_test_pay.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
20 |
26 |
27 |
33 |
34 |
38 |
39 |
46 |
47 |
53 |
54 |
60 |
61 |
62 |
68 |
69 |
75 |
81 |
82 |
88 |
89 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/native_google_pay/Security.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.googlepaytest.csx.native_google_pay;
18 |
19 | import android.text.TextUtils;
20 | import android.util.Base64;
21 |
22 | import com.android.billingclient.util.BillingHelper;
23 |
24 | import java.io.IOException;
25 | import java.security.InvalidKeyException;
26 | import java.security.KeyFactory;
27 | import java.security.NoSuchAlgorithmException;
28 | import java.security.PublicKey;
29 | import java.security.Signature;
30 | import java.security.SignatureException;
31 | import java.security.spec.InvalidKeySpecException;
32 | import java.security.spec.X509EncodedKeySpec;
33 |
34 | /**
35 | * Security-related methods. For a secure implementation, all of this code should be implemented on
36 | * a server that communicates with the application on the device.
37 | */
38 | public class Security {
39 | private static final String TAG = "IABUtil/Security";
40 |
41 | private static final String KEY_FACTORY_ALGORITHM = "RSA";
42 | private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
43 |
44 | /**
45 | * Verifies that the data was signed with the given signature, and returns the verified
46 | * purchase.
47 | * @param base64PublicKey the base64-encoded public key to use for verifying.
48 | * @param signedData the signed JSON string (signed, not encrypted)
49 | * @param signature the signature for the data, signed with the private key
50 | * @throws IOException if encoding algorithm is not supported or key specification
51 | * is invalid
52 | */
53 | public static boolean verifyPurchase(String base64PublicKey, String signedData,
54 | String signature) throws IOException {
55 | if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
56 | || TextUtils.isEmpty(signature)) {
57 | BillingHelper.logWarn(TAG, "Purchase verification failed: missing data.");
58 | return false;
59 | }
60 |
61 | PublicKey key = generatePublicKey(base64PublicKey);
62 | return verify(key, signedData, signature);
63 | }
64 |
65 | /**
66 | * Generates a PublicKey instance from a string containing the Base64-encoded public key.
67 | *
68 | * @param encodedPublicKey Base64-encoded public key
69 | * @throws IOException if encoding algorithm is not supported or key specification
70 | * is invalid
71 | */
72 | public static PublicKey generatePublicKey(String encodedPublicKey) throws IOException {
73 | try {
74 | byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
75 | KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
76 | return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
77 | } catch (NoSuchAlgorithmException e) {
78 | // "RSA" is guaranteed to be available.
79 | throw new RuntimeException(e);
80 | } catch (InvalidKeySpecException e) {
81 | String msg = "Invalid key specification: " + e;
82 | BillingHelper.logWarn(TAG, msg);
83 | throw new IOException(msg);
84 | }
85 | }
86 |
87 | /**
88 | * Verifies that the signature from the server matches the computed signature on the data.
89 | * Returns true if the data is correctly signed.
90 | *
91 | * @param publicKey public key associated with the developer account
92 | * @param signedData signed data from server
93 | * @param signature server signature
94 | * @return true if the data and signature match
95 | */
96 | public static boolean verify(PublicKey publicKey, String signedData, String signature) {
97 | byte[] signatureBytes;
98 | try {
99 | signatureBytes = Base64.decode(signature, Base64.DEFAULT);
100 | } catch (IllegalArgumentException e) {
101 | BillingHelper.logWarn(TAG, "Base64 decoding failed.");
102 | return false;
103 | }
104 | try {
105 | Signature signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM);
106 | signatureAlgorithm.initVerify(publicKey);
107 | signatureAlgorithm.update(signedData.getBytes());
108 | if (!signatureAlgorithm.verify(signatureBytes)) {
109 | BillingHelper.logWarn(TAG, "Signature verification failed.");
110 | return false;
111 | }
112 | return true;
113 | } catch (NoSuchAlgorithmException e) {
114 | // "RSA" is guaranteed to be available.
115 | throw new RuntimeException(e);
116 | } catch (InvalidKeyException e) {
117 | BillingHelper.logWarn(TAG, "Invalid key specification.");
118 | } catch (SignatureException e) {
119 | BillingHelper.logWarn(TAG, "Signature exception.");
120 | }
121 | return false;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/github_google_pay/BillingManager.java:
--------------------------------------------------------------------------------
1 | package com.googlepaytest.csx.github_google_pay;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.util.Log;
7 |
8 | import com.anjlab.android.iab.v3.BillingCommunicationException;
9 | import com.anjlab.android.iab.v3.BillingHistoryRecord;
10 | import com.anjlab.android.iab.v3.BillingProcessor;
11 | import com.anjlab.android.iab.v3.Constants;
12 | import com.anjlab.android.iab.v3.SkuDetails;
13 | import com.anjlab.android.iab.v3.TransactionDetails;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | //import com.android.billingclient.api.BillingClient;
19 |
20 | /**
21 | * Date: 2019/8/13
22 | * create by cuishuxiang
23 | * description: 管理类
24 | *
25 | * tp: 1,需要调用初始化方法{@link BillingManager#init(Activity, String, BillingProcessor.IBillingHandler)}
26 | * 2,具体的操作,需要使用到{@link BillingProcessor},这里也提供了{@link BillingManager#getBillingProcessor()}来获取实例
27 | *
28 | * 注意:下面的信息可能会用到
29 | * 1, 处理已取消的订阅
30 | * 调用bp.getSubscriptionTransactionDetails(...)并检查purchaseInfo.purchaseData.autoRenewing标志。
31 | * 订阅取消后,它将设置为False。
32 | * 另请注意,您需要定期调用bp.loadOwnedPurchasesFromGoogle()方法才能更新订阅信息
33 | * 2, 促销代码支持
34 | * 您可以使用促销代码和此库。 促销代码可以在购买对话框或Google Play应用中输入。
35 | * 网址https://play.google.com/redeem?code=YOUR_PROMO_CODE将启动已添加促销代码的Google Play应用。
36 | * 如果您想让用户选择在您的应用中输入促销代码,这可能会派上用场。
37 | *
38 | * 3,错误码查阅参考:{@link Constants}
39 | *
40 | * 参考:https://github.com/anjlab/android-inapp-billing-v3/blob/master/README.md
41 | */
42 | public class BillingManager {
43 | private static final String TAG = "BillingManager";
44 |
45 | private static BillingProcessor bp;
46 |
47 | private static Activity mActivity;
48 | private static String mLicenseKey;
49 |
50 | /**
51 | * 初始化方法,需要先调用这个方法
52 | *
53 | * @param activity
54 | * @param licenseKey
55 | */
56 | public static void init(Activity activity, String licenseKey, BillingProcessor.IBillingHandler iBillingHandler) {
57 | mActivity = activity;
58 | mLicenseKey = licenseKey;
59 |
60 | bp = BillingProcessor.newBillingProcessor(activity, licenseKey, iBillingHandler);
61 | //商户id获得:GooglePayConsole后台--->设置---->付款设置(注意,商家id需要放置到安全的地方)
62 | // bp = BillingProcessor.newBillingProcessor(activity, licenseKey, "商户id", iBillingHandler);
63 | bp.initialize();
64 | }
65 |
66 | public static BillingProcessor getBillingProcessor() {
67 | return bp;
68 | }
69 |
70 | /**
71 | * 在使用之前,最好检查应用内的结算服务可用性。 在某些旧设备或中国设备中,可能会出现Play Market不可用或已弃用且不支持应用内结算的情况。
72 | *
73 | * @return true 可用; false:不可用
74 | */
75 | public static boolean isPayServiceAvailable() {
76 | if (mActivity == null)
77 | throw new IllegalArgumentException("isPayServiceAvailable() error. Call the init method first , Please check it!");
78 | return BillingProcessor.isIabServiceAvailable(mActivity);
79 | }
80 |
81 | /**
82 | * 请注意,调用BillingProcessor.isIabServiceAvailable()(仅检查是否安装了Play Market应用程序)是不够的,
83 | * 因为可能存在返回true但仍然无法付款的情况。
84 | *
85 | * 因此,最好在初始化BillingProcessor之后调用isOneTimePurchaseSupported():
86 | *
87 | * @return 最好true的时候,调用支付流程
88 | */
89 | public static boolean isPurchaseSupported() {
90 | if (bp == null)
91 | throw new IllegalArgumentException("isPurchaseSupported() error . Call the init method first , Please check it!");
92 |
93 | return bp.isOneTimePurchaseSupported();
94 | }
95 |
96 | /**
97 | * 检测是否支持订阅
98 | *
99 | * @return
100 | */
101 | public static boolean isSubscribeUpdateSupported() {
102 | if (bp == null)
103 | throw new IllegalArgumentException("isSubscriptionUpdateSupported() error . Call the init method first , Please check it!");
104 | return bp.isSubscriptionUpdateSupported();
105 | }
106 |
107 | /**
108 | * 购买方法
109 | *
110 | * @param purchaseId id
111 | */
112 | public static boolean purchase(String purchaseId) {
113 | if (mActivity == null || bp == null)
114 | throw new IllegalArgumentException("Call the init method first , Please check it!");
115 |
116 | //检测服务是否可用
117 | if (!isPayServiceAvailable()) {
118 | Log.e(TAG, "purchase: 服务不可用,请检查!");
119 | return false;
120 | }
121 |
122 | //检测是否支持购买
123 | if (!isPurchaseSupported()) {
124 | Log.e(TAG, "purchase: 不支持购买,请检查!");
125 | return false;
126 | }
127 |
128 | return bp.purchase(mActivity, purchaseId);
129 | }
130 |
131 | /**
132 | * 暂时不知道 "developerPayload" 什么意思
133 | *
134 | * 重要信息:当您提供有效负载时,库内部会为您的负载预先添加一个字符串。
135 | * 对于订阅,它前缀为“subs:\ :”,
136 | * 对于产品,它预先添加“inapp:\ :\ :”。
137 | *
138 | * 了解您是否对成功购买后从Google Play返回的有效内容进行了任何验证,这一点非常重要。
139 | *
140 | * @param productId
141 | * @param developerPayload
142 | * @param extraParams 放置参数如下所示
143 | * @return
144 | */
145 | public static boolean purchase(String productId, String developerPayload, Bundle extraParams) {
146 | /**
147 | * Bundle extraParams = new Bundle()
148 | * extraParams.putString("accountId", "MY_ACCOUNT_ID");
149 | */
150 | if (mActivity == null || bp == null)
151 | throw new IllegalArgumentException("purchase() error . Call the init method first , Please check it!");
152 | //检测服务是否可用
153 | if (!isPayServiceAvailable()) {
154 | Log.e(TAG, "purchase: 服务不可用,请检查!");
155 | return false;
156 | }
157 |
158 | //检测是否支持购买
159 | if (!isPurchaseSupported()) {
160 | Log.e(TAG, "purchase: 不支持购买,请检查!");
161 | return false;
162 | }
163 |
164 |
165 | return bp.subscribe(mActivity, productId, developerPayload, extraParams);
166 | }
167 |
168 | /**
169 | * 订阅方法
170 | *
171 | * @param purchaseId
172 | */
173 | public static boolean subscribe(String purchaseId) {
174 | if (mActivity == null || bp == null)
175 | throw new IllegalArgumentException("subscribe() error . Call the init method first , Please check it!");
176 |
177 | //检测服务是否可用
178 | if (!isPayServiceAvailable()) {
179 | Log.e(TAG, "subscribe: 服务不可用,请检查!");
180 | return false;
181 | }
182 |
183 | //检测是否支持购买
184 | if(!isPurchaseSupported()){
185 | Log.e(TAG, "subscribe: 不支持购买,请检查!");
186 | return false;
187 | }
188 |
189 | return bp.subscribe(mActivity, purchaseId);
190 | //注意:还有一个 三个参数的负载,暂时不知道什么意思
191 | // bp.subscribe(mActivity, purchaseId, "");
192 | }
193 |
194 |
195 | /**
196 | * 订阅方法 (developerPayload 负载,暂时不知道什么意思)
197 | *
198 | * 重要信息:当您提供有效负载时,库内部会为您的负载预先添加一个字符串。
199 | * 对于订阅,它前缀为“subs:\ :”,
200 | * 对于产品,它预先添加“inapp:\ :\ :”。
201 | *
202 | * @param purchaseId
203 | * @param developerPayload
204 | * @param extraParams
205 | * @return
206 | */
207 | public static boolean subscribe(String purchaseId, String developerPayload, Bundle extraParams) {
208 | /**
209 | * Bundle extraParams = new Bundle()
210 | * extraParams.putString("accountId", "MY_ACCOUNT_ID");
211 | */
212 | if (mActivity == null || bp == null)
213 | throw new IllegalArgumentException("subscribe() error . Call the init method first , Please check it!");
214 |
215 | //检测服务是否可用
216 | if (!isPayServiceAvailable()) {
217 | Log.e(TAG, "subscribe: 服务不可用,请检查!");
218 | return false;
219 | }
220 |
221 | //检测是否支持购买
222 | if(!isPurchaseSupported()){
223 | Log.e(TAG, "subscribe: 不支持购买,请检查!");
224 | return false;
225 | }
226 |
227 | return bp.subscribe(mActivity, purchaseId, developerPayload, extraParams);
228 | }
229 |
230 | /**
231 | * 消费购买的商品
232 | *
233 | * 注:您可以调用该方法,随时消费购买并允许多次购买相同的产品
234 | * @param purchaseId
235 | * @return
236 | */
237 | public static boolean consumePurchase(String purchaseId) {
238 | if ( bp == null)
239 | throw new IllegalArgumentException("consumePurchase() error . Call the init method first , Please check it!");
240 |
241 | return bp.consumePurchase(purchaseId);
242 | }
243 |
244 | /**
245 | * 商品详情
246 | * @param productId
247 | * @return
248 | */
249 | public static SkuDetails getSkuDetail(String productId) {
250 | if ( bp == null)
251 | throw new IllegalArgumentException("getSkuDetail() error . Call the init method first , Please check it!");
252 |
253 |
254 | return bp.getPurchaseListingDetails(productId);
255 | }
256 |
257 | public static List getSkuDetailList(ArrayList productIdList) {
258 | if ( bp == null)
259 | throw new IllegalArgumentException("getSkuDetail() error . Call the init method first , Please check it!");
260 |
261 |
262 | return bp.getPurchaseListingDetails(productIdList);
263 | }
264 |
265 | /**
266 | * 获得交易详情(商品)
267 | * @param productId
268 | * @return
269 | */
270 | public static TransactionDetails getPurchaseTransactionDetails(String productId) {
271 | if ( bp == null)
272 | throw new IllegalArgumentException("getPurchaseTransactionDetails() error . Call the init method first , Please check it!");
273 |
274 | return bp.getPurchaseTransactionDetails(productId);
275 | }
276 | /**
277 | * 获得交易详情(订阅)
278 | * @param productId
279 | * @return
280 | */
281 | public static TransactionDetails getSubscribeTransactionDetails(String productId) {
282 | if ( bp == null)
283 | throw new IllegalArgumentException("getSubscribeTransactionDetails() error . Call the init method first , Please check it!");
284 |
285 | return bp.getSubscriptionTransactionDetails(productId);
286 | }
287 |
288 | /**
289 | * 获得 应用内商品 购买历史记录
290 | * @param extraParams
291 | * @return
292 | */
293 | public static List getInappHistory(Bundle extraParams) {
294 | if ( bp == null)
295 | throw new IllegalArgumentException("getInappHistory() error . Call the init method first , Please check it!");
296 | try {
297 |
298 | //请注意,此API需要版本6或更高版本的结算API,因此您应该事先检查是否支持它:
299 | if (!bp.isRequestBillingHistorySupported(Constants.PRODUCT_TYPE_MANAGED)) {
300 | Log.e(TAG, "getInappHistory: isRequestBillingHistorySupported 版本不支持:");
301 | return null;
302 | }
303 |
304 | return bp.getPurchaseHistory(Constants.PRODUCT_TYPE_MANAGED, extraParams);
305 |
306 | } catch (BillingCommunicationException e) {
307 | e.printStackTrace();
308 | Log.e(TAG, "getInappHistory: 异常:" + e);
309 | return null;
310 | }
311 | }
312 |
313 |
314 | /**
315 | * 获得 订阅商品 购买历史记录
316 | * @param extraParams
317 | * @return
318 | */
319 | public static List getSubscribeHistory(Bundle extraParams) {
320 | if ( bp == null)
321 | throw new IllegalArgumentException("getSubscribeHistory() error . Call the init method first , Please check it!");
322 |
323 | try {
324 | //请注意,此API需要版本6或更高版本的结算API,因此您应该事先检查是否支持它:
325 | if (!bp.isRequestBillingHistorySupported(Constants.PRODUCT_TYPE_SUBSCRIPTION)) {
326 | Log.e(TAG, "getInappHistory: getSubscribeHistory 版本不支持:");
327 | return null;
328 | }
329 |
330 | return bp.getPurchaseHistory(Constants.PRODUCT_TYPE_SUBSCRIPTION, extraParams);
331 |
332 | } catch (BillingCommunicationException e) {
333 | e.printStackTrace();
334 | Log.e(TAG, "getSubscribeHistory: 异常:" + e);
335 | return null;
336 | }
337 | }
338 |
339 | /**
340 | * 恢复购买和订阅
341 | * @return
342 | */
343 | public static boolean restore() {
344 | if ( bp == null)
345 | throw new IllegalArgumentException("restore() error . Call the init method first , Please check it!");
346 | return bp.loadOwnedPurchasesFromGoogle();
347 | }
348 |
349 | /**
350 | * 检查交易有效性 (调用该方法,在初始化 BillingProcessor 需要设置商家id)
351 | * @param transactionDetails
352 | * @return
353 | *
354 | *
355 | * 安全性 参考:https://developer.android.com/google/play/billing/billing_best_practices.html#validating-purchase-device
356 | */
357 | public static boolean isValid(TransactionDetails transactionDetails) {
358 | if ( bp == null)
359 | throw new IllegalArgumentException("isValid() error . Call the init method first , Please check it!");
360 |
361 | return bp.isValidTransactionDetails(transactionDetails);
362 | }
363 |
364 |
365 | /**
366 | * 用于接收回调,在Activity可以这样调用,示例:
367 | * @Override
368 | * protected void onActivityResult(int requestCode, int resultCode, Intent data) {
369 | * if (!bp.handleActivityResult(requestCode, resultCode, data))
370 | * super.onActivityResult(requestCode, resultCode, data);
371 | * }
372 | */
373 | public static boolean handleActivityResult(int requestCode, int resultCode, Intent data){
374 | return bp.handleActivityResult(requestCode, resultCode, data);
375 | }
376 |
377 | /**
378 | * 在Activity onDestroy 调用该方法
379 | */
380 | public static void release() {
381 | if (bp != null) {
382 | bp.release();
383 | bp = null;
384 | }
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/native_google_pay/NativeBillingClientManager.java:
--------------------------------------------------------------------------------
1 | package com.googlepaytest.csx.native_google_pay;
2 |
3 | import android.app.Activity;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.util.Log;
7 |
8 | import com.android.billingclient.api.AcknowledgePurchaseParams;
9 | import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
10 | import com.android.billingclient.api.BillingClient;
11 | import com.android.billingclient.api.BillingClientStateListener;
12 | import com.android.billingclient.api.BillingFlowParams;
13 | import com.android.billingclient.api.BillingResult;
14 | import com.android.billingclient.api.ConsumeParams;
15 | import com.android.billingclient.api.ConsumeResponseListener;
16 | import com.android.billingclient.api.Purchase;
17 | import com.android.billingclient.api.PurchaseHistoryRecord;
18 | import com.android.billingclient.api.PurchaseHistoryResponseListener;
19 | import com.android.billingclient.api.PurchasesUpdatedListener;
20 | import com.android.billingclient.api.SkuDetails;
21 | import com.android.billingclient.api.SkuDetailsParams;
22 | import com.android.billingclient.api.SkuDetailsResponseListener;
23 |
24 | import java.util.ArrayList;
25 | import java.util.Arrays;
26 | import java.util.List;
27 |
28 | /**
29 | * Date: 2019/8/13
30 | * create by cuishuxiang
31 | * description:
32 | *
33 | * Google Pay 返回code:{@link BillingClient.BillingResponseCode}
34 | *
35 | * Google Pay 商品信息:{@link SkuDetails}
36 | * 1,getPrice() 向用户显示折扣价,又使用 getOriginalPrice() 显示商品的原价。
37 | * 2,getOriginalPriceAmountMicros() - 返回未设置格式的折扣前 SKU 原价。
38 | * 3,getOriginalPrice() - 返回采用其他货币格式设置的原价。
39 | *
40 | * 商品类型 : {@link BillingClient.FeatureType}
41 | *
42 | * 购买注意事项:如果商品购买成功,系统还会生成购买令牌,它是一个唯一标识符,表示用户及其所购应用内商品的商品 ID。
43 | * 您的应用可以在用户设备上存储购买令牌,理想情况下,也可以将购买令牌传递到安全的后端服务器,
44 | * 以便用于验证购买交易及防范欺诈行为。购买令牌对于一次性商品的每笔购买交易和每个奖励产品都是唯一的。
45 | * 不过,由于订阅是一次性购买并按固定的结算周期自动续订,因此订阅的购买令牌在各个结算周期内保持不变。
46 | *
47 | * 用户还会收到包含交易收据的电子邮件,其中包含订单 ID 或交易的唯一 ID。用户每次购买一次性商品时,
48 | * 都会收到包含唯一订单 ID 的电子邮件。此外,用户最初购买订阅时以及后续定期自动续订时,也会收到这样的电子邮件。
49 | * 您可以在 Google Play 管理中心内使用订单 ID 来管理退款。有关详情,请参阅查看应用的订单和订阅及办理退款。
50 | */
51 | public class NativeBillingClientManager {
52 | private static final String TAG = NativeBillingClientManager.class.getName();
53 |
54 | private static BillingClient billingClient;
55 | private static Activity mActivity;
56 |
57 | //注意,每次请求前最好判断是否已经连接
58 | private static boolean mIsServiceConnected = false;
59 |
60 | /**
61 | * @param activity
62 | * @param listener 这里自己写了一个回调,处理常见的问题
63 | *
64 | * 在调用购买方法后,Google Play 会调用 {@link PurchasesUpdatedListener#onPurchasesUpdated(BillingResult, List)}方法,
65 | * 将购买操作的结果传递给实现 PurchasesUpdatedListener 接口的监听器
66 | */
67 | public static void init(Activity activity, @NonNull final OnPurchaseCallBack listener) {
68 | billingClient = BillingClient.newBuilder(activity).setListener(new PurchasesUpdatedListener() {
69 | @Override
70 | public void onPurchasesUpdated(BillingResult billingResult, @Nullable List purchases) {
71 | Log.d(TAG, "onPurchasesUpdated: ");
72 | if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
73 | /**
74 | * 购买成功
75 | */
76 | listener.onPaySuccess(purchases);
77 | } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
78 | // Handle an error caused by a user cancelling the purchase flow.
79 | //处理由用户取消购买流程引起的错误
80 | listener.onUserCancel();
81 | } else {
82 | // Handle any other error codes.
83 | listener.responseCode(billingResult.getResponseCode());
84 | }
85 | }
86 | }).enablePendingPurchases().build();
87 |
88 |
89 | mActivity = activity;
90 |
91 | //连接服务
92 | connectionService();
93 | }
94 |
95 | /**
96 | * 连接服务
97 | */
98 | private static void connectionService() {
99 | if (billingClient == null)
100 | throw new IllegalArgumentException("Please call init(); first!");
101 |
102 | billingClient.startConnection(new BillingClientStateListener() {
103 | @Override
104 | public void onBillingSetupFinished(BillingResult billingResult) {
105 | if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
106 | // The BillingClient is ready. You can query purchases here.
107 | mIsServiceConnected = true;
108 | }else{
109 | Log.d(TAG, "connectionService - > onBillingSetupFinished: 连接失败code = " + billingResult.getResponseCode());
110 | }
111 | }
112 |
113 | @Override
114 | public void onBillingServiceDisconnected() {
115 | // Try to restart the connection on the next request to
116 | // Google Play by calling the startConnection() method.
117 | mIsServiceConnected = false;
118 | }
119 | });
120 | }
121 |
122 |
123 | /**
124 | * 要异步查询商品详情,应用会使用您在 Google Play 管理中心内配置商品时定义的商品 ID。
125 | *
126 | * @param itemType 针对一次性商品或奖励产品:SkuType.INAPP ; 订阅:SkuType.SUBS
127 | * @param skuList 指定产品 ID 字符串列表
128 | * @param listener
129 | */
130 | private static void querySkuDetailsAsync(@BillingClient.SkuType String itemType,
131 | SkuDetailsResponseListener listener, @NonNull String... skuList) {
132 | if (billingClient == null)
133 | throw new IllegalArgumentException("querySkuDetailsAsync(); error . Please call init(); first!");
134 |
135 | //判断是否连接
136 | if (!mIsServiceConnected) connectionService();
137 |
138 | SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
139 | .setType(itemType)
140 | .setSkusList(Arrays.asList(skuList))//转换成数组
141 | .build();
142 | billingClient.querySkuDetailsAsync(skuDetailsParams, listener);
143 | }
144 |
145 | /**
146 | * 查询单个应用内商品,详情
147 | * @param sku 商品id
148 | * @param skuDetailsResponseListener
149 | */
150 | public static void queryInappDeatil(String sku, SkuDetailsResponseListener skuDetailsResponseListener) {
151 | querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuDetailsResponseListener, sku);
152 | }
153 | /**
154 | * 查询单个 "订阅商品" ,详情
155 | * @param sku 商品id
156 | * @param skuDetailsResponseListener
157 | */
158 | public static void querySubDeatil(String sku, SkuDetailsResponseListener skuDetailsResponseListener) {
159 | querySkuDetailsAsync(BillingClient.SkuType.SUBS, skuDetailsResponseListener, sku);
160 | }
161 |
162 |
163 | /**
164 | * 购买 Google Inapp 商品
165 | * @param sku 商品id
166 | * @return
167 | */
168 | public static void startInAppPurchase(final String sku) {
169 | if (billingClient == null)
170 | throw new IllegalArgumentException("querySkuDetailsAsync(); error . Please call init(); first!");
171 |
172 | //判断是否连接
173 | if (!mIsServiceConnected) connectionService();
174 |
175 | SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
176 | .setType(BillingClient.SkuType.INAPP)
177 | .setSkusList(Arrays.asList(sku))//转换成数组
178 | .build();
179 | //先查询,后调起支付
180 | billingClient.querySkuDetailsAsync(skuDetailsParams, new SkuDetailsResponseListener() {
181 | @Override
182 | public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) {
183 | Log.d(TAG, "startInAppPurchase - > onSkuDetailsResponse: " + billingResult.toString()+"\n");
184 | if (skuDetailsList != null && skuDetailsList.size() > 0) {
185 | Log.d(TAG, "startInAppPurchase - > onSkuDetailsResponse: " + billingResult.toString() + " , " + skuDetailsList.toString());
186 |
187 | SkuDetails skuDetails = skuDetailsList.get(0);
188 | //调起Google支付
189 | BillingResult payResult = launchBillingFlow(skuDetails);
190 | Log.d(TAG, "调起Google支付 : onSkuDetailsResponse: " + payResult.toString());
191 | }
192 | }
193 | });
194 | }
195 |
196 | /**
197 | * 购买 Google Sub 订阅 商品
198 | * @param sku 商品id
199 | * @return
200 | */
201 | public static void startSubPurchase(final String sku) {
202 | if (billingClient == null)
203 | throw new IllegalArgumentException("startSubPurchase(); error . Please call init(); first!");
204 |
205 | //判断是否连接
206 | if (!mIsServiceConnected) connectionService();
207 |
208 | SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
209 | .setType(BillingClient.SkuType.SUBS)
210 | .setSkusList(Arrays.asList(sku))//转换成数组
211 | .build();
212 | //先查询,后调起支付
213 | billingClient.querySkuDetailsAsync(skuDetailsParams, new SkuDetailsResponseListener() {
214 | @Override
215 | public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) {
216 | Log.d(TAG, "startSubPurchase - > onSkuDetailsResponse: " + billingResult.toString() + " , " + skuDetailsList.toString());
217 | Log.d(TAG, "startSubPurchase - > onSkuDetailsResponse: skuDetailsList.size() = " + skuDetailsList.size());
218 |
219 | if (skuDetailsList != null && skuDetailsList.size() > 0) {
220 | SkuDetails skuDetails = skuDetailsList.get(0);
221 | //调起Google支付
222 | BillingResult payResult = launchBillingFlow(skuDetails);
223 | Log.d(TAG, "调起Google支付 : onSkuDetailsResponse: " + payResult.toString());
224 | }
225 | }
226 | });
227 | }
228 |
229 | /**
230 | * 启动应用内商品的购买
231 | *
232 | * @param skuDetails 这里商品详情,从 querySkuDetailsAsync(); 查询
233 | * @return 方法会返回 BillingClient.BillingResponse 中列出的几个响应代码之一
234 | */
235 | private static BillingResult launchBillingFlow(@NonNull SkuDetails skuDetails) {
236 | if (mActivity == null || billingClient == null)
237 | throw new IllegalArgumentException("launchBillingFlow(); error . Please call init(); first!");
238 |
239 | //判断是否连接
240 | if (!mIsServiceConnected) connectionService();
241 |
242 | BillingFlowParams flowParams = BillingFlowParams.newBuilder()
243 | .setSkuDetails(skuDetails)
244 | .build();
245 |
246 | return billingClient.launchBillingFlow(mActivity, flowParams);
247 | }
248 |
249 | /**
250 | * Android 手机安装的 Google Play 商店应用可能是旧版的,不支持订阅等商品类型。
251 | * 因此,在应用进入结算流程之前,请调用 isFeatureSupported()
252 | * 以检查设备是否支持您要销售的商品。如需查看商品类型的列表,请参阅 BillingClient.FeatureType。
253 | *
254 | * 注意:需要 连接 才能
255 | *
256 | * @param feature
257 | * @return
258 | */
259 | public static boolean isFeatureSupported(@BillingClient.FeatureType String feature) {
260 | if (billingClient == null)
261 | throw new IllegalArgumentException("isFeatureSupported(); error . Please call init(); first!");
262 |
263 | //判断是否连接
264 | if (!mIsServiceConnected) connectionService();
265 |
266 | BillingResult result = billingClient.isFeatureSupported(feature);
267 |
268 | if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
269 | return true;
270 | } else {
271 | Log.e(TAG, "isFeatureSupported: isFeatureSupported = false , errorMsg : " + result.getDebugMessage());
272 | return false;
273 | }
274 | }
275 |
276 | /**
277 | * 服务是否连接
278 | * @return
279 | */
280 | public static boolean getServiceConnected() {
281 | return mIsServiceConnected;
282 | }
283 |
284 | /**
285 | * 确认一次性商品交易
286 | *
287 | * 警告! 所有购买都需要确认。 未能确认购买将导致购买退款。 对于一次性产品,请确保使用此方法作为隐式确认,
288 | * 或者您可以通过{@link BillingClient#acknowledgePurchase(AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)} 明确确认购买。
289 | * 对于订阅,请使用{@link #acknowledgePurchase)。
290 | * 有关详细信息,请参阅https://developer.android.com/google/play/billing/billing_library_overview#acknowledge。
291 | */
292 | public static void consumeAsync(@NonNull Purchase purchase, ConsumeResponseListener listener) {
293 | if (billingClient == null)
294 | throw new IllegalArgumentException("consumeAsync(); error . Please call init(); first!");
295 |
296 | //判断是否连接
297 | if (!mIsServiceConnected) connectionService();
298 |
299 | //Purchase 对象包含 isAcknowledged() 方法,该方法会指示购买交易是否已得到确认
300 | if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
301 | && !purchase.isAcknowledged()) {
302 |
303 | ConsumeParams consumeParams = ConsumeParams
304 | .newBuilder()
305 | // .setDeveloperPayload(purchase.getDeveloperPayload())//指定开发人员有效负载与购买信息一起发回。
306 | .setPurchaseToken(purchase.getPurchaseToken())//指定标识要使用的购买的标记。
307 | .build();
308 | billingClient.consumeAsync(consumeParams, listener);
309 | }
310 |
311 | }
312 |
313 | /**
314 | * 确认“订阅商品”交易
315 | *
316 | *
317 | * 消费“费消耗品” 还可以使用服务器 API 中新增的 acknowledge() 方法。
318 | */
319 | public static void acknowledgePurchase(@NonNull Purchase purchase, AcknowledgePurchaseResponseListener listener) {
320 | if (billingClient == null)
321 | throw new IllegalArgumentException("acknowledgePurchase(); error . Please call init(); first!");
322 |
323 | //判断是否连接
324 | if (!mIsServiceConnected) connectionService();
325 |
326 | //Purchase 对象包含 isAcknowledged() 方法,该方法会指示购买交易是否已得到确认
327 | if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
328 | && !purchase.isAcknowledged()) {
329 | AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams
330 | .newBuilder()
331 | // .setDeveloperPayload(purchase.getDeveloperPayload())//这个参数不确定是否需要
332 | .setPurchaseToken(purchase.getPurchaseToken())
333 | .build();
334 |
335 | billingClient.acknowledgePurchase(acknowledgePurchaseParams, listener);
336 | }
337 | }
338 |
339 | /**
340 | * 用户通过应用发起的购买交易的相关信息(使用 Google Play 商店应用的缓存)
341 | *
342 | * @param type 购买类型(SkuType.INAPP 或 SkuType.SUBS)
343 | * @return 注意:
344 | * 在每次启动您的应用时都调用 queryPurchases(),
345 | * 以便您可以恢复用户自应用上次停止以来发起的任何购买交易。
346 | * 在 onResume() 方法中调用 queryPurchases(),
347 | * 因为当您的应用在后台时,用户可能会发起购买交易(例如,在 Google Play 商店应用中兑换促销代码)。
348 | *
349 | * queryPurchases() 方法使用 Google Play 商店应用的缓存,而不发起网络请求。
350 | *
351 | * 如果您需要查看用户对每个商品 ID 发起的最近一笔购买交易,您可以使用 queryPurchaseHistoryAsync()
352 | */
353 | public static List queryPurchases(@BillingClient.SkuType String type) {
354 | if (billingClient == null)
355 | throw new IllegalArgumentException("acknowledgePurchase(); error . Please call init(); first!");
356 |
357 | Purchase.PurchasesResult result = billingClient.queryPurchases(type);
358 |
359 | if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) {
360 |
361 | // BillingResult billingResult = result.getBillingResult();
362 |
363 | return result.getPurchasesList();
364 | }
365 |
366 | return null;
367 | }
368 |
369 | /**
370 | * 查询最近的购买交易(网络)
371 | *
372 | * @param type
373 | * @param listener 注意: 该方法走网络
374 | * 如果使用 queryPurchaseHistoryAsync(),您也可以将其与刷新按钮结合使用,使用户能更新其购买交易列表。
375 | */
376 | public static void queryPurchaseHistoryAsync(@BillingClient.SkuType String type, PurchaseHistoryResponseListener listener) {
377 |
378 | if (billingClient == null)
379 | throw new IllegalArgumentException("acknowledgePurchase(); error . Please call init(); first!");
380 |
381 | billingClient.queryPurchaseHistoryAsync(type, listener);
382 | }
383 |
384 | /**
385 | * 断开链接(一般在 onDestroy 调用)
386 | */
387 | public static void endConnection() {
388 | if (billingClient == null) return;
389 |
390 | billingClient.endConnection();
391 | billingClient = null;
392 | }
393 |
394 | }
395 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/native_google_pay/NativePayTestActivity.java:
--------------------------------------------------------------------------------
1 | package com.googlepaytest.csx.native_google_pay;
2 |
3 | import android.app.Activity;
4 | import android.content.ComponentName;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.os.UserHandle;
9 | import android.support.annotation.Nullable;
10 | import android.support.v7.widget.LinearLayoutManager;
11 | import android.support.v7.widget.RecyclerView;
12 | import android.text.TextUtils;
13 | import android.util.Log;
14 | import android.view.View;
15 | import android.widget.Button;
16 | import android.widget.TextView;
17 | import android.widget.Toast;
18 |
19 | import com.android.billingclient.api.BillingClient;
20 | import com.android.billingclient.api.BillingResult;
21 | import com.android.billingclient.api.ConsumeResponseListener;
22 | import com.android.billingclient.api.Purchase;
23 | import com.android.billingclient.api.PurchaseHistoryRecord;
24 | import com.android.billingclient.api.PurchaseHistoryResponseListener;
25 | import com.android.billingclient.api.SkuDetailsResponseListener;
26 | import com.chad.library.adapter.base.BaseQuickAdapter;
27 | import com.google.gson.Gson;
28 | import com.googlepaytest.csx.github_google_pay.BillingManager;
29 | import com.googlepaytest.csx.github_google_pay.GoogleAccsessTokenBean;
30 | import com.googlepaytest.csx.github_google_pay.GooglePayPurchaseBean;
31 | import com.googlepaytest.csx.github_google_pay.PurchaseAdapter;
32 | import com.googlepaytest.csx.R;
33 | import com.zhy.http.okhttp.OkHttpUtils;
34 | import com.zhy.http.okhttp.callback.StringCallback;
35 |
36 | import java.util.List;
37 |
38 | import okhttp3.Call;
39 |
40 |
41 | /**
42 | * Date: 2019/8/14
43 | * create by cuishuxiang
44 | * description:
45 | *
46 | * Api参考:https://developers.google.com/android-publisher/api-ref/purchases/products/get?hl=zh-cn
47 | *
48 | * 使用 三方库,进行Google Pay 相关操作
49 | * ·
50 | */
51 | public class NativePayTestActivity extends Activity implements View.OnClickListener {
52 | private static final String TAG = "PayTestActivity";
53 |
54 | private Button btn_get_token;
55 | private Button btn_start_pay;
56 | private Button btn_query_all;
57 | private Button btn_query_info;
58 | private Button btn_consume;
59 | private Button btn_history;
60 | private Button btn_purchase_state;
61 | private Button btn_restore_sub;
62 | private Button btn_clean;
63 | private TextView tv_hint;
64 | private RecyclerView mRv;
65 | private PurchaseAdapter mAdapter;
66 | private TextView tv_title;
67 | private Gson gson = new Gson();
68 |
69 | private boolean isServerAvaliable = false;
70 |
71 | //校验需要用到这2个参数
72 | private String mProductId;
73 | private String mPurchaseToken;
74 | private String mDeveloperPayload;
75 | private String code = "4/qwGJ6-mtO5uQWBHC3WVAGeNHWqBX2t0gCpGwMuQBKtHkkdfxrxZba6M";//9.9号生成
76 | private String accessToken;
77 | private String refreshToken = "1/4JEiUYlT4nA1edcMHvV1qRMS9-O7NLjrbi7FtZ16aPE";//9.9号生成
78 | private String client_secret = "GErDLKY0ipw8EGJcouzH_jbu";
79 | private String client_id = "790140086566-9q9lfiir624fl248l7cshcqp3dicitl4.apps.googleusercontent.com";
80 | private String redirect_uris = "urn:ietf:wg:oauth:2.0:oob";
81 | private String purchaseId;
82 | private GooglePayPurchaseBean.InappproductBean curInappProductBean;//当前选择商品bean
83 | private Purchase curPurchase;
84 |
85 | @Override
86 | protected void onCreate(@Nullable Bundle savedInstanceState) {
87 | super.onCreate(savedInstanceState);
88 | setContentView(R.layout.activity_native_test_pay);
89 |
90 | initView();
91 |
92 | //1, 初始化 支付
93 | NativeBillingClientManager.init(this, new OnPurchaseCallBack() {
94 | @Override
95 | public void responseCode(int code) {
96 | Log.d(TAG, "responseCode: code = " + code);
97 | }
98 |
99 | @Override
100 | public void onPaySuccess(List purchaseList) {
101 | Log.d(TAG, "onPaySuccess: " + purchaseList.toString());
102 | if (purchaseList != null && purchaseList.size() != 0) {
103 | curPurchase = purchaseList.get(0);
104 | mProductId = purchaseList.get(0).getSku();
105 | mPurchaseToken = purchaseList.get(0).getPurchaseToken();
106 | }
107 | }
108 |
109 | @Override
110 | public void onUserCancel() {
111 | Log.d(TAG, "onUserCancel: ");
112 | }
113 | });
114 |
115 |
116 | Log.d(TAG, "onCreate: ");
117 |
118 | //判断Pay服务是否可用(连接是异步,所以推迟1s才去判断)
119 | new Handler().postDelayed(new Runnable() {
120 | @Override
121 | public void run() {
122 | isServerAvaliable = NativeBillingClientManager.getServiceConnected();
123 |
124 | //服务可用,先调用一次,获取token
125 | if ((isServerAvaliable)) {
126 | tv_hint.setText(tv_hint.getText() + "------服务链接成功,开始 获取token-------- ");
127 | getToken();
128 | }
129 | }
130 | }, 1000);
131 |
132 | }
133 |
134 | private void initView() {
135 | btn_get_token = findViewById(R.id.btn_get_token);
136 | btn_get_token.setOnClickListener(this);
137 | btn_restore_sub = findViewById(R.id.btn_restore_sub);
138 | btn_restore_sub.setOnClickListener(this);
139 | btn_query_all = findViewById(R.id.btn_query_all);
140 | btn_query_all.setOnClickListener(this);
141 | btn_query_info = findViewById(R.id.btn_query_info);
142 | btn_query_info.setOnClickListener(this);
143 | btn_start_pay = findViewById(R.id.btn_start_pay);
144 | btn_start_pay.setOnClickListener(this);
145 | btn_consume = findViewById(R.id.btn_consume);
146 | btn_consume.setOnClickListener(this);
147 | btn_history = findViewById(R.id.btn_history);
148 | btn_history.setOnClickListener(this);
149 | btn_clean = findViewById(R.id.btn_clean);
150 | btn_clean.setOnClickListener(this);
151 | btn_purchase_state = findViewById(R.id.btn_purchase_state);
152 | tv_title = findViewById(R.id.tv_title);
153 | btn_purchase_state.setOnClickListener(this);
154 | tv_hint = findViewById(R.id.tv_hint);
155 |
156 | tv_title.setText("原生Billing,实现Google支付");
157 |
158 | mRv = findViewById(R.id.rv);
159 | mRv.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
160 | mAdapter = new PurchaseAdapter();
161 | mRv.setAdapter(mAdapter);
162 |
163 | mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
164 | @Override
165 | public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
166 | mAdapter.setChoosePosition(position);
167 | mAdapter.notifyDataSetChanged();
168 | purchaseId = mAdapter.getItem(position).getSku();
169 | curInappProductBean = mAdapter.getItem(position);
170 | }
171 | });
172 | }
173 |
174 | @Override
175 | public void onClick(View v) {
176 | switch (v.getId()) {
177 | case R.id.btn_get_token:
178 | tv_hint.setText(tv_hint.getText() + "\n\n------获取Token开始-------- ");
179 | getToken();
180 | break;
181 | case R.id.btn_query_all:
182 | tv_hint.setText(tv_hint.getText() + "\n\n------查询所有商品开始-------- ");
183 | getAllPurchase();
184 | break;
185 | case R.id.btn_query_info:
186 | if (TextUtils.isEmpty(mProductId)) {
187 | Toast.makeText(this, "请先选择一个商品!", Toast.LENGTH_SHORT).show();
188 | return;
189 | }
190 | tv_hint.setText(tv_hint.getText() + "\n\n点击查询商品详情: ");
191 |
192 | NativeBillingClientManager.queryInappDeatil(purchaseId, new SkuDetailsResponseListener() {
193 | @Override
194 | public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) {
195 | if (skuDetailsList!=null&&skuDetailsList.size()!=0)
196 | tv_hint.setText(tv_hint.getText() + "\n\n查询商品详情为: " + skuDetailsList.get(0).toString());
197 | }
198 | });
199 | break;
200 | case R.id.btn_start_pay:
201 | tv_hint.setText(tv_hint.getText() + "\n\n点击开始支付..... ");
202 | startPay();
203 | break;
204 | case R.id.btn_purchase_state://校验
205 | tv_hint.setText(tv_hint.getText() + "\n\n点击支付校验..... ");
206 | checkPurchase();
207 | break;
208 | case R.id.btn_consume:
209 | if (curPurchase==null) {
210 | Toast.makeText(this, "消费不能为空,请先选择一个商品进行购买!", Toast.LENGTH_SHORT).show();
211 | return;
212 | }
213 |
214 | tv_hint.setText(tv_hint.getText() + "\n\n 点击消费: " + purchaseId);
215 |
216 | NativeBillingClientManager.consumeAsync(curPurchase, new ConsumeResponseListener() {
217 | @Override
218 | public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
219 | if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
220 | showToast("消费成功!");
221 | } else {
222 | showToast("消费失败!");
223 | }
224 | }
225 | });
226 |
227 | break;
228 | case R.id.btn_history:
229 | tv_hint.setText(tv_hint.getText() + "\n\n 点击获取购买记录----- ");
230 |
231 | NativeBillingClientManager.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, new PurchaseHistoryResponseListener() {
232 | @Override
233 | public void onPurchaseHistoryResponse(BillingResult billingResult, List purchaseHistoryRecordList) {
234 | for (int i = 0; i < purchaseHistoryRecordList.size(); i++) {
235 | tv_hint.setText(tv_hint.getText() + "\n\n购买记录: " + purchaseHistoryRecordList.get(i).toString());
236 | }
237 | }
238 | });
239 | break;
240 | case R.id.btn_clean:
241 | tv_hint.setText("");
242 | break;
243 | case R.id.btn_restore_sub:
244 | // boolean restore = BillingManager.restore();
245 | // Log.d(TAG, "onClick: ");
246 | break;
247 | }
248 |
249 | }
250 |
251 | /**
252 | * 调起google支付
253 | */
254 | private void startPay() {
255 | if (TextUtils.isEmpty(purchaseId)) {
256 | Toast.makeText(this, "请从所有的商品中,选择一个商品进行支付!", Toast.LENGTH_SHORT).show();
257 | return;
258 | }
259 |
260 | if (curInappProductBean != null) {
261 | if (curInappProductBean.getPurchaseType().contains("sub")) {
262 | //调用 "购买订阅"
263 | NativeBillingClientManager.startSubPurchase(purchaseId);
264 | } else {
265 | NativeBillingClientManager.startInAppPurchase(purchaseId);
266 | }
267 | }
268 | }
269 |
270 | /**
271 | * 查询所有商品(Google后台)
272 | *
273 | * GET https://www.googleapis.com/androidpublisher/v3/applications/packageName/inappproducts
274 | *
275 | * 参考:https://developers.google.com/android-publisher/api-ref/inappproducts/list?hl=zh-cn
276 | */
277 | private void getAllPurchase() {
278 | /**
279 | * 调用Google后台,查询所有的商品
280 | * 需要:access_token 参数
281 | */
282 | if (TextUtils.isEmpty(accessToken)) {
283 | Toast.makeText(this, "请先调用获取Token接口!", Toast.LENGTH_SHORT).show();
284 | return;
285 | }
286 |
287 | String queryAllPurchase = "https://www.googleapis.com/androidpublisher/v3/applications/" + getPackageName() + "/inappproducts";
288 | OkHttpUtils.get()
289 | .url(queryAllPurchase)
290 | .addParams("access_token", accessToken)
291 | .build()
292 | .execute(new StringCallback() {
293 | @Override
294 | public void onError(Call call, Exception e, int id) {
295 | Log.d(TAG, "onError: getAllPurchase()" + e);
296 | tv_hint.setText(tv_hint.getText() + "\n 获取所有商品 失败= " + e.getMessage());
297 | }
298 |
299 | @Override
300 | public void onResponse(String response, int id) {
301 | Log.d(TAG, "onResponse: getAllPurchase() = " + response);
302 | tv_hint.setText(tv_hint.getText() + "\n 获取所有商品 成功= " + response);
303 | GooglePayPurchaseBean googlePayPurchaseBean = gson.fromJson(response, GooglePayPurchaseBean.class);
304 | if (googlePayPurchaseBean != null && googlePayPurchaseBean.getInappproduct() != null) {
305 | //放到列表展示出来
306 | mAdapter.setNewData(googlePayPurchaseBean.getInappproduct());
307 | }
308 | }
309 | });
310 |
311 |
312 | }
313 |
314 | private void getToken() {
315 | /**
316 | * auth 接口 目的:需要获取到 code
317 | *
318 | * 该接口,是以网页登录的形式,需要在浏览器操作授权,获得 code
319 | *
320 | * 注: code 值,只需要调用一次即可(返回是个Html5需要登录,保存即可,不需要重复调用)。
321 | */
322 | // String auth = "https://accounts.google.com/o/oauth2/v2/auth";
323 | // OkHttpUtils.get().url(auth)
324 | // .addParams("client_id",cliendId)
325 | // .addParams("redirect_uri",redirect_uri)
326 | // .addParams("response_type","code")
327 | // .addParams("scope","https://www.googleapis.com/auth/androidpublisher")
328 | // .addParams("login_hint","cuishuxiang0604@gmail.com")
329 | // .build().execute(new StringCallback() {
330 | // @Override
331 | // public void onError(Call call, Exception e, int id) {
332 | // Log.d(TAG, "onError: ");
333 | // }
334 | //
335 | // @Override
336 | // public void onResponse(String response, int id) {
337 | // Log.d(TAG, "onResponse: ");
338 | // }
339 | // });
340 |
341 | /**
342 | * 获取access_token : 每个请求,都需要拼接到后面,例如:?access_token=xxxxx
343 | */
344 | String tokenUrl = "https://accounts.google.com/o/oauth2/token";
345 | OkHttpUtils.post().url(tokenUrl)
346 | .addParams("client_id", client_id) //GoogleApi后台拿到 : 790140086566-hmeimrjutgjooiga0639hbolj6jh2e72.apps.googleusercontent.com
347 | .addParams("redirect_uri", redirect_uris) //GoogleApi后台拿到 : urn:ietf:wg:oauth:2.0:oob
348 | .addParams("grant_type", "authorization_code")//固定值
349 | .addParams("code", code)//上面获取,只需要调用一次
350 | .addParams("client_secret", client_secret)////GoogleApi后台拿到
351 | .build().execute(new StringCallback() {
352 | @Override
353 | public void onError(Call call, Exception e, int id) {
354 | Log.d(TAG, "onError: ");
355 | tv_hint.setText(tv_hint.getText() + "\n 获取access_token 失败\n ,开始通过refreshToken重新获取access_token" + e.getMessage());
356 |
357 | getTokenByRefreshToken();
358 | }
359 |
360 | @Override
361 | public void onResponse(String response, int id) {
362 | //返回的结果都在 response 包含
363 | GoogleAccsessTokenBean googleAccsessTokenBean = new Gson().fromJson(response, GoogleAccsessTokenBean.class);
364 | Log.d(TAG, "onResponse: googleAccsessTokenBean = " + googleAccsessTokenBean.toString());//返回,GoogleAccsessTokenBean.java 相关内容
365 | tv_hint.setText(tv_hint.getText() + "\n 获取access_token 成功 = " + response);
366 | accessToken = googleAccsessTokenBean.getAccess_token();
367 | refreshToken = googleAccsessTokenBean.getRefresh_token();
368 |
369 | showToast("Access Token 获取成功!");
370 | }
371 | });
372 | }
373 |
374 | /**
375 | * 当Token 失效,可以通过“refresh_token”刷新token
376 | */
377 | private void getTokenByRefreshToken() {
378 | String tokenUrl = "https://www.googleapis.com/oauth2/v4/token";
379 | OkHttpUtils.post().url(tokenUrl)
380 | .addParams("client_id", client_id) //GoogleApi后台拿到
381 | .addParams("grant_type", "refresh_token")//固定值
382 | .addParams("client_secret", client_secret)////GoogleApi后台拿到
383 | .addParams("refresh_token", refreshToken)//接口调用失败,使用该字段,刷新token,auth接口返回,存储即可
384 | .build().execute(new StringCallback() {
385 | @Override
386 | public void onError(Call call, Exception e, int id) {
387 | Log.d(TAG, "onError: ");
388 | tv_hint.setText(tv_hint.getText() + "\n refresh_token 获取access_token 失败= " + e.getMessage());
389 | }
390 |
391 | @Override
392 | public void onResponse(String response, int id) {
393 | //返回的结果都在 response 包含
394 |
395 | GoogleAccsessTokenBean googleAccsessTokenBean = new Gson().fromJson(response, GoogleAccsessTokenBean.class);
396 | Log.d(TAG, "onResponse: googleAccessTokenBean = " + googleAccsessTokenBean.toString());//返回,GoogleAccessTokenBean.java 相关内容
397 | tv_hint.setText(tv_hint.getText() + "\n refresh_token 获取access_token 成功 = " + response);
398 | accessToken = googleAccsessTokenBean.getAccess_token();
399 | }
400 | });
401 |
402 | }
403 |
404 | /**
405 | * 开始校验
406 | * GET https://www.googleapis.com/androidpublisher/v3/applications/packageName/purchases/products/productId/tokens/token
407 | */
408 | private void checkPurchase() {
409 | if (TextUtils.isEmpty(mProductId)) {
410 | Toast.makeText(this, "产品:mProductId为空!请先调用支付获取相应信息 ", Toast.LENGTH_LONG).show();
411 | return;
412 | }
413 |
414 | if (TextUtils.isEmpty(mPurchaseToken)) {
415 | Toast.makeText(this, "产品:token!请先调用支付获取相应信息 ", Toast.LENGTH_LONG).show();
416 | return;
417 | }
418 |
419 | /**
420 | * 调用下面的接口去Google查询订单是否成功
421 | */
422 | String url = "https://www.googleapis.com/androidpublisher/v3/applications/" + getPackageName() + "/purchases/products/" + mProductId + "/tokens/" + mPurchaseToken;
423 | Log.e(TAG, "checkPurchase: 请求的url: " + url, new Throwable());
424 | OkHttpUtils.get().url(url)
425 | .addParams("access_token", accessToken)
426 | // .addParams("developerPayload", mDeveloperPayload)
427 | .build().execute(new StringCallback() {
428 | @Override
429 | public void onError(Call call, Exception e, int id) {
430 | Log.d(TAG, "onError: " + e.getMessage());
431 | tv_hint.setText(tv_hint.getText() + "\n 校验接口失败 = " + e.getMessage());
432 | }
433 |
434 | @Override
435 | public void onResponse(String response, int id) {
436 | Log.d(TAG, "onResponse: " + response);
437 | tv_hint.setText(tv_hint.getText() + "\n 校验接口成功 = " + response);
438 | }
439 | });
440 | }
441 |
442 |
443 | @Override
444 | public synchronized ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
445 | return null;
446 | }
447 |
448 | private void showToast(String message) {
449 | Toast.makeText(this, message, Toast.LENGTH_LONG).show();
450 | }
451 | }
452 |
--------------------------------------------------------------------------------
/app/src/main/java/com/googlepaytest/csx/github_google_pay/GitPayTestActivity.java:
--------------------------------------------------------------------------------
1 | package com.googlepaytest.csx.github_google_pay;
2 |
3 | import android.app.Activity;
4 | import android.content.ComponentName;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.os.UserHandle;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import android.support.v7.widget.LinearLayoutManager;
11 | import android.support.v7.widget.RecyclerView;
12 | import android.text.TextUtils;
13 | import android.util.Log;
14 | import android.view.View;
15 | import android.widget.Button;
16 | import android.widget.TextView;
17 | import android.widget.Toast;
18 |
19 | import com.anjlab.android.iab.v3.BillingCommunicationException;
20 | import com.anjlab.android.iab.v3.BillingHistoryRecord;
21 | import com.anjlab.android.iab.v3.BillingProcessor;
22 | import com.anjlab.android.iab.v3.Constants;
23 | import com.anjlab.android.iab.v3.SkuDetails;
24 | import com.anjlab.android.iab.v3.TransactionDetails;
25 | import com.chad.library.adapter.base.BaseQuickAdapter;
26 | import com.google.gson.Gson;
27 | import com.googlepaytest.csx.R;
28 | import com.zhy.http.okhttp.OkHttpUtils;
29 | import com.zhy.http.okhttp.callback.StringCallback;
30 |
31 | import org.json.JSONException;
32 | import org.json.JSONObject;
33 |
34 | import java.util.List;
35 |
36 | import okhttp3.Call;
37 |
38 | import static com.googlepaytest.csx.github_google_pay.GooglePayConstant.PayLicenseKey;
39 |
40 |
41 | /**
42 | * Date: 2019/8/14
43 | * create by cuishuxiang
44 | * description:
45 | *
46 | * Api参考:https://developers.google.com/android-publisher/api-ref/purchases/products/get?hl=zh-cn
47 | *
48 | * 使用 三方库,进行Google Pay 相关操作
49 | * ·
50 | */
51 | public class GitPayTestActivity extends Activity implements View.OnClickListener {
52 | private static final String TAG = "PayTestActivity";
53 |
54 | private Button btn_get_token;
55 | private Button btn_start_pay;
56 | private Button btn_query_all;
57 | private Button btn_query_info;
58 | private Button btn_consume;
59 | private Button btn_history;
60 | private Button btn_purchase_state;
61 | private Button btn_restore_sub;
62 | private Button btn_clean;
63 | private TextView tv_hint;
64 | private RecyclerView mRv;
65 | private PurchaseAdapter mAdapter;
66 | private Gson gson = new Gson();
67 |
68 | private boolean isServerAvaliable = false;
69 |
70 | BillingProcessor bp;
71 |
72 | //校验需要用到这2个参数
73 | private String mProductId;
74 | private String mPurchaseToken;
75 | private String mDeveloperPayload;
76 | private String code = "4/qwGJ6-mtO5uQWBHC3WVAGeNHWqBX2t0gCpGwMuQBKtHkkdfxrxZba6M";//9.9号生成
77 | private String accessToken;
78 | private String refreshToken = "1/4JEiUYlT4nA1edcMHvV1qRMS9-O7NLjrbi7FtZ16aPE";//9.9号生成
79 | private String client_secret = "GErDLKY0ipw8EGJcouzH_jbu";
80 | private String client_id = "790140086566-9q9lfiir624fl248l7cshcqp3dicitl4.apps.googleusercontent.com";
81 | private String redirect_uris = "urn:ietf:wg:oauth:2.0:oob";
82 | private String purchaseId;
83 |
84 | @Override
85 | protected void onCreate(@Nullable Bundle savedInstanceState) {
86 | super.onCreate(savedInstanceState);
87 | setContentView(R.layout.activity_git_test_pay);
88 |
89 | initView();
90 |
91 | //初始化
92 | BillingManager.init(this, PayLicenseKey, new BillingProcessor.IBillingHandler() {
93 | @Override
94 | public void onProductPurchased(@NonNull String productId, @Nullable TransactionDetails details) {
95 | /*
96 | * Called when requested PRODUCT ID was successfully purchased
97 | *
98 | * 可以通过:{BillingManager#getSkuDetail()}获取交易价格等信息
99 | */
100 | tv_hint.setText(tv_hint.getText() + "\n\n onProductPurchased: productId = " + productId + " \ndetails = " + details);
101 | mProductId = productId;
102 | mPurchaseToken = details.purchaseInfo.purchaseData.purchaseToken;
103 | mDeveloperPayload = details.purchaseInfo.purchaseData.developerPayload;
104 | }
105 |
106 | @Override
107 | public void onPurchaseHistoryRestored() {
108 | /*
109 | * Called when purchase history was restored and the list of all owned PRODUCT ID's
110 | * was loaded from Google Play
111 | *
112 | * 查询购买记录回调
113 | */
114 | tv_hint.setText(tv_hint.getText() + "\n\n-- onPurchaseHistoryRestored被调用了---");
115 |
116 | for (String sku : BillingManager.getBillingProcessor().listOwnedProducts()) {
117 | Log.d(TAG, "onPurchaseHistoryRestored: " + sku);
118 | tv_hint.setText(tv_hint.getText() + "\n购买历史 sku = " + sku);
119 | }
120 | }
121 |
122 | @Override
123 | public void onBillingError(int errorCode, @Nullable Throwable error) {
124 | /*
125 | * Called when some error occurred. See Constants class for more details
126 | *
127 | * Note - this includes handling the case where the user canceled the buy dialog:
128 | * errorCode = Constants.BILLING_RESPONSE_RESULT_USER_CANCELED
129 | */
130 | showToast("购买失败 code = " + errorCode);
131 | switch (errorCode) {
132 | case Constants.BILLING_RESPONSE_RESULT_USER_CANCELED:
133 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingError: errorCode = " + errorCode + "用户取消!");
134 | break;
135 | case Constants.BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE:
136 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingError: errorCode = " + errorCode + "网络连接已关闭!");
137 | break;
138 | case Constants.BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE:
139 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingError: errorCode = " + errorCode + "Billing Api 版本不支持!");
140 | break;
141 | case Constants.BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE:
142 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingError: errorCode = " + errorCode + "请求的产品无法购买");
143 | break;
144 | case Constants.BILLING_RESPONSE_RESULT_DEVELOPER_ERROR:
145 | /**
146 | * 提供给API的参数无效。 此错误也可以指示应用程序
147 | * 未正确签名或正确设置Google Play中的应用内结算,或
148 | * 在其清单中没有必要的权限
149 | */
150 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingError: errorCode = " + errorCode + "提供给API的参数无效!");
151 | break;
152 | case Constants.BILLING_RESPONSE_RESULT_ERROR:
153 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingError: errorCode = " + errorCode + "API操作期间发生致命错误!");
154 | break;
155 | case Constants.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED:
156 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingError: errorCode = " + errorCode + "由于物品已经拥有,因此未能购买!");
157 | break;
158 | case Constants.BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED:
159 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingError: errorCode = " + errorCode + "不属于物品的消费不属于!");
160 | break;
161 | }
162 |
163 | }
164 |
165 | @Override
166 | public void onBillingInitialized() {
167 | /*
168 | * Called when BillingProcessor was initialized and it's ready to purchase
169 | */
170 | tv_hint.setText(tv_hint.getText() + "\n\n onBillingInitialized");
171 | }
172 | });
173 |
174 | bp = BillingManager.getBillingProcessor();
175 |
176 | //判断Pay服务是否可用
177 | isServerAvaliable = BillingProcessor.isIabServiceAvailable(this);
178 | tv_hint.setText(tv_hint.getText() + "服务是否可用? " + isServerAvaliable);
179 |
180 | //服务可用,先调用一次,获取token
181 | if ((isServerAvaliable)) {
182 | getToken();
183 | }
184 | }
185 |
186 | private void initView() {
187 | btn_get_token = findViewById(R.id.btn_get_token);
188 | btn_get_token.setOnClickListener(this);
189 | btn_restore_sub = findViewById(R.id.btn_restore_sub);
190 | btn_restore_sub.setOnClickListener(this);
191 | btn_query_all = findViewById(R.id.btn_query_all);
192 | btn_query_all.setOnClickListener(this);
193 | btn_query_info = findViewById(R.id.btn_query_info);
194 | btn_query_info.setOnClickListener(this);
195 | btn_start_pay = findViewById(R.id.btn_start_pay);
196 | btn_start_pay.setOnClickListener(this);
197 | btn_consume = findViewById(R.id.btn_consume);
198 | btn_consume.setOnClickListener(this);
199 | btn_history = findViewById(R.id.btn_history);
200 | btn_history.setOnClickListener(this);
201 | btn_clean = findViewById(R.id.btn_clean);
202 | btn_clean.setOnClickListener(this);
203 | btn_purchase_state = findViewById(R.id.btn_purchase_state);
204 | btn_purchase_state.setOnClickListener(this);
205 | tv_hint = findViewById(R.id.tv_hint);
206 |
207 | mRv = findViewById(R.id.rv);
208 | mRv.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
209 | mAdapter = new PurchaseAdapter();
210 | mRv.setAdapter(mAdapter);
211 |
212 | mAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
213 | @Override
214 | public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
215 | mAdapter.setChoosePosition(position);
216 | mAdapter.notifyDataSetChanged();
217 | purchaseId = mAdapter.getItem(position).getSku();
218 | }
219 | });
220 | }
221 |
222 | @Override
223 | public void onClick(View v) {
224 | switch (v.getId()) {
225 | case R.id.btn_get_token:
226 |
227 | tv_hint.setText(tv_hint.getText() + "\n\n------获取Token开始-------- ");
228 | getToken();
229 | break;
230 | case R.id.btn_query_all:
231 | tv_hint.setText(tv_hint.getText() + "\n\n------查询所有商品开始-------- ");
232 | getAllPurchase();
233 | break;
234 | case R.id.btn_query_info:
235 | if (TextUtils.isEmpty(mProductId)) {
236 | Toast.makeText(this, "请先选择一个商品!", Toast.LENGTH_SHORT).show();
237 | return;
238 | }
239 | tv_hint.setText(tv_hint.getText() + "\n\n点击查询商品详情: ");
240 | SkuDetails skuDetails = bp.getPurchaseListingDetails(mProductId);
241 | tv_hint.setText(tv_hint.getText() + "\n\n查询商品详情为: " + skuDetails.title);
242 | break;
243 | case R.id.btn_start_pay:
244 | tv_hint.setText(tv_hint.getText() + "\n\n点击开始支付..... ");
245 | startPay();
246 | break;
247 | case R.id.btn_purchase_state://校验
248 | tv_hint.setText(tv_hint.getText() + "\n\n点击支付校验..... ");
249 | checkPurchase();
250 | break;
251 | case R.id.btn_consume:
252 | if (TextUtils.isEmpty(purchaseId)) {
253 | Toast.makeText(this, "消费不能为空,请先选择一个商品!", Toast.LENGTH_SHORT).show();
254 | return;
255 | }
256 | tv_hint.setText(tv_hint.getText() + "\n\n 点击消费: " + purchaseId);
257 |
258 | boolean consumerSuccess = bp.consumePurchase(purchaseId);
259 | if (consumerSuccess) showToast("消费成功!");
260 | break;
261 | case R.id.btn_history:
262 | tv_hint.setText(tv_hint.getText() + "\n\n 点击获取购买记录----- ");
263 | Bundle bundle = new Bundle();
264 | bundle.putString("accountId",mProductId);
265 | try {
266 | List historyRecords = bp.getPurchaseHistory(Constants.PRODUCT_TYPE_MANAGED, bundle);
267 | if (historyRecords == null) {
268 | tv_hint.setText(tv_hint.getText() + "\n购买记录:historyRecords == null ");
269 | return;
270 | }
271 |
272 | for (int i = 0; i < historyRecords.size(); i++) {
273 | tv_hint.setText(tv_hint.getText() + "\n\n购买记录: " + historyRecords.get(i).toString());
274 | }
275 | } catch (BillingCommunicationException e) {
276 | e.printStackTrace();
277 | tv_hint.setText(tv_hint.getText() + "\n\n 点击获取购买记录失败: " + e);
278 | }
279 | break;
280 | case R.id.btn_clean:
281 | tv_hint.setText("");
282 | break;
283 | case R.id.btn_restore_sub:
284 | boolean restore = BillingManager.restore();
285 | Log.d(TAG, "onClick: ");
286 | break;
287 | }
288 |
289 | }
290 |
291 | /**
292 | * 调起google支付
293 | * 1,先调用自己后台,生成订单(用于记录)
294 | * 2,成功后,拉起GooglePay进行支付
295 | * ps:实际调用的是( bp.purchase(GitPayTestActivity.this, purchaseId);)
296 | */
297 | private void startPay() {
298 | if (TextUtils.isEmpty(purchaseId)) {
299 | Toast.makeText(this, "请从所有的商品中,选择一个商品进行支付!", Toast.LENGTH_SHORT).show();
300 | return;
301 | }
302 | JSONObject jsonObject = new JSONObject();
303 | try {
304 | jsonObject.put("packageName", getPackageName());
305 | jsonObject.put("productId", mProductId);
306 | jsonObject.put("purchaseToken", mPurchaseToken);
307 | jsonObject.put("accessToken", accessToken);
308 | } catch (JSONException e) {
309 | e.printStackTrace();
310 | }
311 | bp.purchase(GitPayTestActivity.this, purchaseId);
312 | //自己后台,生成订单接口
313 | // OkHttpUtils.post()
314 | // .url(createOrderUrl)
315 | // .addParams("sign", "")
316 | // .addParams("reqMsg", jsonObject.toString())
317 | // .build().execute(new StringCallback() {
318 | // @Override
319 | // public void onError(Call call, Exception e, int id) {
320 | // tv_hint.setText(tv_hint.getText() + "\n\n调用自己后台生成订单接口失败:" + e.getMessage());
321 | // }
322 | //
323 | // @Override
324 | // public void onResponse(String response, int id) {
325 | // tv_hint.setText(tv_hint.getText() + "\n\n调用自己后台生成订单接口成功");
326 | // bp.purchase(GitPayTestActivity.this, purchaseId);
327 | // }
328 | // });
329 | }
330 |
331 | /**
332 | * 查询所有商品(Google后台)
333 | *
334 | * GET https://www.googleapis.com/androidpublisher/v3/applications/packageName/inappproducts
335 | *
336 | * 参考:https://developers.google.com/android-publisher/api-ref/inappproducts/list?hl=zh-cn
337 | */
338 | private void getAllPurchase() {
339 | /**
340 | * 调用Google后台,查询所有的商品
341 | * 需要:access_token 参数
342 | */
343 | if (TextUtils.isEmpty(accessToken)) {
344 | Toast.makeText(this, "请先调用获取Token接口!", Toast.LENGTH_SHORT).show();
345 | return;
346 | }
347 |
348 | String queryAllPurchase = "https://www.googleapis.com/androidpublisher/v3/applications/" + getPackageName() + "/inappproducts";
349 | OkHttpUtils.get()
350 | .url(queryAllPurchase)
351 | .addParams("access_token", accessToken)
352 | .build()
353 | .execute(new StringCallback() {
354 | @Override
355 | public void onError(Call call, Exception e, int id) {
356 | Log.d(TAG, "onError: getAllPurchase()" + e);
357 | tv_hint.setText(tv_hint.getText() + "\n 获取所有商品 失败= " + e.getMessage());
358 | }
359 |
360 | @Override
361 | public void onResponse(String response, int id) {
362 | Log.d(TAG, "onResponse: getAllPurchase() = " + response);
363 | tv_hint.setText(tv_hint.getText() + "\n 获取所有商品 成功= " + response);
364 | GooglePayPurchaseBean googlePayPurchaseBean = gson.fromJson(response, GooglePayPurchaseBean.class);
365 | if (googlePayPurchaseBean != null && googlePayPurchaseBean.getInappproduct() != null) {
366 | //放到列表展示出来
367 | mAdapter.setNewData(googlePayPurchaseBean.getInappproduct());
368 | }
369 | }
370 | });
371 |
372 |
373 | }
374 |
375 | private void getToken() {
376 | /**
377 | * auth 接口 目的:需要获取到 code
378 | *
379 | * 该接口,是以网页登录的形式,需要在浏览器操作授权,获得 code
380 | *
381 | * 注: code 值,只需要调用一次即可(返回是个Html5需要登录,保存即可,不需要重复调用)。
382 | */
383 | // String auth = "https://accounts.google.com/o/oauth2/v2/auth";
384 | // OkHttpUtils.get().url(auth)
385 | // .addParams("client_id",cliendId)
386 | // .addParams("redirect_uri",redirect_uri)
387 | // .addParams("response_type","code")
388 | // .addParams("scope","https://www.googleapis.com/auth/androidpublisher")
389 | // .addParams("login_hint","cuishuxiang0604@gmail.com")
390 | // .build().execute(new StringCallback() {
391 | // @Override
392 | // public void onError(Call call, Exception e, int id) {
393 | // Log.d(TAG, "onError: ");
394 | // }
395 | //
396 | // @Override
397 | // public void onResponse(String response, int id) {
398 | // Log.d(TAG, "onResponse: ");
399 | // }
400 | // });
401 |
402 | /**
403 | * 获取access_token : 每个请求,都需要拼接到后面,例如:?access_token=xxxxx
404 | */
405 | String tokenUrl = "https://accounts.google.com/o/oauth2/token";
406 | OkHttpUtils.post().url(tokenUrl)
407 | .addParams("client_id", client_id) //GoogleApi后台拿到 : 790140086566-hmeimrjutgjooiga0639hbolj6jh2e72.apps.googleusercontent.com
408 | .addParams("redirect_uri", redirect_uris) //GoogleApi后台拿到 : urn:ietf:wg:oauth:2.0:oob
409 | .addParams("grant_type", "authorization_code")//固定值
410 | .addParams("code", code)//上面获取,只需要调用一次
411 | .addParams("client_secret", client_secret)////GoogleApi后台拿到
412 | .build().execute(new StringCallback() {
413 | @Override
414 | public void onError(Call call, Exception e, int id) {
415 | Log.d(TAG, "onError: ");
416 | tv_hint.setText(tv_hint.getText() + "\n 获取access_token 失败\n ,开始通过refreshToken重新获取access_token" + e.getMessage());
417 |
418 | getTokenByRefreshToken();
419 | }
420 |
421 | @Override
422 | public void onResponse(String response, int id) {
423 | //返回的结果都在 response 包含
424 | GoogleAccsessTokenBean googleAccsessTokenBean = new Gson().fromJson(response, GoogleAccsessTokenBean.class);
425 | Log.d(TAG, "onResponse: googleAccsessTokenBean = " + googleAccsessTokenBean.toString());//返回,GoogleAccsessTokenBean.java 相关内容
426 | tv_hint.setText(tv_hint.getText() + "\n 获取access_token 成功 = " + response);
427 | accessToken = googleAccsessTokenBean.getAccess_token();
428 | refreshToken = googleAccsessTokenBean.getRefresh_token();
429 |
430 | showToast("Access Token 获取成功!");
431 | }
432 | });
433 | }
434 |
435 | /**
436 | * 当Token 失效,可以通过“refresh_token”刷新token
437 | */
438 | private void getTokenByRefreshToken() {
439 | String tokenUrl = "https://www.googleapis.com/oauth2/v4/token";
440 | OkHttpUtils.post().url(tokenUrl)
441 | .addParams("client_id", client_id) //GoogleApi后台拿到
442 | .addParams("grant_type", "refresh_token")//固定值
443 | .addParams("client_secret", client_secret)////GoogleApi后台拿到
444 | .addParams("refresh_token", refreshToken)//接口调用失败,使用该字段,刷新token,auth接口返回,存储即可
445 | .build().execute(new StringCallback() {
446 | @Override
447 | public void onError(Call call, Exception e, int id) {
448 | Log.d(TAG, "onError: ");
449 | tv_hint.setText(tv_hint.getText() + "\n refresh_token 获取access_token 失败= " + e.getMessage());
450 | }
451 |
452 | @Override
453 | public void onResponse(String response, int id) {
454 | //返回的结果都在 response 包含
455 |
456 | GoogleAccsessTokenBean googleAccsessTokenBean = new Gson().fromJson(response, GoogleAccsessTokenBean.class);
457 | Log.d(TAG, "onResponse: googleAccessTokenBean = " + googleAccsessTokenBean.toString());//返回,GoogleAccessTokenBean.java 相关内容
458 | tv_hint.setText(tv_hint.getText() + "\n refresh_token 获取access_token 成功 = " + response);
459 | accessToken = googleAccsessTokenBean.getAccess_token();
460 | }
461 | });
462 |
463 | }
464 |
465 | /**
466 | * 开始校验
467 | * GET https://www.googleapis.com/androidpublisher/v3/applications/packageName/purchases/products/productId/tokens/token
468 | */
469 | private void checkPurchase() {
470 | if (TextUtils.isEmpty(mProductId)) {
471 | Toast.makeText(this, "产品:mProductId为空!请先调用支付获取相应信息 ", Toast.LENGTH_LONG).show();
472 | return;
473 | }
474 |
475 | if (TextUtils.isEmpty(mPurchaseToken)) {
476 | Toast.makeText(this, "产品:token!请先调用支付获取相应信息 ", Toast.LENGTH_LONG).show();
477 | return;
478 | }
479 |
480 | /**
481 | * 调用下面的接口去Google查询订单是否成功
482 | */
483 | String url = "https://www.googleapis.com/androidpublisher/v3/applications/" + getPackageName() + "/purchases/products/" + mProductId + "/tokens/" + mPurchaseToken;
484 | Log.e(TAG, "checkPurchase: 请求的url: " + url, new Throwable());
485 | OkHttpUtils.get().url(url)
486 | .addParams("access_token", accessToken)
487 | // .addParams("developerPayload", mDeveloperPayload)
488 | .build().execute(new StringCallback() {
489 | @Override
490 | public void onError(Call call, Exception e, int id) {
491 | Log.d(TAG, "onError: " + e.getMessage());
492 | tv_hint.setText(tv_hint.getText() + "\n 校验接口失败 = " + e.getMessage());
493 | }
494 |
495 | @Override
496 | public void onResponse(String response, int id) {
497 | Log.d(TAG, "onResponse: " + response);
498 | tv_hint.setText(tv_hint.getText() + "\n 校验接口成功 = " + response);
499 | }
500 | });
501 | }
502 |
503 | /**
504 | * 注意:没下面方法,不会受到Google Play 回调
505 | * @param requestCode
506 | * @param resultCode
507 | * @param data
508 | */
509 | @Override
510 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
511 | if (!bp.handleActivityResult(requestCode, resultCode, data)) {
512 | super.onActivityResult(requestCode, resultCode, data);
513 | }
514 | }
515 |
516 | @Override
517 | public synchronized ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) {
518 | return null;
519 | }
520 |
521 | private void showToast(String message) {
522 | Toast.makeText(this, message, Toast.LENGTH_LONG).show();
523 | }
524 | }
525 |
--------------------------------------------------------------------------------