Am trying to add Stripe to my android app with Firebase. I set the publishable key in gradle.properties and builtype in gradle.build then call in application as BuildConfig.PublishableKey
Every time I try to add card a dialog pops up with this warning...
unexpected char 0x0a at 9 header value: bearer pk_test_xxxxkeyxxxx
Any ideas? Am using prebuilt UI from stripe too (presentPaymentMethodSelection() ) following their Firebase mobile payments android
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