【发布时间】:2017-04-24 20:57:40
【问题描述】:
我正在尝试将 Stripe 集成到我的 JSF 应用程序中,但在离开“添加信用卡”页面时遇到了困难。一切正常,除了用户点击提交后,页面不会离开。
下面是 addCreditCard.xhtml facelet。将 javascript 逻辑添加为提交 eventListener 并使用onclick="#{stripeCCBean.update()}" 触发 bean update method() 是我可以让 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 不正确或效率低下,我也很乐意改变它。
【问题讨论】:
-
我在这个问题中看到的奇怪之处: 1. 看不到你从 xhtml 文件中调用 javascript 的位置。 2. 如果 bean 用于管理视图,请使用
@ViewScoped而不是@RequestScoped(否则您将丢失从请求到请求的状态)。 3.我宁愿使用f:viewAction而不是@PostConstruct注解来调用init方法。 4. 不要在 JSF 表单中使用action和method属性。 JSF 会自己计算它们。 5.h:commandButton的onclick属性应该执行一个javascript监听器而不是一个服务器端方法.. -
@XtremeBiker, 1) JS 通过事件处理程序 (
document.querySelector('form').addEventListener('submit', ...) 触发 2) bean 只是执行一些业务逻辑。它尚未完成,因此最终会将 addCard(...) 方法的结果保存到 @SessionScoped bean。 3) 由#2 回答? 4)我无法让JSF表单触发javascript,将隐藏字段保留到表单并调用没有传递表单属性的bean方法。我不知道为什么,它就是行不通。 5)onclick触发JS时,createToken()方法失败,不知道是什么原因。 -
@XtremeBiker 我意识到代码有点到处都是,但这仅仅是因为当我以“正确的方式”进行操作时无法让事情正常工作。看起来下面有一些建议我会尝试的。
-
嗯,是的,我明白了.. 你有没有尝试过一些类似的例子,看看发生了什么?我的意思是,设置一个视图(不需要是一个完整的项目)并做你想做的事,但使用最少的 JS 代码和最少的托管 bean 功能,而不是涉及到条带支付实用程序。
标签: javascript jsf jsf-2 stripe-payments