Google Pay 谷歌支付(gateway = stripe)

語言: CN / TW / HK

Google pay 國際版開發


一、官網地址(科學上網)

官方對接文件 http://developers.google.com/pay/api/android/overview

Stripe對接Google Pay http://stripe.com/docs/google-pay

Stripe驗證支付 http://stripe.com/docs/payments/accept-a-payment?integration=elements

Stripe管理後臺 http://dashboard.stripe.com/dashboard

二、接入流程

Google Pay.jpg

三、主要流程

1、伺服器請求orderId

2、呼叫Google Pay

3、通過Stripe完成支付

4、伺服器完成驗證

四、主要程式碼實現(1、4主要是伺服器流程,以下主要是2、3流程)

專案配置 build.gradle 新增 implementation 'com.google.android.gms:play-services-wallet:18.1.2' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.stripe:stripe-android:16.0.1' AndroidManifest.xml 的 application新增 <meta-data android:name="com.google.android.gms.wallet.api.enabled" android:value="true" /> 呼叫Google Pay @RequiresApi(api = Build.VERSION_CODES.N) private void payWithGoogle( double price) { if (paymentsClient == null) { paymentsClient = createPaymentsClient(mActivity); } // 主要是Google Pay一些引數設定 Optional<JSONObject> paymentDataRequestJson = createPaymentDataRequest(price); if (!paymentDataRequestJson.isPresent()) { return; } PaymentDataRequest request = PaymentDataRequest.fromJson(paymentDataRequestJson.get().toString()); if (request != null) { if (mActivity!=null){ mActivity.showCircle2Loading(); } // 調起 Google Pay AutoResolveHelper.resolveTask( paymentsClient.loadPaymentData(request), mActivity, LOAD_PAYMENT_DATA_REQUEST_CODE ); } } Google Pay 基本設定 ``` @RequiresApi(api = Build.VERSION_CODES.N) private Optional createPaymentDataRequest(double priceCents) { try { JSONObject paymentDataRequest = getBaseRequest(); // 指定是否支援 Google Pay API 所支援的一種或多種付款方式。 paymentDataRequest.put("allowedPaymentMethods", new JSONArray().put(getCardPaymentMethod())); // 有關根據使用者是否同意交易來為交易授權的詳細資訊。包含總價和價格狀態 paymentDataRequest.put("transactionInfo", getTransactionInfo(priceCents)); //商家資訊 paymentDataRequest.put("merchantInfo", getMerchantInfo()); paymentDataRequest.put("shippingAddressRequired", false); paymentDataRequest.put("emailRequired", false); return Optional.of(paymentDataRequest);

    } catch (JSONException e) {
        return Optional.empty();
    }
}

