Checkout Flow
Introduction
This sample demonstrates how you can implement a simple checkout page in AMP. The sample assumes that all payment processing is done server-side. The covered use cases are:
- How to dynamically render shopping card data.
- How to support user login with stored addresses and credit cards.
- How to let users auto-fill their contact, address and credit card details.
- How to handle promo/discount codes.
Setup
We use quite a few components:
amp-form
for collecting and submitting user input.
<script async custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script>
amp-access
for user login.
<script async custom-element="amp-access" src="https://cdn.ampproject.org/v0/amp-access-0.1.js"></script>
amp-analytics
is required by the amp-access
extension.
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
amp-list
for rendering personalized content, such as the shopping cart.
<script async custom-element="amp-list" src="https://cdn.ampproject.org/v0/amp-list-0.1.js"></script>
amp-mustache
for rendering templates in combination with amp-list
.
<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"></script>
amp-bind
for dynamically updating the page based on user input.
<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>
Sign-in Support
We use amp-access
to integrate login and to show and hide a login button depending on whether the user is logged in. amp-access requires the definition of 3 endpoints as documented here.
This sample allows an user to login and logout using either email/password or Google sign in. Logout is implemented by configuring a second endpoint in the login property sign-out
, find more here.
<script id="amp-access" type="application/json">
{
"authorization": "/documentation/examples/api/amp-access/authorization?rid=READER_ID&url=CANONICAL_URL&ref=DOCUMENT_REFERRER&_=RANDOM",
"noPingback": "true",
"login": {
"sign-in": "/documentation/examples/api/amp-access/login?rid=READER_ID",
"sign-out": "/documentation/examples/api/amp-access/logout?rid=READER_ID"
},
"authorizationFallbackResponse": {
"error": true,
"loggedIn": false
}
}
</script>
amp-access
enables us to show either a Login or Logout button depending on whether the user is already logged-in, e.g. elements marked with amp-access="NOT loggedIn"
will only show for non-logged-in users. The button's on tap actions, e.g. on="tap:amp-access.login-sign-in"
, specifies which action should be taken when clicking on the button: login
defines the property inside the amp-access
json configuration, while sign-in
defines the endpoint.
<div [hidden]="checkoutSuccess">
<button amp-access="NOT loggedIn" on="tap:amp-access.login-sign-in">Login</button>
<button amp-access="loggedIn" tabindex="0" on="tap:amp-access.login-sign-out" amp-access-hide>Logout</button>
<span amp-access="NOT loggedIn">...or continue as guest</span>
</div>
Review Order (Shopping Cart)
The review order section displays the current shopping cart content and the total price. We pull in the shopping cart content from a JSON endpoint and render it inside an amp-list
. The user is identified based on the AMP CLIENT_ID which is passed as
a request parameter. Another possibility would be to use cookies, which are included in the amp-list's source request, if the attribute credentials="include"
is set. Additionally, we bind the amp-list's src
attribute to an implicit state object [src]="shoppingCart.src"
, which we use to refresh the content using amp-bind
.
Review Order
<section [hidden]="checkoutSuccess" class="checkout-section">
<h3>Review Order</h3>
<amp-list width="auto" height="180" items="." single-item layout="fixed-height" credentials="include" src="https://amp.dev/documentation/examples/e-commerce/checkout_flow/shoppingcart?clientId=CLIENT_ID(cart)" [src]="shoppingCart.src" binding="no">
<template type="amp-mustache">
<div class="shopping-cart">
<div class="item header">
<div class="name"></div>
<div class="price"><strong>Price</strong></div>
<div class="quantity"><strong>Quantity</strong></div>
</div>
{{#items}}
<div class="item">
<div class="name">{{name}}</div>
<div class="price">${{price}}</div>
<div class="quantity">{{quantity}}x</div>
</div>
{{/items}}
{{#discount}}
<div class="item summary">
<div class="name"></div>
<div class="price"><strong>Discount:</strong></div>
<div class="quantity"><strong>{{.}}</strong></div>
</div>
{{/discount}}
<div class="item summary">
<div class="name"></div>
<div class="price"><strong>Sum:</strong></div>
<div class="quantity"><strong>${{total}}</strong></div>
</div>
</div>
</template>
</amp-list>
</section>
Promo/discount code
This section makes it possible to enter promo or discount codes. It's a simple amp-form
that posts the
code to an XHR endpoint. If the form has been successfully submitted, we refresh the shopping carts
contents by updating the shoppingCart
object with an updated src URL. We append a random value to invalidate any caches and force
the refresh.
Add a promotional code
<section [class]="checkoutSuccess ? 'hide' : 'checkout-section'" class="checkout-section">
<h3> Add a promotional code </h3>
<form method="post" action-xhr="https://amp.dev/documentation/examples/e-commerce/checkout_flow/apply-code" on="submit-success:AMP.setState({
shoppingCart: {
src: 'https://amp.dev/documentation/examples/e-commerce/checkout_flow/shoppingcart?clientId=CLIENT_ID(cart)&' + random()
}
})" target="_top">
<input name="clientId" type="hidden" value="CLIENT_ID(cart)" data-amp-replace="CLIENT_ID">
<input name="code" placeholder="Code" aria-label="code" value="abc123">
<button value="Apply">Apply</button>
</form>
</section>
The Checkout Form
This is the actual checkout form. Form submission takes place via XHR. Once the form has been successfully submitted, we set the
checkoutSuccess
variable to true
. This enables us to hide the forms once the checkout is done. Another option would have be to redirect to a new page on successful checkout.
<form id="checkout-form" method="post" [hidden]="checkoutSuccess" action-xhr="https://amp.dev/documentation/examples/e-commerce/checkout_flow/apply-code" on="submit-success:AMP.setState({checkoutSuccess: true})" target="_top">
Contact Details
Not logged in users (amp-access="NOT loggedIn"
) will see this section to enter their contact details.
Contact
<section class="checkout-section" amp-access="NOT loggedIn">
<h3>Contact</h3>
<label for="frmNameA">Name</label>
<input name="name" id="frmNameA" placeholder="Full name" autocomplete="name">
<label for="frmEmailA">Email</label>
<input type="email" name="email" id="frmEmailA" placeholder="name@example.com" autocomplete="email">
</section>
Shipping & Billing Address
Logged in users (amp-access="loggedIn"
) can select an existing address which are pulled in using another amp-list. This section is initially hidden via the amp-access-hide
attribute.
We provide a third option for users to enter a new address. The radio button's [change action](/documentation/guides-and-tutorials/learn/amp-actions-and-events) triggers the visibilityof the address form `on="change:manualShippingAddress.toggleVisibility"`.
Select Shipping Address
- {{#addresses}}
- {{^default}} {{/default}} {{#default}} {{/default}} {{/addresses}} {{#manual}}
- {{/manual}}
<section class="checkout-section" amp-access="loggedIn" amp-access-hide>
<h3>Select Shipping Address</h3>
<amp-list width="auto" height="96" layout="fixed-height" items="." single-item credentials="include" src="/static/samples/json/addresses.json" binding="no">
<template type="amp-mustache">
<ul class="list-reset">
{{#addresses}}
<li>
{{^default}}
<input type="radio" id="address{{id}}" name="address" value="{{id}}" on="change:manualShippingAddress.hide">
<label for="address{{id}}">{{name}}, {{street}}, {{city}} </label>
{{/default}}
{{#default}}
<input type="radio" checked id="defaultAddress{{id}}" name="address" value="{{id}}" on="change:manualShippingAddress.hide">
<label for="defaultAddress{{id}}">{{name}}, {{street}}, {{city}} {{#default}}<strong>[DEFAULT]</strong>{{/default}}</label>
{{/default}}
{{/addresses}}
</li>
{{#manual}}
<li>
<input type="radio" id="ship-separate" name="address" value="{{id}}" on="change:manualShippingAddress.toggleVisibility">
<label for="ship-separate">Enter new Shipping Address</label>
</li>
{{/manual}}
</ul>
</template>
</amp-list>
<section class="sub-section" id="manualShippingAddress" hidden>
<label for="manualShipAddressS">Address</label>
<input name="ship-address" id="manualShipAddressS" placeholder="123 Any Street" autocomplete="shipping street-address">
<label for="manualShipCityS">City</label>
<input name="ship-city" id="manualShipCityS" placeholder="New York" autocomplete="shipping locality">
<label for="manualShipStateS">State</label>
<input name="ship-state" id="manualShipStateS" placeholder="NY" autocomplete="shipping region">
<label for="manualShipZipS">Zip</label>
<input name="ship-zip" id="manualShipZipS" placeholder="10011" autocomplete="shipping postal-code">
<label for="manualShipCountryS">Country</label>
<input name="ship-country" id="manualShipCountryS" placeholder="USA" autocomplete="shipping country">
<label for="saveNewAddress1">Save Address</label>
<input id="saveNewAddress1" type="checkbox" checked on="change:shippingAddress.toggleVisibility">
</section>
<label for="shippingAddressCheck">Use Shipping as Billing Address</label>
<input id="shippingAddressCheck" type="checkbox" checked on="change:shippingAddress.toggleVisibility">
</section>
Not logged in users see a simple form for entering the shipping address. We use the autocomplete
attributes
to enable auto fill for addresses which greatly simplifies form fill-out for users.
Enter Shipping Address
<section class="checkout-section" amp-access="NOT loggedIn">
<h3>Enter Shipping Address</h3>
<label for="shipAddressS">Address</label>
<input name="ship-address" id="shipAddressS" placeholder="123 Any Street" autocomplete="shipping street-address">
<label for="shipCityS">City</label>
<input name="ship-city" id="shipCityS" placeholder="New York" autocomplete="shipping locality">
<label for="shipStateS">State</label>
<input name="ship-state" id="shipStateS" placeholder="NY" autocomplete="shipping region">
<label for="shipZipS">Zip</label>
<input name="ship-zip" id="shipZipS" placeholder="10011" autocomplete="shipping postal-code">
<label for="shipCountryS">Country</label>
<input name="ship-country" id="shipCountryS" placeholder="USA" autocomplete="shipping country">
<label for="shippingAddressCheck">Use Shipping as Billing Address</label>
<input type="checkbox" checked on="change:shippingAddress.toggleVisibility">
</section>
The shipping address form is optional. We hide it initially using the hidden
attribute so that it can be toggled
via the toggleVisibility
action (on="change:shippingAddress.toggleVisibility"
).
Enter Billing Address
<section class="checkout-section" hidden id="billingAddress">
<h3>Enter Billing Address</h3>
<label for="billingAddressS">Address</label>
<input name="billing-address" id="billingAddressS" placeholder="123 Any Street" autocomplete="billing street-address">
<label for="billingCityS">City</label>
<input name="billing-city" id="billingCityS" placeholder="New York" autocomplete="billing locality">
<label for="billingStateS">State</label>
<input name="billing-state" id="billingStateS" placeholder="NY" autocomplete="billing region">
<label for="billingZipS">Zip</label>
<input name="billing-zip" id="billingZipS" placeholder="10011" autocomplete="billing postal-code">
<label for="billingCountryS">Country</label>
<input name="billing-country" id="billingCountryS" placeholder="USA" autocomplete="billing country">
</section>
Payment Details
Logged in users (amp-access="loggedIn"
) can select an existing credit card which is pulled in using amp-list
similar to how the shipping addresses are rendered dynamically above. It's also possible to manually enter credit card details.
Select Payment Details
- {{#cards}}
- {{^default}} {{/default}} {{#default}} {{/default}} {{/cards}} {{#manual}}
- {{/manual}}
<section class="checkout-section" amp-access="loggedIn" amp-access-hide>
<h3>Select Payment Details</h3>
<amp-list width="auto" height="96" layout="fixed-height" items="." single-item credentials="include" src="/static/samples/json/credit-cards.json" binding="no">
<template type="amp-mustache">
<ul class="list-reset">
{{#cards}}
<li>
{{^default}}
<input type="radio" id="cc{{id}}" name="cc" value="{{id}}" on="change:manualCC.hide">
{{/default}}
{{#default}}
<input type="radio" checked id="defaultCC{{id}}" name="cc" value="{{id}}" on="change:manualCC.hide">
{{/default}}
<label for="defaultCC{{id}}">{{title}} {{#default}}<strong>[DEFAULT]</strong>{{/default}}</label>
{{/cards}}
</li>
{{#manual}}
<li>
<input type="radio" id="new-cc" name="cc" value="{{id}}" on="change:manualCC.toggleVisibility">
<label for="new-cc">Enter new Credit Card</label>
</li>
{{/manual}}
</ul>
</template>
</amp-list>
<section class="sub-section" id="manualCC" hidden>
<label for="manualCCNameCC">Name on card</label>
<input name="ccname" id="manualCCNameCC" placeholder="Full Name" autocomplete="cc-name">
<label for="manualCCCCNum">Card Number</label>
<input name="cardnumber" id="manualCCCCNum" autocomplete="cc-number">
<label for="manualCCCVC">CVC</label>
<input name="cvc" id="manualCCCVC" autocomplete="cc-csc">
<label for="manualCCExp">Expiry</label>
<input name="cc-exp" id="manualCCExp" placeholder="MM-YYYY" autocomplete="cc-exp">
<label for="saveNewAddress2">Save Credit Card</label>
<input id="saveNewAddress2" type="checkbox" checked on="change:shippingAddress.toggleVisibility">
</section>
</section>
Not logged in users can enter their credit card details manually. Note that we're using the credit card auto-fill markup.
Enter Credit Card Details
<section class="checkout-section" amp-access="NOT loggedIn" amp-access-hide>
<h3>Enter Credit Card Details</h3>
<label for="nameCC">Name on card</label>
<input name="ccname" id="nameCC" placeholder="Full Name" autocomplete="cc-name">
<label for="ccNum">Card Number</label>
<input name="cardnumber" id="ccNum" autocomplete="cc-number">
<label for="ccCVC">CVC</label>
<input name="cvc" id="ccCVC" autocomplete="cc-csc">
<label for="ccExp">Expiry</label>
<input name="cc-exp" id="ccExp" placeholder="MM-YYYY" autocomplete="cc-exp">
</section>
Form Submission
The pay now button simply submits the form containting the different checkout form sections.
<div [hidden]="checkoutSuccess">
<input type="submit" value="Pay Now">
<span>Not for real ...</span>
</div>
This is the message that we will show after a successful checkout and the checkoutSuccess
variable is set to true
.
Checkout success!
<section hidden [hidden]="checkoutSuccess" class="checkout-section">
<h3>Checkout success!</h3>
</section>
Si les explications de cette page ne répondent pas à vos questions, n'hésitez pas à contacter d'autres utilisateurs d'AMP pour discuter de votre cas d'utilisation spécifique.
Se rendre sur Stack Overflow Une fonctionnalité n'a pas encore été expliquée ?Le projet AMP encourage fortement votre participation et vos contributions ! Nous espérons que vous deviendrez un membre régulier de notre communauté open source, mais nous serons également ravis de recevoir des contributions ponctuelles concernant les questions qui vous intéressent particulièrement.
Modifier l'exemple sur GitHub-
Written by @sebastianbenz