skip to Main Content

I am trying to setup a tutorial content page where users pay through a stripe checkout and once, they have done so they can then view the content on the page.

I am using firebase’s firestore and the firebase run stripe payments extension for the payments and everything is working fine as far as the checkout and once it is completed it gives me the new document within the payments collection with the amount and the status and everything.

enter image description here

The problem is I have multiple different products with different prices so I cannot use the status field to query whether it should display the content or not. Also I had tried using the amount field returned to filter through the payments but I soon learnt that if the customer is paying in a foreign currency the amount is changed and therefore will not work either.

Below is my query filtering code with the failed amount field being used as the query which I currently have.

import PriceSelection1 from "./DildoRiding/PriceSelection1.vue";
import SubscribedAccountSignup1 from "./DildoRiding/SubscribedAccountSighnup1.vue";
import { firebaseAuth } from "../../Firebase/firebase";
import { ref } from 'vue'
import {
  getFirestore,
  collection,
  query,
  where,
  getDocs,
} from "firebase/firestore";
export default {
    components: {
    PriceSelection1,
    SubscribedAccountSignup1
  },
  data() {
    return {
      isLoading: false,
      Purchased2: null,
    };
  },

  mounted() {
    this.fetchSubscription();
  },

  methods: {
    async fetchSubscription() {
      this.isLoading = true;
      const db = getFirestore();
      const subsRef = collection(
        db,
        "Subscribed", firebaseAuth.currentUser.uid, "payments"
      );
      const subsQuery = query(
        subsRef, 
        where("amount_received", "==", 5000 )
      );

      this.subscription = await getDocs(subsQuery)
        .then((sub) => 
            sub.docs.length > 0 ? sub.docs[0].data() : null);

      this.isLoading = false;
    },
  },
  setup(){
    const Activate = ref(false)
  return { Activate }
}
}
</script>

<template>
  <div v-if="isLoading">Loading account information...</div>
            <PriceSelection1 v-else-if="!subscription" />
           <SubscribedAccountSignup1 v-else :subscription="subscription"/>
           <br>
           <div v-if="subscription" class="inline-block">
            <p class="text-2xl text-red-700 font-serif inline">Cookie Recipes Purchased</p><br><br>
            <video width="320" height="240" controls>
    <source src="https://vid2">
    Your browser does not support the video tag.
  </video>
  <video width="320" height="240" controls>
    <source src="https://vid1">
    Your browser does not support the video tag.
  </video>
        <br><br>
           </div>
</template>

I would also like to clarify that I am using vue for the display page. I will also include the priceselection page so you can see everything there below.

<script>
import {
  getFirestore,
  getDocs,
  where,
  query,
  collection,
  addDoc,
  onSnapshot,
} from "firebase/firestore";
import { projectFirestore } from '../../../Firebase/Config'
import { firebaseAuth } from "../../../Firebase/firebase";

export default {
  data() {
    return {
      products: [],
      selectedPrice: null,
      isLoading: false,
    };
  },
  mounted() {
    this.fetchProducts();
  },
  methods: {
    async fetchProducts() {
      const db = getFirestore();

      const productsRef = collection(db, "products");
      const productsQuery = query(productsRef, where("name", "==", "Cookie Recipes"));
      const productsQuerySnap = await getDocs(productsQuery);

      productsQuerySnap.forEach(async (doc) => {
        const pricesRef = collection(db, "products", doc.id, "prices");
        const pricesQuerySnap = await getDocs(pricesRef);

        this.products.push({
          id: doc.id,
          ...doc.data(),
          prices: pricesQuerySnap.docs.map((price) => {
            return {
              id: price.id,
              ...price.data(),
            };
          }),
        });
      });
    },
    async createSub() {
      this.isLoading = true;
      const db = getFirestore();
      const collectionRef = collection(
        db,
        "Subscribed",
        firebaseAuth.currentUser.uid,
        "checkout_sessions"
      );
      const docRef = await addDoc(collectionRef, {
        mode: "payment",
        price: this.selectedPrice,
        success_url: window.location.href,
        cancel_url: window.location.href,
        
      });

      onSnapshot(docRef, (snap) => {
        const { error, url } = snap.data();
        if (error) {
          console.error(`An error occured: ${error.message}`);
          
          this.isLoading = false;
        }

        if (url) {
          window.location.assign(url);


        }
       
      });
    },
  },
};
</script>
<template>
    <div class="row">
      <div class="col">
        <br><br>
        <h2 class="text-5xl text-red-700 font-serif">Cookie recipes</h2>
        <br>
        <hr />
        
  <br><br>
        <div v-for="(product, index) in products" 
              :key="index + '-product'">
          <h3 class="text-3xl text-red-700 font-serif">{{ product.name }}</h3>
          <br>
  
          <div
            v-for="(price, priceIndex) in product.prices"
            :key="priceIndex + '-price'">
            <div class="form-check">
  
              <input
                type="radio"
                
                v-model="selectedPrice"
                :value="price.id"
                :id="price.id"
                :label="price.unit_amount" />
              
              <label :for="price.id"  class="cursor-pointer bg-neutral-800 hover:bg-neutral-900 active:bg-neutral-800 hover:scale-105 text-red-700 text-xl w-80 p-4 rounded-xl hover:text-neutral-400 shadow inline-block mx-10 transition ease-in-out duration-300 drop-shadow-2xl">
                {{ price.interval_count }}
                {{ price.interval }}
                {{ price.id.price}}
                ${{ price.unit_amount/100 }}
              </label><br><br>
  
            </div>
          </div>
        </div>
  
        <button
        class="bg-neutral-800 hover:bg-neutral-900 active:bg-neutral-800 text-red-700 hover:text-neutral-400 hover:scale-105 text-xl w-64 p-4 rounded-xl shadow cursor-pointer focus:cursor-wait inline-block m-10 transition ease-in-out duration-300 drop-shadow-2xl"
          target="_blank"
          @click="createSub"
          :disabled="!selectedPrice || isLoading"
        >
          {{ isLoading ? "Loading..." : "Purchase Content" }}
        </button>
      </div>
    </div>
  </template>

