Hot questions for Using Vapor in javascript

Question:

I have a Javascript function in a leaf page using Vapor 3 the function should trigger a post to an api to save a customers card detials and return a token representing that card. I would normally do this in swift on our vapor server but to be PCI complient we are not allowed to take the customers credit card detials onto our server they must go directly to Sum Up (payment provider) I have no experiance with javascript and after lots of googling this it the fucntion I came up with.

    <script>
            function savecard() {

                var name = document.getElementById("nameonCard").value
                var cardNo = document.getElementById("cardNumber").value
                var expiry_year = document.getElementById("expiryMonth").value
                var expiry_month = document.getElementById("expiryYear").value
                var cvv = document.getElementById("cvc").value
                var body = `{"type":"card","card":{"name":"${name}","number":"${cardNo}", "expiry_month": "${expiry_month}", "expiry_year": "${expiry_year}","cvv": "${cvv}"}}`
                return fetch("https://api.sumup.com/v0.1/customers/" + #(sumCustId) + "/payment-instruments", {
                  method: "POST"
                  headers: {
                    "Authorization": "Basic " + #(auth),
                    "Content-Type": "application/json"
                  },
                  data: body,
                })
                .then(function(response){
                      document.getElementById("response").value = response.text()
                      return response.text();
                })
                .then(function(data){
                    console.log(data)//text version
                    var data_obj = JSON.parse(data);
                    document.getElementById("data_obj").value = data_obj
                    return data_obj
                })
            }
        </script>

I don't appear to be getting anything back but I'm not sure where I am going wrong, any help is much appreciated.

Here is the entire leaf page.

<!doctype html>
<html lang="en">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

        <title>Pay for Membership</title>
        <body>
            <div class="card bg-dark text-white h-100">
                <img class="card-img bg-dark img-fluid" src="https://images.unsplash.com/photo-1575151772039-542722abbf63?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format" alt="Card image">
                    <div class="card-img-overlay">
                        <div class="card-deck">
                        <div class="card bg-transparent border-warning align-self-start ">
                        <div class="card-header">
                            <h4 class="card-title text-center">Order Summary</h4>
                        </div>
                        <div class="card-body">
                            <ul>
                                #for(sum in orderSum){
                                    #if(isLast == false){
                                    <li class="list-group-item bg-transparent d-flex justify-content-between align-items-centert">#noam(sum)
                                    <span class="badge badge-info badge-pill text-right align-self-center">#am(sum)</span>
                                    </li>
                                    }
                                }
                            </ul>
                        </div>
                        <div class="card-footer text-center">
                            #for(sum in orderSum){
                                #if(isLast){
                                    <h5>#(sum)</h5>
                                }
                            }
                        </div>
                        </div>
                        <div class="card bg-dark border-warning">
                            <div class="card-header">
                                <h4 class="card-title">Payment Details</h4>
                            </div>
                            <div class="card-body">
                                <form method="post" action="/completedsumupmember" id="paymentForm">
                                <div class="row form-group" hidden>
                                <input type="number" class="form-control" id="amount" name="amount" value =#(amount) hidden>
                                <input type="text" class="form-control" id="currency" name="currency" value =#(currency) hidden>
                                <input type="text" class="form-control" id="orderId" name="orderId" value =#(orderId) hidden>
                                <input type="text" class="form-control" id="desc" name="desc" value =#(desc) hidden>
                                <input type="text" class="form-control" id="type" name="type" value =#(type) hidden>
                                <input type="text" class="form-control" id="orgId" name="orgId" value =#(orgId) hidden>
                                <input type="text" class="form-control" id="payToEmail" name="payToEmail" value =#(payToEmail) hidden>
                                <input type="text" class="form-control" id="auth" name="auth" value =#(auth) hidden>
                                <input type="hidden" class="form-control" id="response" name="response" value ="" hidden>
                                <input type="hidden" class="form-control" id="data_obj" name="data_obj" value ="" hidden>
                                <input type="text" class="form-control" id="customerId" name="customerId" value =#(sumCustId) hidden>
                            </div>
                            <div class="row form-group">
                                <div class = "col-md">
                                    <label for="nameonCard">Name on the Card</label>
                                    <input type="text" class="form-control" id="nameonCard" name="nameonCard" placeholder="John Smith" required>
                                </div>
                            </div>
                            <div class ="row form-group">
                                <div class ="col-lg">
                                    <label for="cardNumber">Card Number</label>
                                    <input type="number" class="form-control" id="cardNumber" name="cardNumber" placeholder="1111 2222 3333 4444" required>
                            </div>
                            </div>
                                <div class = "row form-group">
                                    <div class = "col-md">
                                        <label for="expiryMonth">Expriy Month</label>
                                        <input type="text" class="form-control" id="expiryMonth" name="expiryMonth" placeholder="mm" required>
                                    </div>
                                    <div class = "col-md">
                                        <label for="expiry">Expriy Year</label>
                                        <input type="text" class="form-control" id="expiryYear" name="expiryYear" placeholder="yy" required>
                                    </div>
                                    <div class = "col-md">
                                        <label for="cvc">CVV</label>
                                        <input type="text" class="form-control" id="cvc" name="cvc" placeholder="000" required>
                                    </div>
                                </div>
                                <div class="card-footer text-center">
                                    <button class="btn btn-warning btn-block" type="submit" onclick="savecard()">Pay Now</button>
                                </div>
                                </form>
                            </div>


                        </div>
                    </div>
                    </div>


            <!-- Optional JavaScript -->
            <!-- jQuery first, then Popper.js, then Bootstrap JS -->
            <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
            <script>
                function savecard() {

                    console.log('do something ')
                    var name = document.getElementById("nameonCard").value
                    var cardNo = document.getElementById("cardNumber").value
                    var expiry_year = document.getElementById("expiryMonth").value
                    var expiry_month = document.getElementById("expiryYear").value
                    var cvv = document.getElementById("cvc").value
                    var body = `{"type":"card","card":{"name":"${name}","number":"${cardNo}", "expiry_month": "${expiry_month}", "expiry_year": "${expiry_year}","cvv": "${cvv}"}}`
                    return fetch("https://api.sumup.com/v0.1/customers/" + #(sumCustId) + "/payment-instruments", {
                      method: "POST"
                      headers: {
                        "Authorization": "Basic " + #(auth),
                        "Content-Type": "application/json"
                      },
                      data: body,
                    })
                    .then(function(response){
                          document.getElementById("response").value = response.text()
                          return response.text();
                    })
                    .then(function(data){
                        console.log(data)//text version
                        var data_obj = JSON.parse(data);
                        document.getElementById("data_obj").value = data_obj
                        return data_obj
                    })
                }
            </script>
        </body>
    </html> 

