2017-04-24 46 views
0

我正试图将Stripe整合到我的JSF应用程序中,并且难以从“添加信用卡”页面导航。一切正常,除非用户点击提交后,页面不会离开。使用h:commandButton和Javascript的JSF导航

下面是addCreditCard.xhtml facelet。将javascript逻辑添加为提交eventListener并使用onclick="#{stripeCCBean.update()}"触发bean更新方法()是我可以使JavaScript成功创建令牌的唯一方法(如果由于某些未知原因导致javascript由onclick触发,createToken方法将失败)并让bean识别隐藏的字段。

<?xml version='1.0' encoding='UTF-8' ?> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml" 
     xmlns:h="http://java.sun.com/jsf/html" 
     xmlns:f="http://java.sun.com/jsf/core" 
     xmlns:ui="http://java.sun.com/jsf/facelets" 
     xmlns:p="http://primefaces.org/ui" 
     template="/WEB-INF/template.xhtml" 
     xmlns:pt="http://xmlns.jcp.org/jsf/passthrough" 
     xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"> 
    <head> 
     <title>Facelet Title</title> 
     <link rel="stylesheet" type="text/css" href="/css/StripeCCTokenize.css"/> 
     <script src="https://js.stripe.com/v3/" type="text/javascript"></script> 
     <script src="https://code.jquery.com/jquery-3.2.0.js" type="text/javascript"></script> 
     <script src="/js/StripeCCTokenize.js" type="text/javascript"></script> 
    </head> 
    <h:body> 
     <h:form id="addCC" pt:action="/secure/addCreditCard.xhtml" pt:method="POST"> 

     <h:inputHidden id="cardholder-name" value="#{userManagerBean.user.fullName}"/> 

     We loaded your customer details (name, email and customer ID) from the backend database: 

     <label> 
      Hello #{userManagerBean.user.firstName} #{userManagerBean.user.lastName} 
     </label> 
     <label> 
      E-Mail - #{userManagerBean.user.email} 
     </label> 
     <label> 
      Stripe Customer ID - #{userManagerBean.stripeUser.id} 
     </label> 
     <h:outputText value="Please enter the requested credit card and billing information below"/> 
     <span>Address</span> 
     <h:panelGrid columns="2"> 
      <h:outputText value="Address" /> 
      <h:inputText class="field" id="address1" value="#{stripeCCBean.card.address1}" pt:placeholder="Street address"/> 
      <h:outputText value="Address"/> 
      <h:inputText class="field" id="address2" value="#{stripeCCBean.card.address2}" pt:placeholder="Street address"/> 
      <h:outputText value="City" /> 
      <h:inputText class="field" id="city" value="#{stripeCCBean.card.city}" pt:placeholder="city"/> 
      <h:outputText value="State" /> 
      <h:inputText class="field" id="state" value="#{stripeCCBean.card.state}" pt:placeholder="state"/> 
      <h:outputText value="zip" /> 
      <h:inputText class="field" id="address-zip" value="#{stripeCCBean.card.zipcode}" pt:placeholder="zipcode"/> 
      <h:outputText value="cc"/> 
     </h:panelGrid> 
     <div id="card-element" class="field"></div> 

     <h:commandButton value="Add Credit Card" onclick="#{stripeCCBean.update()}" type="submit" id="addButton"/> 
    </h:form> 
</h:body> 

这里是StripeCCTokenize.js:

var stripe; var card; 

$(document).ready(function() { 
    stripe = Stripe('pk_test_key'); 
    var elements = stripe.elements(); 

card = elements.create('card', { 
    hidePostalCode: true, 
    style: { 
     base: { 
      iconColor: '#F99A52', 
      color: '#32315E', 
      lineHeight: '48px', 
      fontWeight: 400, 
      fontFamily: '"Helvetica Neue", "Helvetica", sans-serif', 
      fontSize: '15px', 
      '::placeholder': { 
       color: '#CFD7DF' 
      } 
     } 
    } 
}); 
card.mount('#card-element'); 

function stripeTokenHandler(token) { 
    // Insert the token ID into the form so it gets submitted to the server 
    var form = document.getElementById('addCC'); 
    var hiddenInput = document.createElement('input'); 
    hiddenInput.setAttribute('type', 'hidden'); 
    hiddenInput.setAttribute('name', 'stripeToken'); 
    hiddenInput.setAttribute('value', token.id); 

    form.appendChild(hiddenInput); 

    // Submit the form 
    form.submit(); 
} 

function setOutcome(result) { 
    if (result.token) { 
     // Use the token to create a charge or a customer 
     // https://stripe.com/docs/charges 

     console.log("Token: " + result.token.id); 
     stripeTokenHandler(result.token); 

    } 
} 

card.on('change', function (event) { 
    setOutcome(event); 
}); 

document.querySelector('form').addEventListener('submit', function (e) { 
    e.preventDefault(); 

    var extraDetails = { 
     address_line1: document.getElementById('addCC:address1').value, 
     address_line2: document.getElementById('addCC:address2').value, 
     address_city: document.getElementById('addCC:city').value, 
     address_state: document.getElementById('addCC:state').value, 
     address_zip: document.getElementById('addCC:address-zip').value, 
     name: document.getElementById('addCC:cardholder-name').value 
    }; 
    console.log(extraDetails); 
    stripe.createToken(card, extraDetails).then(setOutcome); 
    }); 
}); 