The path I originally went down to try and find a solution was the stripe checkout fulfillment in the stripe docs https://docs.stripe.com/checkout/fulfillment
Unfortunately, I wasn’t able to find anything about stripe fulfillment in the firebase documentation and I wasn’t able to piece together how this would work with the firebase extension.

All I really need to be able to do is add an extra field within the document returned when the checkout session is completed successfully that way, I could use the new field containing ProductId or something along those lines with the status field to query and then if it is correct display the content. So to clarify this needs to be done in the stripe document that gets returned because I have no way of telling if that specific product has been paid for after the checkout session has completed other than manually reading through the firestore document.

Is this possible to do and if so, how?
And if not is there another way to achieve what I am trying to do?

————After Edit———-
Unfortunately in my checkout_sessions documents I do not have a completed or status successful field in that document to say that it succeeded the payment the document is added as soon as I am routed to the checkout page and no succeeded is added in the checkout_sessions document just the payments document and since I have more than 1 product that will need to add a document there in that collection on checkout I am still unable to see which product was purchased to display the content.

enter image description here

Also the payment_intent_data only seems to show up with the checkout_sessions documents and doesn’t seem to have any bearing on the payments collection documents that are returned at all.

I was thinking if there was a way to set the document id to the product ID instead of that random pi_3PV… then I could just make a composable for each product I have and then query the status: succeeded for the query and I think that could work is this possible?

2

Answers


  1. Chosen as BEST ANSWER

    I have found a work around that achieves what I wanted to do it is not ideal but it works.

    I changed the success_url from the current page to a landing page I added.

          const docRef = await addDoc(collectionRef, {
            mode: "payment",
            price: this.selectedPrice,
            success_url: "https://website//Landing1",
            cancel_url: window.location.href,
    
          });
    

    Then on my new landing page for each product I add a new document with the productId when the page loads using vues onMounted and at the end of this function I route back to the original page this then has a document containing the productID I can then use to query back on the original page.

    <script>
    import { onMounted } from "vue"
    import { useRouter } from 'vue-router'
    import { projectFirestore } from '../../Firebase/Config'
    import { firebaseAuth } from "../../Firebase/firebase";
    export default{
        setup() {
    const ProductID = "prod_QIRNX4klvLTfKe"
    const router = useRouter()
    
    const Product = async () => {
        const profile = {
            ProductID: ProductID,   
          }
    
          const res = await projectFirestore.collection('Subscribed').doc(firebaseAuth.currentUser.uid).collection('payments').add(profile)
           //console.log(res) // can see the id and path of doc created
          router.push({ name: 'OriginalPage' })
        
    }
    onMounted(() => {
        Product()
    })
    return { Product }
    }
    }
    
    </script>
    
    <template>
    
    </template>
    
    <style>
    
    </style>
    

    I will probably add some sort of load feature for looks but this shouldn't take too long to update and it seems to work fine for me.

    Thanks everyone for the other ideas.


  2. I would recommend trying something like this:

    const docRef = await addDoc(collectionRef, {
        mode: "payment",
        price: this.selectedPrice,
        success_url: window.location.href,
        cancel_url: window.location.href,
        metadata: {
          field: "value" // Should add field to checkout session document
        },
        payment_intent_data: {
           metadata: {
              field: "value" // Should add field to payments document
           }
        }
      });
    

    Worth noting that this is Stripe knowledge and not Firebase knowledge. Since what you’re using is a Firebase library, I’m not a 100% positive this will work, however the arguments you set in your addDoc function appear to be a 1-to-1 match for Stripe’s checkout session create call, which suggests this should work.

    — Edit post comments —

    Since what you need is an attribute on the top level object (and not child attributes like it would be with metadata), then I recommend using client_reference_id:

    const docRef = await addDoc(collectionRef, {
        mode: "payment",
        price: this.selectedPrice,
        success_url: window.location.href,
        cancel_url: window.location.href,
        client_reference_id: "value" // Should add field to checkout session document (top level)
      });
    

    I’m not sure if you have limitations related to the data type of that argument – though it can only be strings anyway.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search