Vaadin Javascript Component

Hi everyone,

I tried to integration
Stripe
(
Payment Checkout solution
) into my application in vain, the problem is that most Checkout solution as Form based with input fields and integrate fine with vaadin such as
PayPal
(I use a
FormSender
or
CustomLayout
even though I could not find for the latter to add input field dynamically in the form html even with the “location”).

Anyway my point is below the form with Javascript from Stripe, this looks dangerous to me to execute the JS with the form where some code is added dynamically to the DOM (Button “Pay with Card” and token for the payment), and I understood
Vaadin
is pretty safe removing potential dangerous JS from my component:

Stripe Checkout html:

<form action="" method="POST"> <script src="https://checkout.stripe.com/checkout.js" class="stripe-button" data-key="pk_test_xxx" data-amount="999" data-name="name" data-description="Widget" data-image="/img/documentation/checkout/marketplace.png" data-locale="auto" data-zip-code="true" data-currency="eur"> </script> </form>

So I wrote a
Javascript Component
from
Vaadin
example and managed to get the form html loaded within a
Window
or
CustomLayout
, but the JS component does not display (Button “Pay with Card”) and the JS from stripe does not seems to be executed. I added all anotation & inspected the page everything seems fine , but it does not work !

I used the following example where xhtml variable was set to the “<form…> above”:
https://vaadin.com/blog/-/blogs/vaadin-7-loves-javascript-components
.

Any idea or help on that matter would be great, I guess the onStateChange does only retrieve btw client & server the xhtml injected above and probably the associated DOM does not execute the script inside the form…

If you use innerHTML the script tag would not be evalutated.

Yuo can create form and script elements using javascript APIs in your connector script and then add them to the javascript component element (this.getElement()).

By the way you can also use shared state to store values for attributes of script tag (data-key, data-amount, data-image, …)

HTH
Marco

I tried your suggestion but didn’t work, maybe I misundestood, here is the code below:

[code]
@JavaScript(value = { “vaadin://themes/mytheme/js/checkout_stripe.js” })
public class CheckoutJs extends AbstractJavaScriptComponent {

/**
 *
 */
private static final long serialVersionUID = -1418946324742025425L;

public CheckoutJs(final String value) {
    getState().value = value;
}

public void setValue(String value) {
    getState().value = value;
}

public String getValue() {
    return getState().value;
}

@Override
protected CheckoutJsLabelState getState() {
    return (CheckoutJsLabelState) super.getState();
}

}

@SuppressWarnings(“serial”)
public class CheckoutJsLabelState extends JavaScriptComponentState {
public String value;
}
[/code]The client side

com_innotek_ui_CheckoutJs = function() {
    var e = this.getElement();
    
    this.onStateChange = function() {
        e = this.getState().value;
    }
}

I meant something like this

State class holds values needed by script tag

public class CheckoutJsLabelState extends JavaScriptComponentState {
    public String key;
    public String amount; // or a Number tye
    ... 
}

Javascript component sets/gets values from shared state

@JavaScript(value = { "vaadin://themes/mytheme/js/checkout_stripe.js" })
public class CheckoutJs extends AbstractJavaScriptComponent {

   public void setAmount(String amount) {
      getState().amount = amount;
   }
   
   // .... other getter/setter
}

javascript connector creates dom elements; when shared state changes script tag will be removed and created again

window.com_innotek_ui_CheckoutJs = function() {

    var me = this;
    
    me.formElement = document.createElement("form");
    me.formElement.setAttribute("action", "");
    // ... set other form attributes
    
    // add form to javascript component element
    me.getElement().appendChild(me.formElement);

    // create a new script tag with attributes from state
    var makeScriptTag = function(state) {
        var scriptEl = document.createElement("script");
        scriptEl.setAttribute("src", "https://checkout.stripe.com/checkout.js")
        scriptEl.setAttribute("data-key", state.key);
        scriptEl.setAttribute("data-amount", state.amount);
        scriptEl.setAttribute("data-currency", "eur");
        // ... other script tag attributes 
        // with values hardcoded or from state
        return scriptEl;
    }

    me.onStateChange = function() {
        // remove existing script
        var child = me.formElement.firstChild;
        if (child) {
            me.formElement.removeChild(child);
        }
        // add script tag as form child
        me.formElement.appendChild(makeScriptTag(me.getState()));
    }
}

Thank you Marco for this example from which you create all the form on the client side and setter / getter on the component side, I will give it a try and let you know if it works on my side.

I noticed on your example the “window.” as prefix from the package client side convention syntax, any particular reason for that ?

Just to be sure that the function is added to the window object as stated in
documentation

Unfortunately the widget tag is empty, the only time I managed to get the form html of stripe was with the innerHTML but it was not evaluated as you mentionned. This script within a form from checkout stripe is really awkweird and dangerous to me but I am trying aside from PayPal to get Card payment solution for my startup…

Here is my code from your example:

window.com_innotek_ui_CheckoutJs = function() {

    var me = this;

    me.formElement = document.createElement("form");
    me.formElement.setAttribute("action", "");
    me.formElement.setAttribute("method", "POST");

    // add form to javascript component element
    me.getElement().appendChild(me.formElement);

    // create a new script tag with attributes from state
    var makeScriptTag = function(state) {
        var scriptEl = document.createElement("script");
        scriptEl.setAttribute("src", "https://checkout.stripe.com/checkout.js")
        scriptEl.setAttribute("data-key", state.key);
        scriptEl.setAttribute("data-amount", state.amount);
        scriptEl.setAttribute("data-description", "PLATINUM service purchased");
        scriptEl.setAttribute("data-image", "/img/smartminder-reg.png");
        scriptEl.setAttribute("data-locale", "auto");
        scriptEl.setAttribute("data-zip-code", "true");
        scriptEl.setAttribute("data-currency", "eur");
        return scriptEl;
    }

    me.onStateChange = function() {
        // remove existing script
        var child = me.formElement.firstChild;
        if (child) {
            me.formElement.removeChild(child);
        }
        // add script tag as form child
        me.formElement.appendChild(makeScriptTag(me.getState()));
    }
}


@JavaScript(value = { "vaadin://themes/innotek/js/checkout_stripe.js" })
public class CheckoutJs extends AbstractJavaScriptComponent {

    /**
     *
     */
    private static final long serialVersionUID = -1418946324742025425L;

    public String getAmount() {
        return getState().amount;
    }

    public void setAmount(String amount) {
        getState().amount = amount;
    }

    public String getKey() {
        return getState().key;
    }

    public void setKey(String key) {
        getState().key = key;
    }

    @Override
    protected CheckoutJsLabelState getState() {
        return (CheckoutJsLabelState) super.getState();
    }
}

@SuppressWarnings("serial")
public class CheckoutJsLabelState extends JavaScriptComponentState {
      public String amount;
      
      public String key;
}

I tried your code and form and script tag are created inside widget div (obviously the button is not rendered)

<div class="v-widget">
   <form action="" method="POST">
      <script src="https://checkout.stripe.com/checkout.js" data-key="xxxx" data-amount="999" data-description="PLATINUM service purchased" data-image="/img/smartminder-reg.png" data-locale="auto" data-zip-code="true" data-currency="eur"></script>
</form>
</div>

Have you noticed some javascript errors on console?

Yep a security issue :
Content Security Policy: La directive « frame-src » est obsolète. Veuillez utiliser la directive « child-src » à la place. => unknown source !

AJS error or warning:
L’encodage de caractères d’un document en texte brut n’a pas été déclaré. Le document sera affiché avec des caractères incorrects pour certaines configurations de navigateur si le document contient des caractères en dehors de la plage US-ASCII. L’encodage de caractères du fichier doit être déclaré dans le protocole de transfert ou le fichier doit utiliser une marque d’ordre des octets (BOM) comme signature d’encodage. => checkout.js

Means encoding is not UTF8 but I do not have special characters… weird, seems more like a warning to me.

Just the charset UTF-8 in the script tag was missing, I tried to eval() within the onStateChange the innerHtml but it did not work either.

From chrome console, maybe this warning is a hint on the way the form html is badly inserted:
Resource interpreted as Document but transferred with MIME type application/x-javascript: “https://checkout.stripe.com/checkout.js”.

I don’t know if it could help but adding class attribute to the script element make the button work for me.

scriptEl.setAttribute("class", "stripe-button");

Great Job Marco, thanks it works :slight_smile: I will go throught the overall payment flow but seems a good start.

Best,

Dylan.

Glad I could help.

Cheers
Marco