#Slush - Payments Quickstart

1 messages · Page 1 of 1 (latest)

pallid cedar
#

HI đź‘‹

terse shore
#

Hey!

pallid cedar
#

What do you mean the payload isn't working?

terse shore
#

Are you familiar with working in .NET 🙂

pallid cedar
#

Only a little bit

terse shore
#

The payload itself is working as seen here:

#

So we know the frontend is working correctly 🙂

#

When that gets called my breakpoint on the server side does get hit. So we know its connecting with the backend.

#

But for some reason public ActionResult Create(PaymentIntentCreateRequest request)

#

"request" doesnt contain the items payload

pallid cedar
#

You are hitting the Payment Intents API.

#

Payment Intents do not support items.

terse shore
#

The guide says it does?

pallid cedar
pallid cedar
#

The prebuilt Checkout page supports Items

terse shore
#

"Custom Payment Flow"

#

Look at the checkout.js code and Server.cs code

#

They are both sending and recieving items

#

Am I wrong?

pallid cedar
#

I'm looking at server.cs

#

And no, it's not using items

#

The only options passed in are the amount and the currency and enabling automatic payment methods

terse shore
#

Look at checkout.js its sending over items as the payload

pallid cedar
#

Checkout JS doesn't matter here

#

The payment intent does not support line items and is not created with them

terse shore
#

and if you look at CalculateOrderAmount its called from the create function

#

using "request.Items"

pallid cedar
#

Which is fine but the Payment Intent is not created with items because it does not support line items

#

All that is doing in this guide is providing a mechanism to calculate the total anount

terse shore
#

Yes but how come "request.Items" is null

pallid cedar
#

Do you have a request ID?

terse shore
#

Yes

#

It looks like pi_xxxxxx right?

pallid cedar
#

That's the Payment Intent ID but that will work

terse shore
#

That will work for what?

pallid cedar
#

FOr me to examine what you are talking about

terse shore
#

"pi_3Ldd91K8LPxiZPYt13wUYpsm"

pallid cedar
#

But just to be absolutely clear. You are creating a payment intent. Payment intents do not have lines.

#

This is the data you sent to the API

{
  automatic_payment_methods: {
    enabled: "True"
  },
  amount: "1400",
  currency: "usd"
}
terse shore
#

Okay maybe I can explain to you what I am trying to do and how I can edit this so that it accomplishes what I am attempting to do.

#

On the frontend I need an ID value passed over to the server to be able to correctly calculate the order amount. (As you know right now its just returning 1400) so when the payment intent service gets called, I also need it to contain the ID value that will be passed in from the frontend.

#

How would you suggest going about this?

pallid cedar
#
  1. You can create a Price object in Stripe that defines the amount for something specific. This will return a price_XXXX ID.
#

But Payment Intents are one of our most simple APIs so they just need amount and currency

terse shore
#

All the prices are stored on my own backend and are completely random based on the ID that gets passed in on the front end.

pallid cedar
#

So you would need to get the Price object to determine what the amount should be

#

Okay, that works too

#

as long as your integration knows what to provide in the amount parameter

terse shore
#

So what part of the code are you talking about editing so I can include this ID?

#

For example on the .js file I have a constant called "goldenID" how should I be passing that to the server along with the paymentintent.

pallid cedar
#

If you are talking about sending data to the API, the area you want to focus on is the server.cs and especially this code:

var paymentIntent = paymentIntentService.Create(new PaymentIntentCreateOptions
      {
        Amount = CalculateOrderAmount(request.Items),
        Currency = "usd",
        AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
        {
          Enabled = true,
        },
      });
#

You could include the ID in the metadata parameter for the Payment Intent

terse shore
#

Hmm, okay maybe this makes sense. But still trying to understand fully.

#

Basically on the frontend .js file I have a "goldenID"

