1
votes

I'm working on a project with angular 6, Stripe elements and Google's Firebase (with Cloud Functions). All are new to me!

For the life of me, I'm unable to figure out how I can return 'something' which states that the payment has been successful. Stripe API docs, state that it only returns an error call if there is an 'error'...

I can see that the card is being charged successfully from the charge object in Firebase.

What can I use to query this and return the 'status: paid' value to my front-end...so I can use an *ngIf to display confirmation/failure message?

I know i'm missing something dead simple here...! I really appreciate any help with this guys.

index.js (cloud function)

const functions = require('firebase-functions');
const admin = require('firebase-admin')

admin.initializeApp(functions.config().firebase);

const stripe = require('stripe') 
(functions.config().stripe.testkey)

exports.stripeCharge = functions.database
.ref('/payments/{paymentId}')
.onWrite((change, context) => {
const payment = change.after.val();

const paymentId = context.params.paymentId;

// checks if payment exists or if it has already been charged
if (!payment || payment.charge) {
  return
}

return admin.database()
  .ref('/users/')
  .once('value')
  .then(snapshot => {
    return snapshot.val()
  })

  .then(customer => {
    const amount = payment.amount;
    const idempotency_key = paymentId; // prevent duplicate charges
    const source = payment.token.id;
    const currency = 'gbp';
    const charge = { amount, currency, source };

    return stripe.charges.create(charge, { idempotency_key });
  })

  .then(charge => {
    admin.database()
      .ref(`/payments/${paymentId}/charge`)
      .set(charge)
      return true;
  })
});

Payment.service.ts

import { Injectable } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';

@Injectable()
export class PaymentService { 

constructor(private db: AngularFireDatabase) {}

// save the token to firebase, triggering the cloud function
processPayment(token: any, amount) {
  const payment = { token, amount }
  return this.db.list('/payments/').push(payment)
  }
}

payment.component.ts (here's my onSubmit handler for the checkout)

async onSubmit(form: NgForm) {
//this.paymentProcess = true;
const { token, error } = await stripe.createToken(this.card, {
  name: this.contactName,
  email: this.contactEmail
});

if (error) {  
  console.log('Something is wrong:', error);
} else {
  console.log('Success!', token);
    this.paymentSvc.processPayment(token, this.amount);
  }
  this.card.clear();
}
1
Just a small remark: you say that you are "working on a project with ... Google's Firestore" but note that your code is about the Firebase Realtime Database. There are two different database services offered by Firebase.Renaud Tarnec
Brilliant, thanks ever so much Renaud. Thank you for flagging the 'Firestore' correction also.jason ewins

1 Answers

0
votes

You should modify your Cloud Function code as follows, in order to return the promise returned by the asynchronous .set(charge) method.

exports.stripeCharge = functions.database
.ref('/payments/{paymentId}')
.onWrite((change, context) => {
const payment = change.after.val();

const paymentId = context.params.paymentId;

// checks if payment exists or if it has already been charged
if (!payment || payment.charge) {
  return
}

return admin.database()
  .ref('/users/')
  .once('value')
  .then(snapshot => {
    return snapshot.val()
  })

  .then(customer => {
    const amount = payment.amount;
    const idempotency_key = paymentId; // prevent duplicate charges
    const source = payment.token.id;
    const currency = 'gbp';
    const charge = { amount, currency, source };

    return stripe.charges.create(charge, { idempotency_key });
  })

  .then(charge => {
    return admin.database()  <-- Here return !!!!!!
      .ref(`/payments/${paymentId}/charge`)
      .set(charge);
      //return true;    <-- Here don't return !!!!!!
  })
});

In order to "return the 'status: paid' value to the front-end..", just set a listener to the /payments/${paymentId}/charge path and as soon as the charge has the correct status value, update your front-end.


Finally, note that with:

  ...
  .then(charge => {
    admin.database()
      .ref(`/payments/${paymentId}/charge`)
      .set(charge)
      return true;
  })

you were returning the value true to the Cloud Function platform, (indicating that the Function can be terminated) before the set() asynchronous operation was completed.