Giter VIP home page Giter VIP logo

stripe-samples / checkout-single-subscription Goto Github PK

View Code? Open in Web Editor NEW
705.0 15.0 328.0 5.48 MB

Learn how to combine Checkout and Billing for fast subscription pages

Home Page: https://checkout.stripe.dev/?mode=subscription

License: MIT License

HTML 4.76% CSS 27.35% JavaScript 6.45% Java 6.76% PHP 9.60% Python 5.34% Ruby 24.64% C# 7.54% Go 5.95% Hack 1.60%
stripe-checkout stripe-billing stripe

checkout-single-subscription's Introduction

Using Checkout for subscriptions

Checkout is a pre-built payment page that lets you accept cards and Apple Pay. Billing is a suite of APIs that lets you model complex subscription plans. You can combine the two products to get a subscription payment page up and running without the need of a server.

When your customer is ready to pay, use Stripe.js with the ID of your Checkout Session to redirect them to your Checkout page.

A gif of the Checkout payment page rendering

Demo

See the sample of the integration live or fork the Node implementation on CodeSandbox.

The demo is running in test mode -- use 4242424242424242 as a test card number with any CVC + future expiration date.

Use the 4000002500003155 test card number to trigger a 3D Secure challenge flow.

Read more about testing on Stripe at https://stripe.com/docs/testing.

Features:

  • Localization in over 25 different languages 🌍
  • Built-in Apple Pay support 🍎
  • Built-in dynamic 3D Secure (ready for SCA) πŸ””

For more features see the Checkout documentation.

The integration uses the Checkout Sessions API for additional functionality.

main
πŸ”¨ Prebuilt checkout page. Create a payment page that is customizable with your business' name and logo. βœ…
πŸ–₯️ Define prices in Dashboard or via API. Create a price with either the Stripe Dashboard or API. βœ…
πŸ”’ Start subscription for an existing Customer. Use Customers to keep track of additional customer data. βœ…

How to run locally

This sample includes 8 server implementations in Node, Ruby, Python, Java, PHP, PHP with Slim, Go and .NET.

Follow the steps below to run locally.

1. Clone and configure the sample

The Stripe CLI is the fastest way to clone and configure a sample to run locally.

Cloning using the Stripe CLI

If you haven't already installed the CLI, follow the installation steps in the project README. The CLI is useful for cloning samples and locally testing webhooks and Stripe integrations.

In your terminal shell, run the Stripe CLI command to clone the sample:

./stripe samples create checkout-single-subscription

The CLI will walk you through picking your server and client languages and configuring your .env config file with your Stripe API keys.

Cloning manually

If you do not want to use the Stripe CLI, you can manually clone and configure the sample yourself:

git clone https://github.com/stripe-samples/checkout-single-subscription

Copy the .env.example file into a file named .env in the folder of the server you want to use. For example:

cp .env.example server/node/.env

You will need a Stripe account in order to run the demo. Once you set up your account, go to the Stripe developer dashboard to find your API keys.

STRIPE_PUBLISHABLE_KEY=<replace-with-your-publishable-key>
STRIPE_SECRET_KEY=<replace-with-your-secret-key>

2. Create Products and Prices on Stripe

This sample requires two Price IDs to create the Checkout page. Products and Prices are objects on Stripe that let you model a subscription.

Using the Stripe CLI

Create basic product

./stripe products create --name="Basic" --description="Basic plan"

Create premium product

./stripe products create --name="Premium" --description="Premium plan"

Take note of the id value for the products you just created as you will need this to create prices. For example:

{
  "id": "prod_RANDOM_ID_VALUE"
}

Create price for Basic product, substituting ID_OF_BASIC_PRODUCT with the appropriate product Id:

./stripe prices create \
  -d "product=ID_OF_BASIC_PRODUCT" \
  -d "unit_amount=1800" \
  -d "currency=usd" \
  -d "recurring[interval]=month"

Create price for Premium product, substituting ID_OF_BASIC_PRODUCT with the appropriate product Id:

./stripe prices create \
  -d "product=ID_OF_PREMIUM_PRODUCT" \
  -d "unit_amount=1800" \
  -d "currency=usd" \
  -d "recurring[interval]=month"
With Stripe Tax Stripe Tax lets you calculate and collect sales tax, VAT and GST with one line of code.

Before creating a price, make sure you have Stripe Tax set up in the dashboard: Docs - Set up Stripe Tax.

