Google Pay 谷歌支付(gateway = stripe)

Google pay 国际版开发


一、官网地址(科学上网)

官方对接文档 https://developers.google.com/pay/api/android/overview
Stripe对接Google Pay https://stripe.com/docs/google-pay
Stripe验证支付 https://stripe.com/docs/payments/accept-a-payment?integration=elements
Stripe管理后台 https://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<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();
        // 设置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配置

配置地址 https://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());
        }
    }
}



希望可以帮助遇到同样问题的你😁😁😁😁
如有建议和意见,请及时沟通。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值