I have finally managed to get a function that works, which is below.

    <script>
        function savecard() {
            let customerId = document.getElementById("customerId").value;
            let url = "https://api.sumup.com/v0.1/customers/" + customerId + "/payment-instruments"
            let auth = " Basic " + document.getElementById("auth").value;
            alert(auth);

            let cardBody = {
                type:"card",
                card: {
                name: document.getElementById("nameonCard").value,
                cardNo: document.getElementById("cardNumber").value,
                expiry_year: document.getElementById("expiryMonth").value,
                expiry_month: document.getElementById("expiryYear").value,
                cvv: document.getElementById("cvc").value
                }
                };
            alert(cardBody.card.name);
            let options = {
                //mode: 'no-cors',
                method: 'POST',
                body: JSON.stringify(cardBody),
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': auth
                }
            }

            fetch(url, options)
            .then(res => res.json())
            .then(res => alert(res))
            .catch(err => alert(`Error with message: ${err}`));;
        }
    </script> 

However I am now getting an error in the catch

SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 of the JSON data

Seems like this is a simple typo, but I've been looking at it to long to see what it is.


Answer:

In the end this worked. I thin by placing it in the head of the leaf file and removing any leaf tags fixed the issues. Instead of using leaf tags I added hidden form inputs with the values set by the leaf tag then accessed that value in the function.