Stripe needs to know what kind of product you are selling to calculate the taxes. For this example we will submit a tax code describing what kind of product is used: txcd_10000000 which is 'General - Electronically Supplied Services'. You can find a list of all tax codes here: Available tax codes. If you leave the tax code empty, Stripe will use the default one from your Tax settings.

./stripe products create \
  -d "name=Premium" \
  -d "description=Premium plan" \
  -d "tax_code=txcd_10000000"

From the response, copy the id and create a price. The tax behavior can be either inclusive or exclusive. For our example, we are using exclusive.

./stripe prices create \
  -d "unit_amount=1800" \
  -d "currency=usd" \
  -d "tax_behavior=exclusive" \
  -d "recurring[interval]=month" \
  -d "product=<INSERT_ID, like prod_ABC123>"

More Information: Docs - Update your Products and Prices

Using the Dashboard

You can create Products and Prices in the dashboard. Create two recurring Prices to run this sample.

Update BASIC_PRICE_ID and PRO_PRICE_ID in your .env file

Repeat these steps for to create a second product and price.

Next, open .env in the folder of the server you want to use, and update the values for BASIC_PRICE_ID and PRO_PRICE_ID with the price IDs of the two prices you added.

3. Confirm that you have set the account name

In order to use Checkout, you must set an account or business name at https://dashboard.stripe.com/account

4. Follow the server instructions on how to run:

Pick the server language you want and follow the instructions in the server folder README on how to run.

For example, if you want to run the Node server:

cd server/node
# There's a README in this folder with instructions to run the server and how to enable Stripe Tax.
npm install
npm start

[Optional] Customize your branding

To customize your icon, logo and colors for Checkout and the Customer Portal, go to Branding settings in the Dashboard.

[Optional] Run a webhook locally:

You can use the Stripe CLI to easily spin up a local webhook.

First install the CLI and link your Stripe account.

./stripe listen --forward-to "localhost:4242/webhook"

The CLI will print a webhook secret key to the console. Set STRIPE_WEBHOOK_SECRET to this value in your .env file.

You should see events logged in the console where the CLI is running.

When you are ready to create a live webhook endpoint, follow our guide in the docs on configuring a webhook endpoint in the dashboard.

[Optional] Adjust other environment variables

The other environment variables are configurable:

STATIC_DIR tells the server where to the client files are located and does not need to be modified unless you move the server files.

DOMAIN is the domain of your website, where Checkout will redirect back to after the customer completes the payment on the Checkout page.

FAQ

Q: Why did you pick these frameworks?

We chose the most minimal framework to convey the key Stripe calls and concepts you need to understand. These demos are meant as an educational tool that helps you roadmap how to integrate Stripe within your own system independent of the framework.

Q: What happened to Plans and SKUs?

Plans and SKUs were old ways to model recurring and one-off prices. We created the Prices API to unify the two concepts and make it easier to reason about your pricing catalog. You can still pass old Plan and SKU IDs to Checkout -- to learn more read our docs but know that you do not need to migrate any of your existing SKUs and Plans.

Get support

If you found a bug or want to suggest a new [feature/use case/sample], please file an issue.

If you have questions, comments, or need help with code, we're here to help:

Sign up to stay updated with developer news.

Author(s)

@adreyfus-stripe @cjavilla-stripe

checkout-single-subscription's People

Contributors

adreyfus-stripe avatar andybons-stripe avatar anthonylabuda avatar anuragsati avatar bc-stripe-2 avatar brendanm-stripe avatar cecilphillip avatar ch-stripe avatar charliegerard-stripe avatar cjavilla-stripe avatar ctrudeau-stripe avatar darrenthomas-dev avatar dawn-stripe avatar dependabot[bot] avatar forrest-keller avatar github-actions[bot] avatar hibariya avatar hideokamoto-stripe avatar jfobrien29 avatar jrondeau-stripe avatar kaznovac avatar kevinpeters-stripe avatar mikito-stripe avatar paulasjes-stripe avatar replaysmike avatar richardm-stripe avatar romainfd avatar thorsten-stripe avatar trag-stripe avatar vcheung-stripe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

checkout-single-subscription's Issues

Change .NET Code to use App Secrets

Bug report

.NET sample shows using env variables for retrieving secrets. There's actually a more secure way recommended by Microsoft.

Describe the bug

The current example code shows the following in Startup.cs:

services.Configure<StripeOptions>(options =>
            {
                options.PublishableKey = Environment.GetEnvironmentVariable("STRIPE_PUBLISHABLE_KEY");
                options.SecretKey = Environment.GetEnvironmentVariable("STRIPE_SECRET_KEY");
                options.WebhookSecret = Environment.GetEnvironmentVariable("STRIPE_WEBHOOK_SECRET");
                options.BasicPrice = Environment.GetEnvironmentVariable("BASIC_PRICE_ID");
                options.ProPrice = Environment.GetEnvironmentVariable("PRO_PRICE_ID");
                options.Domain = Environment.GetEnvironmentVariable("DOMAIN");
 });

However, for greater security (outlined in the next link), individuals should use App Secrets to manage these things.

Example code:

POCO:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MyNamespace
{
    /// <summary>
    /// Holds our Stripe secrets. 
    /// </summary>
    /// <remarks>
    /// This is established for DI.
    /// </remarks>
    public interface IStripeApiSecrets
    {
        /// <summary>
        /// The base URI for the API calls.
        /// </summary>
        /// <remarks>
        /// This is so we can change between sandbox and live via config.
        /// </remarks>
        Uri ApiBaseUri
        {
            get;
        }

        /// <summary>
        /// The publishable token.
        /// </summary>
        string PublishableKey
        {
            get;
        }

        /// <summary>
        /// The secret token.
        /// </summary>
        string SecretKey
        {
            get;
        }
    }

    /// <summary>
    /// POCO object for retrieving our secret Stripe API data.
    /// </summary>
    /// <remarks>
    /// See https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-3.1&tabs=linux#map-secrets-to-a-poco
    /// </remarks>
    public sealed class StripeApiSecrets : IStripeApiSecrets
    {
        /// <inheritdoc/>
        public Uri ApiBaseUri
        {
            get;
            set;
        }

        /// <inheritdoc/>
        public string PublishableKey
        {
            get;
            set;
        }

        /// <inheritdoc/>
        public string SecretKey
        {
            get;
            set;
        }
    }
}

Using it in Startup.cs:

// Init our Stripe secrets for taking payments.
var stripeSecrets = Configuration.GetSection(nameof(StripeApiSecrets)).Get<StripeApiSecrets>();
if (stripeSecrets == null)
{
    throw new InvalidDataException($"{nameof(stripeSecrets)} is null.");
}
// For DI.
services.AddSingleton<IStripeApiSecrets>(stripeSecrets);

Edit User Secrets (Windows/VS2019 method shown here):

image

Enter data into JSON configuration with matching property names:

image

To Reproduce

N/A

Expected behavior

N/A

Screenshots

If applicable, add screenshots to help explain your problem.

System information

Server Env: .NET

Additional context

N/A

The client

Bug report

Describe the bug

How to update the home page with Stripe.

I changed the value in .env file, but the price in home page don't change

Is it normal ? if yes what can I do ?

PS : When I click on the price button the redirection works and the price in payement page is good, but not in home page.

Cannot destructure property 'subscription' of 'undefined' as it is undefined at handlePaymentMethodRequired

Hi,

I have been trying to implement this checkout-single-subscription

What is working :

  • Integration Card : 4242424242424242
  • Integration Card : 4000002760003184 (3DS)

Failure :

  • Integration Card : 4000008260003178.

** Expected ** : retrieve the error message insufficient funds

** Explanation ** : The logs (see index) are able to throw the right error Your card has insufficient funds. However the error catching in the creatsubscription function fails.

Catching 8 in createSubscription = TypeError: Cannot destructure property 'subscription' of 'undefined' as it is undefined.
at handlePaymentMethodRequired (Checkoutform.js:103)

I had to create a default error showCardError({ error: { message: 'Your card was declined.' } });. because I wasn't able to use the thrown erros.

Screenshot 2020-06-24 at 12 57 51

