首页 > 解决方案 > addPaymentMethod Stripe + firebase android

问题描述

我正在尝试使用 Firebase 将 Stripe 添加到我的 android 应用程序中。我在 gradle.properties 中设置可发布密钥,在 gradle.build 中设置 buildype 然后在应用程序中调用BuildConfig.PublishableKey

每次我尝试添加卡时,都会弹出一个带有此警告的对话框...

9 标头值处的意外字符 0x0a:bearer pk_test_xxxxkeyxxxx

有任何想法吗?在他们的 Firebase 移动支付 android 之后,我也在使用来自 stripe 的预构建 UI(presentPaymentMethodSelection())


var RC_SIGN_IN = 1
class MainActivityStripe : AppCompatActivity() {
    private var currentUser: FirebaseUser? = null
    private lateinit var paymentSession: PaymentSession
    private lateinit var selectedPaymentMethod: PaymentMethod
    private val stripe: Stripe by lazy { Stripe(applicationContext,
        PaymentConfiguration.getInstance(applicationContext).publishableKey) }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main_stripe)
        currentUser = FirebaseAuth.getInstance().currentUser
        payButton.isEnabled = false

        loginButton.setOnClickListener {
            // login to firebase
            val providers = arrayListOf(
                AuthUI.IdpConfig.EmailBuilder().build())

            startActivityForResult(
                AuthUI.getInstance()
                    .createSignInIntentBuilder()
                    .setAvailableProviders(providers)
                    .build(),
                RC_SIGN_IN
            )
        }

        payButton.setOnClickListener {
            confirmPayment(selectedPaymentMethod.id!!)
        }

        paymentmethod.setOnClickListener {
            Toast.makeText(applicationContext, PaymentConfiguration.getInstance(applicationContext).publishableKey, Toast.LENGTH_LONG).show()
            // Create the customer session and kick start the payment flow
            paymentSession.presentPaymentMethodSelection()
        }

        showUI()
    }

    private fun confirmPayment(paymentMethodId: String) {

        val paymentCollection = Firebase.firestore
            .collection("stripe_customers").document(currentUser?.uid?:"")
            .collection("payments")

        // Add a new document with a generated ID
        paymentCollection.add(hashMapOf(
            "amount" to 8800,
            "currency" to "cad"
        ))
            .addOnSuccessListener { documentReference ->
                Log.d("paymentsss", "DocumentSnapshot added with ID: ${documentReference.id}")
                documentReference.addSnapshotListener { snapshot, e ->
                    if (e != null) {
                        Log.w("paymentsss", "Listen failed.", e)
                        return@addSnapshotListener
                    }

                    if (snapshot != null && snapshot.exists()) {
                        Log.d("paymentsss", "Current data: ${snapshot.data}")
                        val clientSecret = snapshot.data?.get("client_secret")
                        Log.d("paymentsss", "Create paymentIntent returns $clientSecret")
                        clientSecret?.let {
                            stripe.confirmPayment(this, ConfirmPaymentIntentParams.createWithPaymentMethodId(
                                paymentMethodId,
                                (it as String)
                            ))

                            checkoutSummary.text = "Thank you for your payment"
                            Toast.makeText(applicationContext, "Payment Done!!", Toast.LENGTH_LONG).show()
                        }
                    } else {
                        Log.e("paymentsss", "Current payment intent : null")
                        payButton.isEnabled = true
                    }
                }
            }
            .addOnFailureListener { e ->
                Log.w("paymentsss", "Error adding document", e)
                payButton.isEnabled = true
            }
    }

    private fun showUI() {
        currentUser?.let {
            loginButton.visibility = View.INVISIBLE

            greeting.visibility = View.VISIBLE
            checkoutSummary.visibility = View.VISIBLE
            payButton.visibility = View.VISIBLE
            paymentmethod.visibility = View.VISIBLE

            greeting.text = "Hello ${it.displayName}"

            setupPaymentSession()
        }?: run {
            // User does not login
            loginButton.visibility = View.VISIBLE

            greeting.visibility = View.INVISIBLE
            checkoutSummary.visibility = View.INVISIBLE
            paymentmethod.visibility = View.INVISIBLE
            payButton.visibility = View.INVISIBLE
            payButton.isEnabled = false

        }
    }

    private fun setupPaymentSession () {
        // Setup Customer Session
        CustomerSession.initCustomerSession(this, FirebaseEphemeralKeyProvider())
        // Setup a payment session
        paymentSession = PaymentSession(this, PaymentSessionConfig.Builder()
            .setShippingInfoRequired(false)
            .setShippingMethodsRequired(false)
            .setBillingAddressFields(BillingAddressFields.None)
            .setShouldShowGooglePay(true)
            .build())

        paymentSession.init(
            object: PaymentSession.PaymentSessionListener {
                override fun onPaymentSessionDataChanged(data: PaymentSessionData) {
                    Log.d("PaymentSession1", "11PaymentSession has changed: $data")
                    Log.d("PaymentSession11", "1111 ${data.isPaymentReadyToCharge} <> ${data.paymentMethod}")

                    if (data.isPaymentReadyToCharge) {
                        Log.d("PaymentSession2", "222Ready to charge");
                        payButton.isEnabled = true

                        data.paymentMethod?.let {
                            Log.d("PaymentSession3", "333PaymentMethod $it selected")
                            paymentmethod.text = "${it.card?.brand} card ends with ${it.card?.last4}"
                            selectedPaymentMethod = it
                        }
                    }
                }

                override fun onCommunicatingStateChanged(isCommunicating: Boolean) {
                    Log.d("PaymentSession4",  "444isCommunicating $isCommunicating")
                }

                override fun onError(errorCode: Int, errorMessage: String) {
                    Log.e("PaymentSession5",  "555onError: $errorCode, $errorMessage")
                }
            }
        )

    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == RC_SIGN_IN) {
            val response = IdpResponse.fromResultIntent(data)

            if (resultCode == Activity.RESULT_OK) {
                currentUser = FirebaseAuth.getInstance().currentUser

                Log.d("Login", "User ${currentUser?.displayName} has signed in.")
                Toast.makeText(applicationContext, "Welcome ${currentUser?.displayName}", Toast.LENGTH_SHORT).show()
                showUI()
            } else {
                Log.d("Login", "Signing in failed!")
                Toast.makeText(applicationContext, response?.error?.message?:"Sign in failed", Toast.LENGTH_LONG).show()
            }
        } else {
            paymentSession.handlePaymentData(requestCode, resultCode, data ?: Intent())
        }
    }

}**
'use strict';
  const functions = require('firebase-functions');
  const admin = require('firebase-admin');

  const { Logging } = require('@google-cloud/logging');
  const logging = new Logging({
    projectId: process.env.GCLOUD_PROJECT,
  });

  admin.initializeApp();

  const stripe = require('stripe')(functions.config().stripe.secret, {
     apiVersion: '2020-03-02',
   });

  exports.createEphemeralKey = functions.https.onCall(async (data, context) => {
      // Checking that the user is authenticated.
      if (!context.auth) {
        // Throwing an HttpsError so that the client gets the error details.
        throw new functions.https.HttpsError(
          'failed-precondition',
          'The function must be called while authenticated!'
        );
      }

      const uid = context.auth.uid;
      try {
            if (!uid) throw new Error('Not authenticated!');
                // Get stripe customer id
            const customer = (
              await admin.firestore().collection('stripe_customers').doc(uid).get()
            ).data().customer_id;
            const key = await stripe.ephemeralKeys.create(
              { customer: customer },
              { apiVersion: data.api_version }
            );
            return key;
      } catch (error) {
            throw new functions.https.HttpsError('internal', error.message);
      }
  });

  exports.createStripeCustomer = functions.auth.user().onCreate(async (user) => {
        const customer = await stripe.customers.create({
          email: user.email,
          metadata: { firebaseUID: user.uid },
        });

        await admin.firestore().collection('stripe_customers').doc(user.uid).set({
          customer_id: customer.id,
        });
        return;
  });

  exports.createStripePayment = functions.firestore
    .document('stripe_customers/{userId}/payments/{pushId}')
    .onCreate(async (snap, context) => {

    const { amount, currency } = snap.data();

    try {
      // Look up the Stripe customer id.
      const customer = (await snap.ref.parent.parent.get()).data().customer_id;

      // Create a charge using the pushId as the idempotency key
      // to protect against double charges.
      const idempotencyKey = context.params.pushId;
      const payment = await stripe.paymentIntents.create(
        {
          amount,
          currency,
          customer,
        },
        { idempotencyKey }
      );

      // If the result is successful, write it back to the database.
      await snap.ref.set(payment);
    } catch (error) {

      // We want to capture errors and render them in a user-friendly way, while
      // still logging an exception with StackDriver
      console.log(error);
      await snap.ref.set({ error: userFacingMessage(error) }, { merge: true });
      await reportError(error, { user: context.params.userId });
    }
  });

    const updatePaymentRecord = async (id) => {

          // Retrieve the payment object to make sure we have an up to date status.
          const payment = await stripe.paymentIntents.retrieve(id);
          const customerId = payment.customer;

          // Get customer's doc in Firestore.
          const customersSnap = await admin
            .firestore()
            .collection('stripe_customers')
            .where('customer_id', '==', customerId)
            .get();

          if (customersSnap.size !== 1) throw new Error('User not found!');

          // Update record in Firestore
          const paymentsSnap = await customersSnap.docs[0].ref
            .collection('payments')
            .where('id', '==', payment.id)
            .get();

          if (paymentsSnap.size !== 1) throw new Error('Payment not found!');

          await paymentsSnap.docs[0].ref.set(payment);
    };

    exports.cleanupUser = functions.auth.user().onDelete(async (user) => {
          const dbRef = admin.firestore().collection('stripe_customers');
          const customer = (await dbRef.doc(user.uid).get()).data();
          await stripe.customers.del(customer.customer_id);
          // Delete the customers payments & payment methods in firestore.
          const snapshot = await dbRef
            .doc(user.uid)
            .collection('payment_methods')
            .get();
          snapshot.forEach((snap) => snap.ref.delete());
          await dbRef.doc(user.uid).delete();
          return;
    });

    exports.handleWebhookEvents = functions.https.onRequest(async (req, resp) => {
      const relevantEvents = new Set([
        'payment_intent.succeeded',
        'payment_intent.processing',
        'payment_intent.payment_failed',
        'payment_intent.canceled',
      ]);

      let event;

      try {
        event = stripe.webhooks.constructEvent(
          req.rawBody,
          req.headers['stripe-signature'],
          functions.config().stripe.webhooksecret
        );
      } catch (error) {
        console.error('❗️ Webhook Error: Invalid Secret');
        resp.status(401).send('Webhook Error: Invalid Secret');
        return;
      }

      if (relevantEvents.has(event.type)) {
        try {
          switch (event.type) {
            case 'payment_intent.succeeded':
            case 'payment_intent.processing':
            case 'payment_intent.payment_failed':
            case 'payment_intent.canceled':{
              const id = event.data.object.id;
              await updatePaymentRecord(id);
              break;
            }
            default:
              throw new Error('Unhandled relevant event!');
          }
        } catch (error) {
          console.error(
            `❗️ Webhook error for [${event.data.object.id}]`,
            error.message
          );
          resp.status(400).send('Webhook handler failed. View Function logs.');
          return;
        }
      }

      // Return a response to Stripe to acknowledge receipt of the event.
      resp.json({ received: true });
    });