private JSONObject getCardPaymentMethod() throws JSONException { JSONObject cardPaymentMethod = getBaseCardPaymentMethod(); // 設定stripe為付款方式 JSONObject tokenizationSpec = new GooglePayConfig(LIVE_API).getTokenizationSpecification(); cardPaymentMethod.put("tokenizationSpecification", tokenizationSpec); return cardPaymentMethod; }

/**
 * 金錢資訊
 */
private JSONObject getTransactionInfo(double price) throws JSONException {
    JSONObject transactionInfo = new JSONObject();
    transactionInfo.put("totalPrice", price+"");
    transactionInfo.put("totalPriceStatus", "FINAL");
    transactionInfo.put("countryCode", COUNTRY_CODE);
    transactionInfo.put("currencyCode", CURRENCY_CODE);
    transactionInfo.put("checkoutOption", "COMPLETE_IMMEDIATE_PURCHASE");

    return transactionInfo;
}

/**
 * 商家資訊
 * merchantId 商家Id
 */
private JSONObject getMerchantInfo() throws JSONException {
    return new JSONObject().put("merchantName", "Guruji").put("merchantId", "填寫商家ID");
}
private JSONObject getBaseRequest() throws JSONException {
    return new JSONObject()
            .put("apiVersion", 2)
            .put("apiVersionMinor", 0);
}

**_Google Pay返回資料_** { "apiVersionMinor": 0, "apiVersion": 2, "paymentMethodData": { "description": "中國招商銀行 (CMB) •••• 5019", "tokenizationData": { "type": "PAYMENT_GATEWAY", "token": "{\n \"id\": \"tok_1HcQIMBcf7rsT369XhdHf1aI\",\n \"object\": \"token\",\n \"card\": {\n \"id\": \"card_1HcQIMBcf7rsT369lDDy6PIM\",\n \"object\": \"card\",\n \"address_city\": null,\n \"address_country\": null,\n \"address_line1\": null,\n \"address_line1_check\": null,\n \"address_line2\": null,\n \"address_state\": null,\n \"address_zip\": null,\n \"address_zip_check\": null,\n \"brand\": \"Visa\",\n \"country\": \"US\",\n \"cvc_check\": null,\n \"dynamic_last4\": \"4242\",\n \"exp_month\": 10,\n \"exp_year\": 2025,\n \"funding\": \"credit\",\n \"last4\": \"5019\",\n \"metadata\": {\n },\n \"name\": \"name\",\n \"tokenization_method\": \"android_pay\"\n },\n \"client_ip\": \"173.194.101.160\",\n \"created\": 1602744638,\n \"livemode\": false,\n \"type\": \"card\",\n \"used\": false\n}\n" }, "type": "CARD", "info": { "cardNetwork": "VISA", "cardDetails": "5019" } } } **_資料處理,然後通過Stripe生成PaymentMethodId_** public void onCheckResult(int resultCode,Intent data) { switch (resultCode) { case Activity.RESULT_OK: { onGooglePayResult(data); break; } case Activity.RESULT_CANCELED: { errorShowAndRetry(); break; } case AutoResolveHelper.RESULT_ERROR: { final Status status = AutoResolveHelper.getStatusFromIntent(data); errorShowAndRetry(); break; } default: { // Do nothing. } }

}

private void onGooglePayResult(@NonNull Intent data) {
    PaymentData paymentData = PaymentData.getFromIntent(data);
    if (paymentData == null) {
        errorShowAndRetry();
        return;
    }
    try {
        PaymentMethodCreateParams paymentMethodCreateParams = PaymentMethodCreateParams.createFromGooglePay(new JSONObject(paymentData.toJson()));
        mStripe.createPaymentMethod(
                paymentMethodCreateParams,
                new ApiResultCallback<PaymentMethod>() {
                    @Override
                    public void onSuccess(@NonNull PaymentMethod result) {
                        paymentGotoStripe(result.id);
                    }

                    @Override
                    public void onError(@NonNull Exception e) {
                        errorShowAndRetry();
                    }
                }
        );
    } catch (Exception e) {
        errorShowAndRetry();
    }

}

**_通過Stripe完成最後支付_** /* * 得到PaymentMethodId 去stripe 支付 / private void paymentGotoStripe(String id){ if ( mStripe != null && !TextUtils.isEmpty(mSecret)) { ConfirmPaymentIntentParams confirmParams = ConfirmPaymentIntentParams.createWithPaymentMethodId(id, mSecret); mStripe.confirmPayment(mActivity, confirmParams); Logger.logE("live pay, click pay ---"); } else if (mStripe == null) { ToastUtils.showSystemToast(R.string.payment_in_progress_tips); Logger.logE("live pay, click pay --- order is null"); } }

/**
 * Google --> stripe--> CallBack
 * @param requestCode
 * @param data
 */
public void paymentResultCallback(int requestCode,Intent data){
    mStripe.onPaymentResult(requestCode, data, new PaymentResultCallback(mActivity));
}
private final class PaymentResultCallback implements ApiResultCallback<PaymentIntentResult> {
    @NonNull
    private final WeakReference<PaymentActivity> activityRef;

    PaymentResultCallback(@NonNull PaymentActivity activity) {
        activityRef = new WeakReference<>(activity);
    }

    @Override
    public void onSuccess(@NonNull PaymentIntentResult result) {
        PaymentActivity activity = activityRef.get();
        if (activity == null) {
            return;
        }

        PaymentIntent paymentIntent = result.getIntent();
        PaymentIntent.Status status = paymentIntent.getStatus();
        if (status == PaymentIntent.Status.Succeeded) {
            // 後臺驗證訂單
            PaymentNetManager.verifyStripeOrder(true,mOrderId, statusData);
        } else if (status == PaymentIntent.Status.RequiresPaymentMethod) {
            errorShowAndRetry();
        }
        Logger.logE("live pay, stripe succ, status = " + status);
    }

    @Override
    public void onError(@NonNull Exception e) {
        PaymentActivity activity = activityRef.get();
        if (activity == null) {
            return;
        }
        errorShowAndRetry();

        Logger.logE("live pay, stripe succ, error = " + e.getMessage());
    }
}

```

五、Google Pay配置

配置地址 http://pay.google.com/business/console/u/2/payment/BCR2DN6TZP4O3TIO 整合專案 完成Google Pay配置 完成Google Pay配置

六、完整程式碼

``` /* * Description: 國際版Google Pay * --------------------- * Author: xiangpan * Date: 2020/10/14 6:01 PM /

public class GooglePayGlobe implements DefaultLifecycleObserver { public static final int LOAD_PAYMENT_DATA_REQUEST_CODE = 5300; public static final String LIVE_API = "pk_test_51Hc6UgKbT4q9TnA8xWZpZwKjXagTRmgyMC5q8HaQqgP1XmiPYRAsLCUMriIoLe5nR2gVtpRY39SeL8x7r00J3duNXzg"; //測試 ENVIRONMENT_TEST 正式 ENVIRONMENT_PRODUCTION public static final int PAYMENTS_ENVIRONMENT = WalletConstants.ENVIRONMENT_TEST;

public static final List<String> SUPPORTED_NETWORKS = Arrays.asList(
        "AMEX",
        "DISCOVER",
        "JCB",
        "MASTERCARD",
        "VISA");

public static final List<String> SUPPORTED_METHODS = Arrays.asList(
        "PAN_ONLY",
        "CRYPTOGRAM_3DS");

public static final String COUNTRY_CODE = "US";

public static final String CURRENCY_CODE = "USD";
public MutableLiveData<StripeDto> statusData = new MutableLiveData<>();
private PaymentActivity mActivity;
private PaymentsClient paymentsClient;
private Stripe mStripe;
private String mOrderId;
private String mSecret;
private int mCreateOrderRetryTime = 0;
private int mVerifyOrderRetryTime = 0;
private ChargeUnitDto mChargeUnitDto;
private String mCouponUuid;
private double mPrice;

@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void onCreate(@NonNull LifecycleOwner owner) {
    mActivity = (com.ev.live.payment.PaymentActivity) owner;
    paymentsClient = createPaymentsClient(mActivity);
    statusData.observe(mActivity, stripeDto -> {
        if (stripeDto != null && stripeDto.code == 0) {
            Logger.logE("live pay stripe, observ " + stripeDto.isFail + " status  = " + stripeDto.status);
            if (stripeDto.status == StripeDto.STRIPE_ORDER_SUCC) {
                mStripe = new Stripe(mActivity, LIVE_API);
                mOrderId = stripeDto.orderId;
                mSecret = stripeDto.client_secret;
                mPrice = stripeDto.amount;
                payWithGoogle(mPrice);

// AnalyticUtil.firebaseEvent(AnalyticUtil.STRIPE_ORDER_SUCC, getStaticsBundle()); } else if (stripeDto.status == StripeDto.STRIPE_ORDER_FAIL) { if (mCreateOrderRetryTime < 3) { PaymentNetManager.requestStripeOrder(true,mChargeUnitDto.id, mCouponUuid, statusData); }else { stripeDto.status = StripeDto.STRIPE_ORDER_FAIL_FINAL; stripeDto.isFail = true; statusData.postValue(stripeDto); } mCreateOrderRetryTime ++; } else if (stripeDto.status == StripeDto.STRIPE_VERIFY_FAIL) { if (mVerifyOrderRetryTime < 3) { PaymentNetManager.verifyStripeOrder(true,mOrderId, statusData); } mVerifyOrderRetryTime++; // AnalyticUtil.firebaseEvent(AnalyticUtil.STRIPE_VERIFY_FAIL, getStaticsBundle()); } } });

}

@Override
public void onDestroy(@NonNull LifecycleOwner owner) {
    mActivity = null;
}

/**
 * Google 支付
 * @param dto
 * @param couponUuid
 */
public void startPayment(ChargeUnitDto dto, String couponUuid) {
    if (dto != null) {
        mChargeUnitDto = dto;
        mCouponUuid = couponUuid;
        PaymentNetManager.requestStripeOrder(true,mChargeUnitDto.id, mCouponUuid, statusData);
    }

// AnalyticUtil.threeChannelEvent(PAYPAL_ORDER_CLICK, getStaticsBundle()); }

private PaymentsClient createPaymentsClient(Activity activity) {
    Wallet.WalletOptions walletOptions =
            new Wallet.WalletOptions.Builder().setEnvironment(PAYMENTS_ENVIRONMENT).build();
    return Wallet.getPaymentsClient(activity, walletOptions);
}

@RequiresApi(api = Build.VERSION_CODES.N)
private void payWithGoogle( double price) {
    if (paymentsClient == null) {
        paymentsClient = createPaymentsClient(mActivity);
    }
    Optional<JSONObject> paymentDataRequestJson = createPaymentDataRequest(price);
    if (!paymentDataRequestJson.isPresent()) {
        return;
    }
    PaymentDataRequest request =
            PaymentDataRequest.fromJson(paymentDataRequestJson.get().toString());
    if (request != null) {
        if (mActivity!=null){
            mActivity.showCircle2Loading();
        }
        AutoResolveHelper.resolveTask(
                paymentsClient.loadPaymentData(request),
                mActivity,
                LOAD_PAYMENT_DATA_REQUEST_CODE
        );
    }
}

/**
 * {
 * "apiVersionMinor": 0,
 * "apiVersion": 2,
 * "paymentMethodData": {
 * "description": "中國招商銀行 (CMB) •••• 5019",
 * "tokenizationData": {
 * "type": "PAYMENT_GATEWAY",
 * "token": "{\n  \"id\": \"tok_1HcQIMBcf7rsT369XhdHf1\",\n  \"object\": \"token\",\n  \"card\": {\n    \"id\": \"card_1HcQIMBcf7rsT369lDDy6PIM\",\n    \"object\": \"card\",\n    \"address_city\": null,\n    \"address_country\": null,\n    \"address_line1\": null,\n    \"address_line1_check\": null,\n    \"address_line2\": null,\n    \"address_state\": null,\n    \"address_zip\": null,\n    \"address_zip_check\": null,\n    \"brand\": \"Visa\",\n    \"country\": \"US\",\n    \"cvc_check\": null,\n    \"dynamic_last4\": \"4242\",\n    \"exp_month\": 10,\n    \"exp_year\": 2025,\n    \"funding\": \"credit\",\n    \"last4\": \"5019\",\n    \"metadata\": {\n    },\n    \"name\": \"name\",\n    \"tokenization_method\": \"android_pay\"\n  },\n  \"client_ip\": \"173.194.101.160\",\n  \"created\": 1602744638,\n  \"livemode\": false,\n  \"type\": \"card\",\n  \"used\": false\n}\n"
 * },
 * "type": "CARD",
 * "info": {
 * "cardNetwork": "VISA",
 * "cardDetails": "5019"
 * }
 * }
 * }
 *
 * @param data
 * @param resultCode
 */
public void onCheckResult(int resultCode,Intent data) {
    switch (resultCode) {
        case Activity.RESULT_OK: {
            onGooglePayResult(data);
            break;
        }
        case Activity.RESULT_CANCELED: {
            errorShowAndRetry();
            break;
        }
        case AutoResolveHelper.RESULT_ERROR:  {
            final Status status =
                    AutoResolveHelper.getStatusFromIntent(data);
            errorShowAndRetry();
            break;
        }
        default: {
            // Do nothing.
        }
    }

}

private void onGooglePayResult(@NonNull Intent data) {
    PaymentData paymentData = PaymentData.getFromIntent(data);
    if (paymentData == null) {
        errorShowAndRetry();
        return;
    }
    try {
        PaymentMethodCreateParams paymentMethodCreateParams = PaymentMethodCreateParams.createFromGooglePay(new JSONObject(paymentData.toJson()));
        mStripe.createPaymentMethod(
                paymentMethodCreateParams,
                new ApiResultCallback<PaymentMethod>() {
                    @Override
                    public void onSuccess(@NonNull PaymentMethod result) {
                        paymentGotoStripe(result.id);
                    }

                    @Override
                    public void onError(@NonNull Exception e) {
                        errorShowAndRetry();
                    }
                }
        );
    } catch (Exception e) {
        errorShowAndRetry();
    }

}

/**
 * 得到PaymentMethodId 去stripe 支付
 */
private void paymentGotoStripe(String id){
    if ( mStripe != null && !TextUtils.isEmpty(mSecret)) {
        ConfirmPaymentIntentParams confirmParams = ConfirmPaymentIntentParams.createWithPaymentMethodId(id, mSecret);
        mStripe.confirmPayment(mActivity, confirmParams);
        Logger.logE("live pay, click pay ---");
    } else if (mStripe == null) {
        ToastUtils.showSystemToast(R.string.payment_in_progress_tips);
        Logger.logE("live pay, click pay --- order is null");
    }
}

/**
 * Google --> stripe--> CallBack
 * @param requestCode
 * @param data
 */
public void paymentResultCallback(int requestCode,Intent data){
    mStripe.onPaymentResult(requestCode, data, new PaymentResultCallback(mActivity));
}

/**
 * 判斷使用者是否支援Google pay支付
 *
 */
@RequiresApi(api = Build.VERSION_CODES.N)
public void possiblyShowGooglePayButton(View view) {
    if (paymentsClient == null) {
        paymentsClient = createPaymentsClient(mActivity);
    }
    final Optional<JSONObject> isReadyToPayJson = getIsReadyToPayRequest();
    if (!isReadyToPayJson.isPresent()) {
        return;
    }
    IsReadyToPayRequest request = IsReadyToPayRequest.fromJson(isReadyToPayJson.get().toString());
    Task<Boolean> task = paymentsClient.isReadyToPay(request);
    task.addOnCompleteListener(mActivity,
            new OnCompleteListener<Boolean>() {
                @Override
                public void onComplete(@NonNull Task<Boolean> task) {
                    if (task.isSuccessful()) {
                        if (task.getResult()) {
                            view.setVisibility(View.VISIBLE);
                        } else {
                            view.setVisibility(View.GONE);
                        }

                    } else {
                        view.setVisibility(View.GONE);
                        Log.w("isReadyToPay failed", task.getException());
                    }
                }
            });
}

@RequiresApi(api = Build.VERSION_CODES.N)
private Optional<JSONObject> createPaymentDataRequest(double priceCents) {
    try {
        JSONObject paymentDataRequest = getBaseRequest();
        // 指定是否支援 Google Pay API 所支援的一種或多種付款方式。
        paymentDataRequest.put("allowedPaymentMethods", new JSONArray().put(getCardPaymentMethod()));
        // 有關根據使用者是否同意交易來為交易授權的詳細資訊。包含總價和價格狀態
        paymentDataRequest.put("transactionInfo", getTransactionInfo(priceCents));
        //商家資訊
        paymentDataRequest.put("merchantInfo", getMerchantInfo());
        paymentDataRequest.put("shippingAddressRequired", false);
        paymentDataRequest.put("emailRequired", false);
        return Optional.of(paymentDataRequest);

    } catch (JSONException e) {
        return Optional.empty();
    }
}

private JSONObject getCardPaymentMethod() throws JSONException {
    JSONObject cardPaymentMethod = getBaseCardPaymentMethod();
    JSONObject tokenizationSpec = new GooglePayConfig(LIVE_API).getTokenizationSpecification();
    cardPaymentMethod.put("tokenizationSpecification", tokenizationSpec);
    return cardPaymentMethod;
}

/**
 * 金錢資訊
 */
private JSONObject getTransactionInfo(double price) throws JSONException {
    JSONObject transactionInfo = new JSONObject();
    transactionInfo.put("totalPrice", price+"");
    transactionInfo.put("totalPriceStatus", "FINAL");
    transactionInfo.put("countryCode", COUNTRY_CODE);
    transactionInfo.put("currencyCode", CURRENCY_CODE);
    transactionInfo.put("checkoutOption", "COMPLETE_IMMEDIATE_PURCHASE");

    return transactionInfo;
}

/**
 * 商家資訊
 * merchantId 商家Id
 */
private JSONObject getMerchantInfo() throws JSONException {
    return new JSONObject().put("merchantName", "Guruji").put("merchantId", "BCR2DN6T4XFIT7KA");
}


@RequiresApi(api = Build.VERSION_CODES.N)
private Optional<JSONObject> getIsReadyToPayRequest() {
    try {
        JSONObject isReadyToPayRequest = getBaseRequest();
        isReadyToPayRequest.put(
                "allowedPaymentMethods", new JSONArray().put(getBaseCardPaymentMethod()));
        isReadyToPayRequest.put("existingPaymentMethodRequired", true);
        return Optional.of(isReadyToPayRequest);

    } catch (JSONException e) {
        return Optional.empty();
    }
}

private JSONObject getBaseCardPaymentMethod() throws JSONException {
    JSONObject cardPaymentMethod = new JSONObject();
    cardPaymentMethod.put("type", "CARD");

    JSONObject parameters = new JSONObject();
    parameters.put("allowedAuthMethods", getAllowedCardAuthMethods());
    parameters.put("allowedCardNetworks", getAllowedCardNetworks());
    // Optionally, you can add billing address/phone number associated with a CARD payment method.
    parameters.put("billingAddressRequired", false);

    cardPaymentMethod.put("parameters", parameters);

    return cardPaymentMethod;
}

private JSONArray getAllowedCardAuthMethods() {
    return new JSONArray(SUPPORTED_METHODS);
}

private JSONArray getAllowedCardNetworks() {
    return new JSONArray(SUPPORTED_NETWORKS);
}

private JSONObject getBaseRequest() throws JSONException {
    return new JSONObject()
            .put("apiVersion", 2)
            .put("apiVersionMinor", 0);
}

private Bundle getStaticsBundle() {
    Bundle bundle = new Bundle();
    if (!TextUtils.isEmpty(mOrderId)) {
        bundle.putString("order_id", mOrderId);
    }
    return bundle;
}

private void errorShowAndRetry () {
    StripeDto stripeDto = new StripeDto();
    stripeDto.isFail = true;
    stripeDto.status = StripeDto.STRIPE_PAY_FAIL;
   statusData.setValue(stripeDto);
}

private final class PaymentResultCallback implements ApiResultCallback<PaymentIntentResult> {
    @NonNull
    private final WeakReference<PaymentActivity> activityRef;

    PaymentResultCallback(@NonNull PaymentActivity activity) {
        activityRef = new WeakReference<>(activity);
    }

    @Override
    public void onSuccess(@NonNull PaymentIntentResult result) {
        PaymentActivity activity = activityRef.get();
        if (activity == null) {
            return;
        }

        PaymentIntent paymentIntent = result.getIntent();
        PaymentIntent.Status status = paymentIntent.getStatus();
        if (status == PaymentIntent.Status.Succeeded) {
            PaymentNetManager.verifyStripeOrder(true,mOrderId, statusData);
        } else if (status == PaymentIntent.Status.RequiresPaymentMethod) {
            errorShowAndRetry();
        }
        Logger.logE("live pay, stripe succ, status = " + status);
    }

    @Override
    public void onError(@NonNull Exception e) {
        PaymentActivity activity = activityRef.get();
        if (activity == null) {
            return;
        }
        errorShowAndRetry();

        Logger.logE("live pay, stripe succ, error = " + e.getMessage());
    }
}

}

```


希望可以幫助遇到同樣問題的你😁😁😁😁 如有建議和意見,請及時溝通。