export default function CheckoutForm(props) {
    const [priceId, setPriceId] = React.useState('price_1GqywBC2ahMs23DqyBiAZLZG');
    const [openedSuccessModal, setOpenedSuccessModal] = React.useState(false)
    const[openErrorSnack,setOpenSnack] = React.useState(false)
    const[errorMessage,setErrorContent] = React.useState('')

  const stripe = useStripe();
  const elements = useElements();



function showCardError(event) {
  console.log("showCardError")
  if (event.error) {
    setErrorContent(event.error.message)
    setOpenSnack(true)
  } else {
    setErrorContent('event.error.message')

  }
}

function retryInvoiceWithNewPaymentMethod(
  customerId,
  paymentMethodId,
  invoiceId,
  priceId
) {
  console.log("retryInvoiceWithNewPaymentMethod")
  return (
    fetch('http://127.0.0.1:8000/api/user/retry_subscription/', {
      method: 'post',
      headers: {
        'Content-type': 'application/json',
          Authorization: `Bearer ${props.token}`
      },
      body: JSON.stringify({
        customerId: customerId,
        paymentMethodId: paymentMethodId,
        invoiceId: invoiceId,
      }),
    })
      .then((response) => {
        return response.json();
      })
      // If the card is declined, display an error to the user.
      .then((result) => {
        if (result.error) {
          console.log("1 throwing in retryInvoiceWithNewPaymentMethod")
            console.log("1 result=",result)
          // The card had an error when trying to attach it to a customer.
          throw result;
        }
        return result;
      })
      // Normalize the result to contain the object returned by Stripe.
      // Add the addional details we need.
      .then((result) => {
        return {
          // Use the Stripe 'object' property on the
          // returned result to understand what object is returned.
          invoice: result,
          paymentMethodId: paymentMethodId,
          priceId: priceId,
          isRetry: true,
        };
      })
      // Some payment methods require a customer to be on session
      // to complete the payment process. Check the status of the
      // payment intent to handle these actions.
      .then(handleCustomerActionRequired)
      // No more actions required. Provision your service for the user.
      .then(onSubscriptionComplete)
      .catch((error) => {
        console.log(" catching 2 in retryInvoiceWithNewPaymentMethod :",error)
        // An error has happened. Display the failure to the user here.
        // We utilize the HTML element we created.
        showCardError(error);
      })
  );
}





function handlePaymentMethodRequired({
  subscription,
  paymentMethodId,
  priceId,
}) {
  console.log("handlePaymentMethodRequired")
  if (subscription.status === 'active') {
    console.log("considered active in handlePaymentMethodRequired")
    // subscription is active, no customer actions required.
    return { subscription, priceId, paymentMethodId };
  } else if ( subscription.latest_invoice.payment_intent.status === 'requires_payment_method') {
    console.log("subscription.latest_invoice.payment_intent.status=",subscription.latest_invoice.payment_intent.status)
    // Using localStorage to manage the state of the retry here,
    // feel free to replace with what you prefer.
    // Store the latest invoice ID and status.

    // faire des vraies variables de Γ§a, car comme Γ§a = de la merde
    localStorage.setItem('latestInvoiceId', subscription.latest_invoice.id);
    localStorage.setItem('latestInvoicePaymentIntentStatus', subscription.latest_invoice.payment_intent.status);
        console.log("11 throwing in handlePaymentMethodRequired")
      console.log("11 custom_throw=",{ error: { message: 'Your card was declined.' } })



    throw { error: { message: 'Your card was declined.' } };
  } else {
    return { subscription, priceId, paymentMethodId };
  }
}


function handleCustomerActionRequired({
  subscription,
  invoice,
  priceId,
  paymentMethodId,
  isRetry,
}) {
      console.log("handleCustomerActionRequired")

  if (subscription && subscription.status === 'active') {
        console.log("subscription exists and subscription.status ===active in handleCustomerActionRequired")

    // Subscription is active, no customer actions required.
    return { subscription, priceId, paymentMethodId };
  }

  // If it's a first payment attempt, the payment intent is on the subscription latest invoice.
  // If it's a retry, the payment intent will be on the invoice itself.
  let paymentIntent = invoice ? invoice.payment_intent : subscription.latest_invoice.payment_intent;

  if (paymentIntent.status === 'requires_action' ||  (isRetry === true && paymentIntent.status === 'requires_payment_method')) {
    console.log("paymentIntent.status:",paymentIntent.status)
    console.log("paymentIntent.client_secret:",paymentIntent.client_secret)

    return stripe
      .confirmCardPayment(paymentIntent.client_secret, {
        payment_method: paymentMethodId,
      })
      .then((result) => {
        if (result.error) {
          console.log("4")
          console.log("Throwing 4 in handleCustomerActionRequired =",result)
          // Start code flow to handle updating the payment details.
          // Display error message in your UI.
          // The card was declined (i.e. insufficient funds, card has expired, etc).
          console.log("result ")
          throw result;
        } else {
          if (result.paymentIntent.status === 'succeeded') {
            console.log("result.paymentIntent.status:",result.paymentIntent.status)
            console.log("in handleCustomerActionRequired")
            // Show a success message to your customer.

            setOpenedSuccessModal(true)
            localStorage.removeItem('latestInvoiceId')
            localStorage.removeItem('latestInvoicePaymentIntentStatus')


            // There's a risk of the customer closing the window before the callback.
            // We recommend setting up webhook endpoints later in this guide.
            return {
              priceId: priceId,
              subscription: subscription,
              invoice: invoice,
              paymentMethodId: paymentMethodId,
            };
          }
        }
      })
      .catch((error) => {
        console.log("6")
        console.log("Catching 6 in handleCustomerActionRequired=",error)
        showCardError(error);
      });
  } else {
    // No customer action needed.
    return { subscription, priceId, paymentMethodId };
  }
};


  function onSubscriptionComplete(result) {
    console.log("onSubscriptionComplete")
    console.log("result.subscription.status=",result.subscription.status)
    if (result.subscription.status === 'active') {

      setOpenedSuccessModal(true)
      localStorage.removeItem('latestInvoiceId')
      localStorage.removeItem('latestInvoicePaymentIntentStatus')

  }
}


  function createSubscription({ customerId, paymentMethodId, priceId }) {
  return (
    fetch('http://127.0.0.1:8000/api/user/create_subscription/', {
      method: 'post',
      headers: {
        'Content-type': 'application/json',
          Authorization: `Bearer ${props.token}`

      },
      body: JSON.stringify({
        customerId: customerId,
        paymentMethodId: paymentMethodId,
        priceId: priceId,
      }),
    })
      .then((response) => {
        return response.json();
      })
      // If the card is declined, display an error to the user.
      .then((result) => {
        if (result.error) {
                            console.log("7 throwing in createSubscription ")
                              console.log("7 result=",result)

          // The card had an error when trying to attach it to a customer.
          throw result;
        }
        return result;
      })
      // Normalize the result to contain the object returned by Stripe.
      // Add the addional details we need.
      .then((result) => {
          //console.log("return=",result)
        return {
          paymentMethodId: paymentMethodId,
          priceId: priceId,
          subscription: result,
        };
      })

      // Some payment methods require a customer to be on session
      // to complete the payment process. Check the status of the
      // payment intent to handle these actions.
      // // If attaching this card to a Customer object succeeds,
      // // but attempts to charge the customer fail, you
      // // get a requires_payment_method error.
      // // No more actions required. Provision your service for the user.
        .then(handleCustomerActionRequired)
        .then(handlePaymentMethodRequired)
        .then(onSubscriptionComplete)
        .catch((error) => {
        // An error has happened. Display the failure to the user here.
        // We utilize the HTML element we created.
        console.log("8")
        console.log("Catching 8 in createSubscription =",error)
        showCardError({ error: { message: 'Your card was declined.' } });
      })
  );
}

  function createPaymentMethod(cardElement, customerId, priceId) {
  return stripe
    .createPaymentMethod({
      type: 'card',
      card: cardElement,
    })



    .then((result) => {
      if (result.error) {
        console.log("9 in createPaymentMethod")
        showCardError(result)
      } else {
          let latestInvoiceId = localStorage.getItem('latestInvoiceId');
        let latestInvoicePaymentIntentStatus =  localStorage.getItem('latestInvoicePaymentIntentStatus')

        if (latestInvoiceId && latestInvoicePaymentIntentStatus==='requires_payment_method'){
            retryInvoiceWithNewPaymentMethod(customerId, result.paymentMethod.id, latestInvoiceId,priceId)
        }

        else  createSubscription({
          customerId: customerId,
          paymentMethodId: result.paymentMethod.id,
          priceId: priceId,
        });
      }
    });
}



  const handleSubmit = async (event) => {
    // We don't want to let default form submission happen here,
    // which would refresh the page.
    event.preventDefault();

    console.log("priceId=",priceId)
    console.log("customerId=",props.customerId)

    if (!stripe || !elements) {
      // Stripe.js has not yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }

      await createPaymentMethod(elements.getElement(CardElement),props.customerId,priceId)
   };



  return (
    <form onSubmit={handleSubmit}>
      <FormControl component="fieldset">
  <FormLabel component="legend">Plan</FormLabel>
  <RadioGroup aria-label="product_id" name="product_id" value={priceId} onChange={(event)=>setPriceId(event.target.value)}>
    <FormControlLabel value="price_1GvhOFC2ahMs23Dqf7E83kTk" control={<Radio />} label="EXPENSIVE Plan" />
    <FormControlLabel value="price_1GqywBC2ahMs23DqyBiAZLZG" control={<Radio />} label="Veterinary Plan (15.99 / mois)" />
  </RadioGroup>
</FormControl>
      <CardSection/>
      <button disabled={!stripe}>Save Card</button>
<ModalSuccessPayment is_open={openedSuccessModal} setIsOpen={setOpenedSuccessModal} text ={<Emoji text=" πŸŽ‰οΈ Your payment has been accepted and your subscription started" />} {...props}/>
      {/*{snack}*/}
      <Snackbar open={errorMessage.length > 0} autoHideDuration={600000} message={errorMessage}/>
    </form>
  );
}

Discrepancy between GitHub code and live-demo code β€” intentional?

Hello, I'm working through the client-only integration, and noticed a discrepancy between the live demo, at https://4iupj.sse.codesandbox.io/ , and the GitHub repo.

If you inspect the live demo in browser devtools, there's an extra code block, which results in the session information being displayed on the 'success' page (copied at the bottom).

That fetch path doesn't exist in the sample or on my own page. Am I correct in assuming that

  1. it's just there to demo what the session returns? And,
  2. Checkout does not generate/provide such a path that I would be able to access? I.e., is this just a path or route you guys made on your own server for the purposes of the demo?

I'm getting all the necessary logs and session info in my own server side code via a Webhook, but I wanted to make sure that I'm not missing a more direct way to acquire the session info on the client side.

Discrepant Code

var sessionId = urlParams.get("session_id")
if (sessionId) {
   fetch("/checkout-session?sessionId=" + sessionId).then(function (result) {
          return result.text();
        }).then(function (session) {
          var sessionJSON = JSON.stringify(session, null, 2);
          document.querySelector("#stripejson").textContent = sessionJSON;
        })
      }

Thanks!

Subscription management

Hi there,

Thanks for the demo. Do you have any demo with subscription management (and authentication ?) using the hosted checkout page?

Create new customers all the time when a subscription is created

I am trying out Stripe checkout client-only integration. I am facing an issue which actually creates a new customer after checkout even though a customer with the same email id exists. Is there a way to handle it in a way that if a customer already exists with the same name add the subscription to the existing customer or create a new customer and add the subscription to the new customer

obvious mistake or is that a deliberate choice behind the screen

hey, I agree that it's an obvious mistake but is there any other valid reason or serious reason why the amount is different from the index page to the checkout page?

plan 5$ week become 5$ /month
plan 12$ week become 15$/month

I guess that this is just a parameter inside dashboard wrongly setup

Repeat Orders

Every night, 3 or so orders repeat. Any idea how this could be happening?

Screen Shot 2021-07-07 at 4 55 57 PM

Domain redirect

var redirectToCheckout = function(planId) {
// Make the call to Stripe.js to redirect to the checkout page
// with the current quantity
stripe
.redirectToCheckout({
items: [{ plan: planId, quantity: 1 }],
successUrl:
"https://" + DOMAIN + "/success.html?session_id={CHECKOUT_SESSION_ID}",
cancelUrl: "https://" + DOMAIN + "/canceled.html"
})
.then(handleResult);
};

MISS protocol in default client only redirect (in javascript)

Using stripe samples and client-server means you need to change the STATIC_DIR

When using the stripe samples command to clone the repository, it removes the python/ folder (and copies the content into 'server'). This then means that when you run the application, it is unable to find the client files because the static dir is set to ../../client while it should be set to ../client.

Potential solutions:

  • Ensure the stripe samples command also edits the .env.example file to set the static director appropriately
    OR
  • Update the documentation to make clear that the static dir should be updated.

Stripe session customer info not being retrieved despite successful webhook

Hi there,
I have the following code taken from multiple flask subscription docs on the web. Everything works perfectly fine, however, stripeData = event["data"]["object"]looks like its not returning the customer info, as "didn't get stored" gets return in the success endpoint. I am so confused why this is the case when the web hook is successful per the payments going through in the dashboard and when connecting to stripe-cli, the customer info even gets shown. However, by its own it doesn't. Would really appreciate some clarity.

stripe_keys = {
    "secret_key":'sk_test_5...GKIv8T67w0f8000ypWZx9om',
    "publishable_key": 'pk_test_51Hhm...X6q2FLqW',
    "price_id": 'price_1HhmCyBBlU..f',
    "endpoint_secret":'whsec_lFkitrmzG...u0PbD4riv7'
}
stripe.api_key = stripe_keys["secret_key"]

@blueprint.route("/stripe_webhook", methods=["POST"]) 
def stripe_webhook():
    # print("Webhook called ")
    payload = request.get_data(as_text=True)
    sig_header = request.headers.get("Stripe-Signature") 
    event = None
    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, stripe_keys["endpoint_secret"]
        )
    except ValueError as e:
        return "Invalid payload", 400
    except stripe.error.SignatureVerificationError as e:
        # Invalid signature
        return "Invalid signature", 400

    if event["type"] == "checkout.session.completed":
        try:
            stripeData = event["data"]["object"]
            stripe_customer_id = stripeData['customer']
            stripe_subscription_id = stripeData['subscription']
            user = User.query.filter_by(id=str(session['id'])).first()
            user = User(stripeSubscripstionId= stripe_subscription_id, stripeCustomerId= stripe_customer_id)
            db.session.add(user)
            db.session.commit()
        except:
            print('FAILED TO WORK') #doesn't get returned
    return {}