function reportError(err, context = {}) {
  // This is the name of the StackDriver log stream that will receive the log
  // entry. This name can be any valid log stream name, but must contain "err"
  // in order for the error to be picked up by StackDriver Error Reporting.
  const logName = 'errors';
  const log = logging.log(logName);

  // https://cloud.google.com/logging/docs/api/ref_v2beta1/rest/v2beta1/MonitoredResource
  const metadata = {
    resource: {
      type: 'cloud_function',
      labels: { function_name: process.env.FUNCTION_NAME },
    },
  };

  // https://cloud.google.com/error-reporting/reference/rest/v1beta1/ErrorEvent
  const errorEvent = {
    message: err.stack,
    serviceContext: {
      service: process.env.FUNCTION_NAME,
      resourceType: 'cloud_function',
    },
    context: context,
  };

  // Write the error log entry
  return new Promise((resolve, reject) => {
    log.write(log.entry(metadata, errorEvent), (error) => {
      if (error) {
        return reject(error);
      }
      return resolve();
    });
  });
}

// [END reporterror]

/**
 * Sanitize the error message for the user.
 */
function userFacingMessage(error) {
  return error.type
    ? error.message
    : 'An error occurred, developers have been alerted';
}
buildTypes.each {
    it.buildConfigField 'String', 'PublishableKey', stripePublishableKey
}