#
      {
        Amount = CalculateOrderAmount(request.GOLDENID),
        Currency = "usd",
        AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
        {
          Enabled = true,
        },
      });```
#

I basically need that "goldenID" passed into my calculateOrderAmount method in order for me to get the correct price.

#

How would you suggest getting that value there?

pallid cedar
#

Pass the ID back to your server in the request, then add it to the PaymentIntentCreateOptions metadata parameter

terse shore
#

Which is what I think I am trying to do with the request.Items? I was just trying to use that as storage for my ID

#

How would I alter this: const response = await fetch("api/Stripe/create-payment-intent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ items }), }); to allow me to pass in my custom ID to the request?

balmy seal
#

đź‘‹ I'm hopping in since @pallid cedar has to head out soon - give me a minute to catch up

terse shore
#

Thank you @balmy seal 🙂

balmy seal
#

You'd pass that in the body when you call your server endpoint that makes the Payment Intent. See in the code you sent over it has body: JSON.stringify({ items }) - you/d want to change that so it sends over items as well as the customer ID you want

terse shore
#

Then how do I access that on the server side?

#

Because right now I cant even access the items

balmy seal
#

The example should be showing you how to access items - what do you mean when you say you can't access them? Are you getting an error?

terse shore
#

No error, items is just null

#

Thats the code I have, as you can see items = id xl-tshirt

#

Just like it is on the example

#

Thats the payload successfully being sent

balmy seal
#

What happens when you change your code to body: JSON.stringify ({items: items})

#

ah actually the request payload does seem to indicate it's correct

terse shore
#

On the server side request.Items is null

balmy seal
#

What happens when you log request on your server-side?

terse shore
#

When i debug it and that function gets called

#

I hover over request and it says "Items null"

balmy seal
#

Hmmm... Can you change your code to log the raw request body you get back?

terse shore
#

Not really

#

Its not that easy in .net

#

Any other suggestions?

balmy seal
#

Hmm... Can you show me the server-side code you have for your Item and PaymentIntentCreateRequest classes?

terse shore
#

I sure can!

#
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Stripe;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;


// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860

namespace Tourny.Controllers
{
    [Route("api/[controller]/create-payment-intent")]
    public class StripeController : Controller
    {
        // GET: api/<controller>
        
        [HttpPost]
        public ActionResult Create(PaymentIntentCreateRequest request)
        {
            //StripeConfiguration.ApiKey = "sk_test_XXX";
            var paymentIntentService = new PaymentIntentService();
            var paymentIntent = paymentIntentService.Create(new PaymentIntentCreateOptions
            {
                Amount = CalculateOrderAmount(request.Items),
                Currency = "usd",
                AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
                {
                    Enabled = true,
                },
            });

            return Json(new { clientSecret = paymentIntent.ClientSecret });
        }

        private int CalculateOrderAmount(Item[] items)
        {
            // Replace this constant with a calculation of the order's amount
            // Calculate the order total on the server to prevent
            // people from directly manipulating the amount on the client
            return 1400;
        }

        public class Item
        {
            [JsonProperty("id")]
            public string Id { get; set; }
        }

        public class PaymentIntentCreateRequest
        {
            [JsonProperty("items")]
            public Item[] Items { get; set; }
        }
    }

    

    }
#

Here is my Startup.cs file:

#
namespace Tourny
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        
        public void ConfigureServices(IServiceCollection services)
        {
           
            services.Configure<CookiePolicyOptions>(options =>
            {
                
                options.CheckConsentNeeded = context => false;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddDistributedMemoryCache();
            services.AddSession(options => {
                options.IdleTimeout = TimeSpan.FromMinutes(60);//You can set Time   
            });
            services.AddRazorPages();
            services.AddMvc().AddNewtonsoftJson();
            //services.AddControllersWithViews().AddRazorRuntimeCompilation();
            //services.AddMvc();

        }

       
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            StripeConfiguration.ApiKey = "sk_test_xxxxx";
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseSession();
            
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                //endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
            });

        }
    }
}```
balmy seal
#

Hmm... Yeah I don't see anything out of the ordinary with that code so I'm a bit confused about why it's not working

#

Let me mull this over a bit

terse shore
#

Thank you.

#

This line: CalculateOrderAmount(request.Items),

#

request.Items should NOT be null right?

balmy seal
#

correct - if this was all working, the JSON from the request body would be converted into a PaymentIntentCreateRequest and populate items

terse shore
#

That’s what I thought

#

No idea why it’s not

balmy seal
#

You're sure it's being sent as a POST request right?

terse shore
#

Yeah I think so

#

How can I check

#

It does say method: “POST”

#

On sending the request on the JavaScript side

balmy seal
#

gotcha

#