@blueprint.route("/config")
def get_publishable_key():
    stripe_config = {"publicKey": stripe_keys["publishable_key"]}
    return jsonify(stripe_config)

@blueprint.route("/success")
def success():
    print('session id is')
    print(session['id'])
    user = User.query.filter_by(id=str(session['id'])).first()
    if user.stripeSubscriptionId:
        return render_template("/General/success.html")
    return "didn't get stored"

@blueprint.route("/create-checkout-session") 
def create_checkout_session():
    domain_url = "http://localhost:5000/"
    stripe.api_key = stripe_keys["secret_key"]
    # User.query.filter_by(id=session['id']).first() 
    try:
        checkout_session = stripe.checkout.Session.create(

            client_reference_id=User.id,
            success_url=domain_url + 'success?session_id={CHECKOUT_SESSION_ID}',
            cancel_url=domain_url + "cancel", 
            payment_method_types=["card"],
            mode="subscription",
            line_items=[
                {
                    "price": stripe_keys["price_id"],
                    "quantity": 1,
                }
            ]
        )
        return jsonify({"sessionId": checkout_session["id"]})
    except Exception as e:
        return jsonify(error=str(e)), 403

System information

  • OS: macOS BigSur
  • Browser (if applies) Safari
  • Server environment py 3.9