<script>
        function savecard() {
            let customerId = document.getElementById("customerId").value; // for testing hard code value in form
            let url = "https://api.sumup.com/v0.1/customers/" + customerId + "/payment-instruments"
            let auth = " Basic " + document.getElementById("auth").value; // for testing hard code value in form


            let cardBody = {
                type:"card",
                card: {
                name: document.getElementById("nameonCard").value,
                cardNo: document.getElementById("cardNumber").value,
                expiry_year: document.getElementById("expiryMonth").value,
                expiry_month: document.getElementById("expiryYear").value,
                cvv: document.getElementById("cvc").value
                }
                };

            let headers = {
                'Authorization': auth,
                'Content-Type': 'application/json'
                 }
            let options = {
                //mode: 'no-cors',
                method: 'POST',
                body: JSON.stringify(cardBody), //
                headers: headers
            }

            fetch(url, options)
            .then(res => res.json())
                var token = res.token
                document.getElementById("token").value = token
                .then(res => console.log(res))
            .catch(err => alert(`Error with message: ${err}`));;

            document.getElementById("paymentForm").submit()
        }
    </script>

Hopefully that helps someone.

Question:

I'm having trouble dealing with multiple parameters. I'm Ok with passing one, but am unsure about passing in multiple ones. I have this JS code in a webpage:

$.getJSON('api/vendor/countryVendors/'+country+'&'+resourceType,    function(result){} 

And the following in my Vapor controller:

func getcountryVendors(_ req: Request) throws -> Future<[Vendor]> {
    let countryString = try req.parameters.next(String.self)
    let resourceTypeString = try req.parameters.next(String.self) 

not sure if the URL I've created is wrong or my Swift code or both


Answer:

It looks like you are trying to pass in query-string parameters, which are different from the route path parameters. In this case, both snippets are wrong.

Query string parameters are key/value pairs that are appended to the end of a URL, like this:

/my/url/path?key=value&key1=value1

So your URL in your JS code should look like this:

'api/vendor/countryVendors?country='+country+'&resourceType='+resourceType

To get query string parameters from the URL passed in to the route handler, you use the request.query property and the .get(_:at:) method:

func getcountryVendors(_ req: Request) throws -> Future<[Vendor]> {
    let countryString = try req.query.get(String.self, at: "country")
    let resourceTypeString = try req.query.get(String.self, at: "resourceType") 

    // Other code...
}

Question:

I have a webpage that is sending the following JS Object via Ajax to a Vapor handler. The JS object has a Bool variable, as does the receiving struct in Vapor.

When I stringify the JS Object to create a JSON then it Stringifies the true to "true". The Vapor app crashes because it is expecting a bool. The JS object also has am array.

How can I send a JSON that contains non string elements such as Bool or numbers that then can be mapped to a Struct with the same data type?


Answer:

Hi so just using the console in chrome I knocked together this code which creates the desired effect.

 JSON.stringify({ noStr: true }) // "{"noStr":true}"

So achieving the string this way should work but if you are using a library like Axios or jQuery this should happen automatically. Perhaps you could share a sample of what you are trying to do and how you are getting a "true" in your request object.

Good luck!

Question:

I have added the Scroll Back To Top Button in my Base template and it's working perfectly. But when I go to any other pages, the button won't work. Why?!

Here is my Base template:

<!DOCTYPE html>
<html>
  <head></head>

  <body>
    <button onclick="topFunction()" id="myBtn" title="Go to top">Top</button>
    .
    .
    <div class="container">
      #import("content") 
    </div>
    .
    .
  </body>
</html>

And I'm using the JavaScript from the above link:

window.onscroll = function() { scrollFunction() };

function scrollFunction() {
  if (document.body.scrollTop > 50 || document.documentElement.scrollTop > 50) {
    $('#myBtn').fadeIn();
  } else {
    $('#myBtn').fadeOut();
  }
}

function topFunction() {
  document.body.scrollTop = 0; // For Safari
  document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera
}

The button is only working on the homepage, but not on any other pages even though I see (from View Page Source) <button onclick="topFunction()" id="myBtn" title="Go to top">Top</button> is in the body. Can anyone please help??


Answer:

The code above is actually correct and the button is working perfectly on every page now. After tobygriffin pointed out, I noticed that there was a Cannot read property 'style' of undefined error that I didn't before. The button is working on all pages after fixing that error.

Even though it was a silly mistake on my part, I am keeping this Q&A (not deleting it) hoping someone might find it useful :)