import android.util.Log
import com.google.firebase.functions.FirebaseFunctionsException
import com.google.firebase.functions.ktx.functions
import com.google.firebase.ktx.Firebase
import com.stripe.android.EphemeralKeyProvider
import com.stripe.android.EphemeralKeyUpdateListener

class FirebaseEphemeralKeyProvider: EphemeralKeyProvider {

    override fun createEphemeralKey(
        apiVersion: String,
        keyUpdateListener: EphemeralKeyUpdateListener
    ) {
        val data = hashMapOf(
            "api_version" to apiVersion
        )

        // User firebase to call the functions
        Firebase.functions
            .getHttpsCallable("createEphemeralKey")
            .call(data)
            .continueWith { task ->
                if (!task.isSuccessful) {
                    val e = task.exception
                    if (e is FirebaseFunctionsException) {
                        val code = e.code
                        val message = e.message
                        Log.e("EphemeralKey", "Ephemeral key provider returns error: $e $code $message")
                    }
                }
                val key = task.result?.data.toString()
                Log.d("EphemeralKey", "Ephemeral key provider returns $key")
                keyUpdateListener.onKeyUpdate(key)
            }
    }

}

import android.app.Application
import com.stripe.android.PaymentConfiguration

class MyApp : Application(){
    override fun onCreate() {
        super.onCreate()
        PaymentConfiguration.init(applicationContext, BuildConfig.PublishableKey)
    }
}```
>2020-09-07 09:23:29.753 15967-15967/com.asgd.indigenoussource D/PaymentSession4: 444isCommunicating true
>2020-09-07 09:23:29.754 15967-15967/com.asgd.indigenoussource D/PaymentSession1: 11PaymentSession has changed: PaymentSessionData(isShippingInfoRequired=false, isShippingMethodRequired=false, cartTotal=0, shippingTotal=0, shippingInformation=null, shippingMethod=null, paymentMethod=null, useGooglePay=false)
>2020-09-07 09:23:29.754 15967-15967/com.asgd.indigenoussource D/PaymentSession11: 1111 false <> null
>2020-09-07 09:23:31.636 15967-15967/com.asgd.indigenoussource D/PaymentSession4: 444isCommunicating false

标签: androidfirebasekotlinstripe-payments

解决方案


在从有条纹的人那里得到一些帮助后,它就可以正常工作了。我的可发布密钥在 gradle 中发生了一些变化,并且该密钥与设备相关联。我必须制作新的模拟设备并重置所有内容


推荐阅读