Can't redirect to Localhost on success, preventing local testing

Hello, the client-only integration is great!
I'm running into a bug (maybe?) when attempting to test this locally.

To reproduce

  1. change the successURL: to "https://localhost:5000" + "?session_id={CHECKOUT_SESSION_ID}",
  2. go through checkout successfully, using a Test Card
  3. see error in browser This site can’t provide a secure connectionlocalhost sent an invalid response. ERR_SSL_PROTOCOL_ERROR

This seems to be a bug, because when loading Checkout locally, it simply issues a warning in the console You may test your Stripe.js integration over HTTP. However, live Stripe.js integrations must use HTTPS.

Current Workaround
I've created a live testing environment, but this is extremely slow to test, and hard to debug via logging, because any change requires a redeploy. Being able to use Localhost would
greatly speed up the dev process, on both counts (speed and readability).

Question
If this is an intentional feature, is there a recommended workaround to serve Localhost over https? (I did due-diligence Google this, but most solutions are more involved than someone using the client-only integration is probably comfortable with! So just checking here, first.)

Additional Information
Not sure if it's relevant, but I'm using the Firebase local emulator to locally serve and test my web app.

Thanks!

Stripe

Bug report

Describe the bug

A clear and concise description of what the bug is.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior

A clear and concise description of what you expected to happen.