other than changing your code so that we can see the raw request body (I think our webhooks sample code gives an example of how to do this https://stripe.com/docs/webhooks/signatures#verify-official-libraries), the only other thing I can think of is to change up/simplify the request body to just send a string and then use [FromBody] (https://docs.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api#using-frombody) to make sure the request can pull out that string from the reqeust body. With the intention being to see if something is wrong with how the json is being deserialized

terse shore
#

Sorry not exactly sure what you’re asking me to try

#

Is there anything I can log on the JavaScript side so you can see the raw request?

balmy seal
#

No, there isn't anything you can change on the Javascript side to see the raw request - you're already looking at it from the screenshot you sent over

#

The issue is on the server-side, not doing what we expect it to (which is why we need to see the raw body it's getting)

#

My suggestion was to change the request body and simplify it to just be a string just to see if that works, but really, the best thing would be to get the raw request body

terse shore
#

What if I do request.ToString()

#

Will that get what youre looking for?

balmy seal
#

It probably wouldn't - we need to know what the reqeust looks like BEFORE it's been de-serialized. By the time you call request.toString() it's too late.

terse shore
#
        public ActionResult Create(PaymentIntentCreateRequest request)
        {
            string output = request.ToString();
            //StripeConfiguration.ApiKey = "sk_test_XXX";
            var paymentIntentService = new PaymentIntentService();
            var paymentIntent = paymentIntentService.Create(new PaymentIntentCreateOptions
            {
                Amount = CalculateOrderAmount(request.Items),
                Currency = "usd",
                AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
                {
                    Enabled = true,
                },
            });

            return Json(new { clientSecret = paymentIntent.ClientSecret });
        }```
#

Take a look at that.

#

That should turn it into a string the second the request comes in right?

#

Output contains "Tourny.Controllers.StripeController+PaymentIntentCreateRequest"

#

Thats it.

balmy seal
#

Yeah that's because what I mentioned before - that isn't enough to get your the raw request body. At that point the JSON from the request has already been de-serialized

terse shore
#

So when do you suggest i grab the request?

balmy seal
terse shore
#

{"items":[{"id":"xl-tshirt"}]}

#

reading the var json = await new StreamReader....

#

json contains that ^^

hasty lantern
#

Hi stepping in for karbi as they have to step out

#

Seeing this is a very long running thread

#

Can you summarize exactly what you are blocked on this moment?

terse shore
#

but the request.Items = null

#

frontend the payload looks fine:

#

Karbi suggested for me to use the webhook method to see the request stream from the backend to make sure its working

#

So I replaced the "Create" method with this:

#
        public async Task<IActionResult> Create()
        {
            var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();

            try
            {
                var stripeEvent = EventUtility.ConstructEvent(json,
                    Request.Headers["Stripe-Signature"], endpointSecret);

                // Handle the event
                if (stripeEvent.Type == Events.PaymentIntentSucceeded)
                {
                    var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
                    Console.WriteLine("PaymentIntent was successful!");
                }
                else if (stripeEvent.Type == Events.PaymentMethodAttached)
                {
                    var paymentMethod = stripeEvent.Data.Object as PaymentMethod;
                    Console.WriteLine("PaymentMethod was attached to a Customer!");
                }
                // ... handle other event types
                else
                {
                    Console.WriteLine("Unhandled event type: {0}", stripeEvent.Type);
                }

                return Ok();
            }
            catch (StripeException)
            {
                return BadRequest();
            }
        }```
#

And grabbed the output of var json

#

and that read: {"items":[{"id":"xl-tshirt"}]}

#

So we know that the request is getting sent successfully to the backend as well

#

But there is just something wrong with this method:

#
        public ActionResult Create(PaymentIntentCreateRequest request)
        {
            
            //StripeConfiguration.ApiKey = "sk_test_XXX";
            var paymentIntentService = new PaymentIntentService();
            var paymentIntent = paymentIntentService.Create(new PaymentIntentCreateOptions
            {
                Amount = CalculateOrderAmount(request.Items),
                Currency = "usd",
                AutomaticPaymentMethods = new PaymentIntentAutomaticPaymentMethodsOptions
                {
                    Enabled = true,
                },
            });

            return Json(new { clientSecret = paymentIntent.ClientSecret });
        }```
#

Where for some reason the request.Items is returning null when we know it shouldnt be.

hasty lantern
#

Wait so to clarify that second code block you sent there is your original code?

terse shore
#

Yes

#

I replaced that with the top code block just so I can see the request body before it was serialized

hasty lantern
#

Gotcha. Can you try adding [FromBody into that method? So, it would be like: public ActionResult Create([FromBody] PaymentIntentCreateRequest request)

terse shore
#

Yes let me give it a try

#

holy fucking shit

#

you did it

#

I have been working on this problem

#

for like 4 days

hasty lantern
#

Haha. Happy to help

terse shore
#

Youre the man!

#

Also, quick question. If I want to do a proccess on my backend after the payment is completed I need a webhook?

#

And can I do this with the current system I have implemented?