Update At Bottom
I am trying to build a signup page in my Android app that signs users up for a subscription through Stripe. What I am stuck on is adding a payment source from Android, through a cloud function, and receive a token from Stripe.
I currently have solved, automatically adding a newly created User to Stripe. As well creating the subscription when (/users/{userId}/membership/token) is written to, or changed.
On Android I am able to obtain the credit card data through the input..
PaymentMethodCreateParams.Card card = cardInputWidget.getPaymentMethodCard();
I next need to submit this to my cloud function by using..
mFunctions = FirebaseFunctions.getInstance();
mFunctions.getHttpsCallable("addPaymentSource")
.call()
.addOnCompleteListener(task -> {
...
Being I am having trouble finding information on this, here is all I have for this cloud function (Javascript)
exports.addPaymentSource = functions.https.onCall((data, context) =>{
const pm = await stripe.paymentMethods.attach('pm_678', {customer: 'cus_123'});
return admin.firestore().collection('users').doc(user.uid).get('membership').set({token: token});
}
I need to obtain the customer number which is saved at - /users/{user.uid}/customerId'. As well pass the payment method through my http data call, and pass/obtain the user_id (which would have been created long before all this).
I got this far watching this youtube video and converting my code over. Subscription Payments with Stripe, Angular, and Firebase
I also referenced Stripe's Cloud Function examples quite a bit. The one issue is everyone seems to be using this code (below), which doesn't work in my implementation. With most guides/examples not being used for Subscriptions.
// Add a payment source (card) for a user by writing a stripe payment source token to Cloud Firestore
exports.addPaymentSource = functions.firestore.document('/stripe_customers/{userId}/tokens/{pushId}').onCreate(async (snap, context) => {
const source = snap.data();
const token = source.token;
if (source === null){
return null;
}
try {
const snapshot = await admin.firestore().collection('stripe_customers').doc(context.params.userId).get();
const customer = snapshot.data().customer_id;
const response = await stripe.customers.createSource(customer, {source: token});
return admin.firestore().collection('stripe_customers').doc(context.params.userId).collection("sources").doc(response.fingerprint).set(response, {merge: true});
} catch (error) {
await snap.ref.set({'error':userFacingMessage(error)},{merge:true});
return reportError(error, {user: context.params.userId});
}
});
Update:
Made some small changes to get try and get this to work..
exports.addPaymentSource = functions.https.onCall((data, context) =>{
///users/{userId}/membership/token
// Create Payment Method
const paymentMethod = stripe.paymentMethods.create(
{
type: 'card',
card: {
number: '4242424242424242',
exp_month: 5,
exp_year: 2021,
cvc: '314',
},
}).then(pm => {
console.log('paymentMethod: ', paymentMethod.id);
return stripe.paymentMethods.attach(paymentMethod.id, { customer: 'cus_HCQNxmI5CSlIV5' })
.then(pm => {
return admin.firestore().collection('users').doc(user.uid).get('membership').set({token: pm.id});
});
});
});
I am getting close, the problem is paymentMethod.id is 'undefined'
# Answer 1
While I'm not a Firebase expert, on your Android side, you want to call your cloud function with parameters of the Customer ID and PaymentMethod ID in order to pass them to your cloud function.
Then in your cloud function, you want to attach the PaymentMethod to the Customer (as you are doing using stripe-node) and make it the Customer's default for Subscriptions, as shown here: https://stripe.com/docs/billing/subscriptions/payment#signup-3
Then, you should create a Subscription on the Customer for a particular Plan, again using stripe-node, as shown here https://stripe.com/docs/billing/subscriptions/payment#signup-4
# Answer 2
Here I have my functioning code. (I used some placeholder data to fill in the variables)
exports.addPaymentSource = functions.https.onCall((data, context) =>{
// Create Payment Method
stripe.paymentMethods.create( {
type: 'card',
card: {
number: '4242424242424242',
exp_month: 5,
exp_year: 2021,
cvc: '314',
},
})
.then(pm => {
return stripe.paymentMethods.attach(pm.id, { customer: 'cus_HCCNMAAwRhNM3c' })
})
.then(pm => {
console.log('final step');
console.log('paymentMethod: ', pm.id);
admin.firestore().collection('users').doc('LzgbQBtk0QSZi7QISIbV').set({token: pm.id});
return admin.firestore().collection('users').doc(user.uid).collection('membership').set({token: pm.id});
})
.catch(error => { return null });
});
So I manually pasted in some variables to confirm my features were functioning. The CustomerID and card details need to be passed in from the Android app. These card details are the only ones I should need for a subscription
'pm' is the returned Payment Method object, in which id is the variable that needs to be attached to the user.
Finally pm.id is the token that must be saved inside into the firestore. Doing this triggers my subscription setup cloud function(not displayed).
The code displayed shows how to avoid nested then statements, and Android firestore direct function calling. While also not shown, the data field can call upon any variable's key word "data.cardnbr".
The method avoids any use of SetupIntents. While this is incredibly effective for Subscription based charging, it might not be best practice for direct charges.