Screenshots

If applicable, add screenshots to help explain your problem.

System information

  • OS: [e.g. macOS, Windows]
  • Browser (if applies) [e.g. chrome, safari]
  • Server environment [e.g. Go, Java, Node, PHP, Python, Ruby, TypeScript]

Additional context

Add any other context about the problem here.

billing address not stored with customer/invoice

I'm using the client-server code with python and added billing_address_collection="required", to the server-side create checkout session code. With the payment form, the billing address is shown, but it is not stored with neither the customer nor the invoice.

Readme intro - error with prices create commant

Bug report

Describe the bug

The stock command in the readme.md gives an error:
Missing required param: Currency

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:
Follow the readme.md , Using Node although it would be the same for others.

When you are up to this part:

./stripe prices create
  -d "product=ID_OF_BASIC_PRODUCT"
  -d "unit_amount=1800"
  -d "currency=usd"
  -d "recurring[interval]=month"

After substituting in your ID, you'll get this error:

  "error": {
    "code": "parameter_missing",
    "doc_url": "https://stripe.com/docs/error-codes/parameter-missing",
    "message": "Missing required param: currency.",
    "param": "currency",
    "request_log_url": "https://dashboard.stripe.com/test/logs/req_DizJci51DGcUB5?t=1685198228",
    "type": "invalid_request_error"
  }
}

