Stripe Apple and Google Pay Quick Start
Broadleaf Commerce offers an out-of-the-box Stripe solution that requires little configuration and is easily set up.
The quick start solution implements the Payment Request button model offered by Stripe for supporting
Apple Pay and Google Pay altogether.
This implementation should be useful for those with a simple checkout flow.
You must have completed the Stripe Environment Setup before continuing
Adding Stripe Checkout Support
These instructions assume integration with the default Heat Clinic Demo Site provided with the framework.
- Configure domain verification file path in SiteServletConfig, only for Apple Pay:
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("{wellKnownFolder:\\.well-known}/**")
.addResourceLocations("classpath:/well-known/");
}
Add the following code to your
head.html
template. This will pull in the Stripe Javascript library:<script src="https://js.stripe.com/v3"></script>
Insert the following Javascript into your checkout html in order to generate the client token and client secret and retrieve the payment nonce:
<div id="ready_only_apple_pay_payment_method" class="read-only-apple_pay-payment-method" th:fragment="read-only">
<p>Apple Pay</p>
</div>
<div id="apple_pay_payment_method" th:fragment="form">
<script>
var publicKey = '[[${#stripe.generateClientToken()}]]';
var clientSecret = '[[${#stripe.getClientSecret(paymentRequestDTO)}]]';
var stripe = Stripe(publicKey, {
apiVersion: '[[${#stripe.getApiVersion()}]]',
});
var paymentRequest = stripe.paymentRequest({
country: 'US',
currency: 'usd',
total: {
label: 'Demo total',
amount: [[${#stripe.calculateTransactionTotal(paymentRequestDTO)}]]
},
requestPayerName: true,
requestPayerEmail: true,
});
var elements = stripe.elements();
var prButton = elements.create('paymentRequestButton', {
paymentRequest: paymentRequest,
});
// Check the availability of the Payment Request API first.
paymentRequest.canMakePayment().then(function(result) {
console.log("### can make payment? ### " + result);
if (result) {
prButton.mount('#payment-request-button');
} else {
document.getElementById('payment-request-button').style.display = 'none';
}
});
paymentRequest.on('paymentmethod', function(ev) {
// Confirm the PaymentIntent without handling potential next actions (yet).
stripe.confirmCardPayment(
clientSecret,
{payment_method: ev.paymentMethod.id},
{handleActions: false}
).then(function(confirmResult) {
if (confirmResult.error) {
// Report to the browser that the payment failed, prompting it to
// re-show the payment interface, or show an error message and close
// the payment interface.
ev.complete('fail');
} else {
// Report to the browser that the confirmation was successful, prompting
// it to close the browser payment method collection interface.
ev.complete('success');
// Check if the PaymentIntent requires any actions and if so let Stripe.js
// handle the flow. If using an API version older than "2019-02-11" instead
// instead check for: `paymentIntent.status === "requires_source_action"`.
if (confirmResult.paymentIntent.status === "requires_action") {
// Let Stripe.js handle the rest of the payment flow.
stripe.confirmCardPayment(clientSecret).then(function(result) {
if (result.error) {
// The payment failed -- ask your customer for a new payment method.
console.log(result.error)
} else {
console.log("success")
// The payment has succeeded.
// submit stripe checkout form and fill opaque data from transaction
$("#payment_intent").val(confirmResult.paymentIntent.id);
$("#stripe_checkout_form").submit();
}
});
} else {
// The payment has succeeded.
// submit stripe checkout form and fill opaque data from transaction
$("#payment_intent").val(confirmResult.paymentIntent.id);
$("#stripe_checkout_form").submit();
}
}
});
})
</script>
<div id="payment-request-button">
<!-- A Stripe Element will be inserted here. -->
</div>
<blc:form id="stripe_checkout_form" th:action="@{/checkout/stripe/confirm_payment_intent}" method="POST">
<input type="hidden" name="payment_intent" id="payment_intent"/>
</blc:form>
</div>
- Create a Spring MVC Controller to handle the form you created above in order to process the payment nonce and confirm the transaction. It would look something like this:
@Controller
public class StripeCheckoutController extends BroadleafCheckoutController {
@Resource(name = "blAddressService")
protected AddressService addressService;
@Resource(name = "blStripeConfiguration")
protected StripeConfiguration stripeConfiguration;
@RequestMapping(value = "/checkout/stripe/confirm_payment_intent", method = RequestMethod.GET)
public String confirmStripePaymentIntent(HttpServletRequest request, HttpServletResponse response,
@RequestParam(value = "payment_intent", required = true) String paymentIntentId,
@RequestParam(value = "payment_intent_client_secret", required = false) String paymentIntentClientSecret,
@RequestParam(value = "source_type", required = false) String sourceType,
RedirectAttributes redirectAttributes) throws IOException, ServletException {
LOG.info("Attempting to CONFIRM Payment Intent and perform initial AUTH");
try {
Order cart = CartState.getCart();
//Find existing Order Payment Intent
OrderPayment orderPaymentIntent = null;
for (OrderPayment payment : cart.getPayments()) {
if (StripePaymentGatewayType.STRIPE.equals(payment.getGatewayType()) &&
StripePaymentType.PAYMENT_INTENT.equals(payment.getType()) &&
payment.getArchived() != 'Y') {
orderPaymentIntent = payment;
break;
}
}
List<PaymentTransaction> unconfirmedTransactions = orderPaymentIntent.getTransactionsForType(PaymentTransactionType.UNCONFIRMED);
PaymentTransaction transaction = unconfirmedTransactions.get(0);
transaction.getAdditionalFields().remove(MessageConstants.PAYMENT_METHOD_ID);
transaction.getAdditionalFields().put(MessageConstants.PAYMENT_INTENT_ID, paymentIntentId);
//override the idempotency key as this needs to be unique for the payment intent confirmation
String currentIdempotencyKey = transaction.getAdditionalFields().get(MessageConstants.IDEMPOTENCY_KEY);
transaction.getAdditionalFields().put(MessageConstants.IDEMPOTENCY_KEY, currentIdempotencyKey + "_" + paymentIntentId);
orderService.save(cart, true);
if (!(cart instanceof NullOrderImpl)) {
try {
String orderNumber = initiateCheckout(cart.getId());
return getConfirmationViewRedirect(orderNumber);
} catch (Exception e) {
handleProcessingException(e, redirectAttributes);
}
}
return getCheckoutPageRedirect();
} catch (Exception e) {
if (LOG.isErrorEnabled()) {
LOG.error(e);
}
}
return getCheckoutPageRedirect();
}
}
When processCompleteCheckoutOrderFinalized
is called, the checkout workflow is invoked and the ValidateAndConfirmPaymentActivity
is executed to confirm the payment nonce.
Done!
At this point, all the configuration should be complete and you are now ready to test your integration with Stripe and Apple Pay/Google Pay.