这里是stripeCCBean类:

import javax.annotation.PostConstruct; 
import javax.faces.bean.ManagedBean; 
import javax.faces.bean.ManagedProperty; 
import javax.faces.bean.RequestScoped; 
import javax.faces.context.FacesContext; 
import lombok.Data; 
import lombok.ToString; 

@Data 
@ToString 
@RequestScoped 
@ManagedBean(name = "stripeCCBean") 
public class StripeCCBean implements Serializable { 

    StripeCard card; 

    @ManagedProperty(value = "#{stripeServiceBean}") 
    private StripeServiceBean stripeServiceBean; 

    @ManagedProperty(value = "#{userManagerBean}") 
    private UserManagerBean userManagerBean; 

    @PostConstruct 
    public void init() { 
     System.out.println("StripeCCBean.init()"); 
     card = new StripeCard(); 
     card.setName(userManagerBean.getUser().getFullName()); 
    } 

    public void update() throws IOException { 
     String token = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("stripeToken"); 

     if (token == null) { 
      return; 
     } 

     System.out.println("StripeCCBean.update()"); 

     System.out.println("Token: " + token); 
     System.out.println("Card: " + card); 

     try { 
      StripeService.addCard(userManagerBean.getStripeUser().getId(), token); 
     } catch (AuthenticationException | APIConnectionException | CardException | APIException | InvalidRequestException ex) { 
      ex.printStackTrace(); 
     } 

    } 
} 

我尝试添加action="#{stripeCCBean.viewAccount()}"<h:commandButton .../>和相应的方法,以StripeCCBean:

public String viewAccount() { 
    return "AccountView"; 
} 

然而,表单只是运行Javascript,调用stripeCCBean.update()(一切正常),然后停留在该页面上。客户信息字段不会被清除,但是信用卡元素可以。

我尝试添加FacesContext.getCurrentInstance().getExternalContext().redirect("/secure/AccountView.xhtml"); 以及 FacesContext.getCurrentInstance().getExternalContext().dispatch("/secure/AccountView.xhtml");到stripeCCBean.update()方法和既不作品。事实上,他们抛出了一个例外。

任何人都可以看到我做错了什么?如果我错误地或低效地触发JS,我很乐意改变它。

+0

在这个问题上我看到奇怪的事情:1.看不到你在哪里从你的xhtml文件调用javascript。 2.如果这个bean是为了管理一个视图,使用'@ ViewScoped'而不是'@ RequestScoped'(否则你将失去从请求到请求的状态)。 3.我宁愿使用'f:viewAction'而不是'@ PostConstruct'注释来调用'init'方法。 4.不要在JSF表单中使用'action'和'method'属性。 JSF负责为你自己计算它们。 5.'h:commandButton'的'onclick'属性应该执行javascript listener,而不是服务器端方法。 –

+0

@XtremeBiker,1)通过事件处理程序('document.querySelector('form'))触发JS .addEventListener('submit',...')2)这个bean只是执行一些业务逻辑。它尚未完成,因此最终会将addCard(...)方法的结果保存到@SessionScoped bean中。 3)通过#2回答? 4)我无法获得JSF表单来触发javascript,将隐藏的字段保存到表单中,并调用没有透传表单属性的bean方法。我不知道为什么,这是行不通的。 5)当JS被onclick触发时,createToken()方法失败,不知道为什么。 – Brooks

+0

@XtremeBiker我意识到代码有点到处都是,但仅仅是因为我做不到'正确的方式'时才能使用。看起来像下面有一些建议,我会尝试。 – Brooks

回答

0

另一个“我不知道为什么这个工作”,但我试着在stripeCCBean.update()方法的末尾添加FacesContext.getCurrentInstance().getExternalContext().redirect("/secure/AccountView.xhtml");,现在它工作。

仍然不知道为什么我必须通过命令按钮的onclick虽然叫做update()方法...

0

请取出的preventDefault功能,防止浏览默认行为,你不需要使用onclick事件。

0

如果要通过f:commandButton/f:commandLink进行导航,请使用action属性而不是onclick。

<f:commandButton ... action="#{myBean.actionHandler}".../> 

如果你不使用任何AJAX机制,绿豆可以@RequestScoped,其他情况下@ViewScoped

@Named 
@RequestScoped 
public class MyBean 
{ 
    public String actionHandler() 
    { 
    ... 
    return "navigationRuleName"; 
    } 

    ... 
} 

navigationRuleNamefaces-config.xml注册:

<navigation-rule> 
    <from-view-id>/path/source.xhtml</from-view-id> 
    <navigation-case> 
    <from-outcome>navigationRuleName</from-outcome> 
    <to-view-id>/path/targetPageName.xhtml</to-view-id> 
    <redirect/> 
    </navigation-case> 
</navigation-rule> 

或者它可以是页面名称:

@Named 
@RequestScoped 
public class MyBean 
{ 
    public String actionHandler() 
    { 
    ... 
    return "/path/targetPageName.xhtml?faces-redirect=true"; 
    } 

    ... 
} 

或者xhtml扩展名可以省略。

如果您希望浏览器的位置URL在HTTP POST请求 - 响应导航后更改为targetPageName.xhtml,则必须使用实体或faces-redirect=true参数。重定向在HTTP POST之后通过另一个HTTP GET调用完成。