Expected behavior

That the price is created, without you having to go in the GUI and do it.

Examples needed fixing for legibility and Windows environments

Bug report

I've added a pull request #218 to fix a few issues with the examples documentation, especially when using on Windows environments. The formatting was also fairly inconsistent, and I feel the updates I did makes it more legible.

Welcome any changes of course.

Client-only in codesandbox.io

Hello,
I would like to set up a codesandbox.io client only example to share it.
I guess the server part with the start command in the package.json would not be needed.
Can you help me on that one ?

How to backdate and postdate the start of a subscription with the new checkout?

Hello. We need to handle these 2 scenarios:

  1. Postdate a subscription: save the card info, charge the first payment instantly, but only start the subscription in the future.
    E.g, user pays first payment on Dec 1 2020, but subscription only starts on Jan 1 2021, and the next payment will be on Jan 1 2022.

  2. Backdate a subscription: save the card info, charge the first payment instantly, starts the subscription in the past.
    E.g, user pays first payment on Feb 1 2020, but subscription actually started on Jan 1 2020, and the next payment will be on Jan 1 2021.

With the old legacy charge/subscription API, we were able to achieve these using one time charges and set future start date for subscription. These cases are very common in membership payments that do not offer proration (proration in full).

How can we address these cases? Thank you!

Checkout page attaches Payment Method to customer even if card data is incorrect.

Bug report

Describe the bug

Checkout page in 'subscription' mode saves payment methods even when payment is not sucessfull (declined card for example).
This may not be a bug in a strict sense but it is a major inconvience, because if user enters wrong card data by mistake it will be later seen in his payment methods without any indication that it is wrong card.

After inspecting events generated by stripe I can see that there is no setup_intent.* events present in logs. So it seems like Checkout in subscription mode doesn't use SetupIntent API to create payment methods as is recommended in docs (https://stripe.com/docs/api/payment_methods/attach). When I used SetupIntent API to manually add a payment method to a customer it is not created if card data is incorrect, which is desirable behaviour.

I understand that it may be a design decision to make it work that way, but it is unituitive from customer's view point.

To Reproduce

  1. Create customer
  2. Create Checkout Session in subscription mode with added customer field and payment_method_types set to ['card']
  3. Go to Checkout page
  4. Enter wrong card data ( for example 4000000000000002 from test cards list: https://stripe.com/docs/testing#cards-responses)
  5. Enter correct card (for example 4242424242424242 from test cards list)
  6. Go to dashboard or customer portal to see that coresponding customer has failed payment method attached to his account.

Expected behavior

I expected that Checkout page wouldn't add payment methods to customer if first subscription payment has failed.

Screenshots

System information

Additional context

No StripeOptions class

Bug report

Sample Startup.cs Breaks

The sample Startup.cs code references a type that does not exist in the namespaces or libraries referenced in the using statements.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Copy the code into an actual file and see the error

Expected behavior

A clear and concise description of what you expected to happen.

Screenshots

If applicable, add screenshots to help explain your problem.

System information

  • OS: [e.g. macOS, Windows]
  • Browser (if applies) [e.g. chrome, safari]
  • Server environment [e.g. Go, Java, Node, PHP, Python, Ruby, TypeScript]

Additional context

Add any other context about the problem here.

PHP Demo's "shared.php" file missing

Many of the files in the server/PHP demonstration of this repo are including a "shared.php" file, but there is not one anywhere in the repo.

Some of the files that include it are:
server/php/public/config.php
server/php/public/get-checkout-session.php
server/php/public/webhook.php
server/php/public/create-checkout-session.php

How to make a referral program with stripe subscription

Hi there,
My question is more business related than code:

In my app, I'd like to give an extra week of subscription if a user referred someone to the app.
My question:

  1. Does stripe provide any referral program apis? to handle the referral program backend by itself just like with checkout. I wasn't able to find any.
  2. if not, I would construct the program, in such logic on my own end:
    Attach the inviter's username to the end of the URL, if the invitee logs in, a week gets taken of from inviter's subscription time in db. My only problem is that how will I make this manual update to the user's stripe info on your db?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.