diff --git a/commerce_pos.libraries.yml b/commerce_pos.libraries.yml
index 64d7f4e..d4e4c28 100644
--- a/commerce_pos.libraries.yml
+++ b/commerce_pos.libraries.yml
@@ -4,4 +4,4 @@ form:
     layout:
       css/commerce_pos_style.css: {}
   js:
-    js/commerce_pos.js: {}
\ No newline at end of file
+    js/commerce_pos.js: {}
diff --git a/gulpfile.js b/gulpfile.js
index c824136..48af231 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -12,6 +12,7 @@ var gutil = require('gulp-util');
 var config = {
   'sassDirectories': [
     { src: './modules/keypad/sass/**/*.scss', dest: './modules/keypad/css'},
+    { src: './modules/receipt/sass/**/*.scss', dest: './modules/receipt/css'},
     { src: './sass/**/*.scss', dest: './css' }
   ]
 };
diff --git a/modules/keypad/js/commerce_pos_keypad.js b/modules/keypad/js/commerce_pos_keypad.js
index 5c1bae5..0e9b73b 100644
--- a/modules/keypad/js/commerce_pos_keypad.js
+++ b/modules/keypad/js/commerce_pos_keypad.js
@@ -1,4 +1,4 @@
-(function ($) {
+(function ($, Drupal, drupalSettings) {
   var inputBox = null;
 
   Drupal.behaviors.commercePosKeypadKeypad = {
@@ -258,4 +258,4 @@
     });
   };
 
-}(jQuery));
+}(jQuery, Drupal, drupalSettings));
diff --git a/modules/receipt/README.md b/modules/receipt/README.md
new file mode 100644
index 0000000..6e2ca23
--- /dev/null
+++ b/modules/receipt/README.md
@@ -0,0 +1,5 @@
+The receipt submodule provides for receipt printing. You may configure custom
+receipt header/footer at admin/commerce/config/pos/receipt.
+
+This module depends on the jQuery print plugin which you will find
+in your /libraries directory.
diff --git a/modules/receipt/commerce_pos_receipt.info.yml b/modules/receipt/commerce_pos_receipt.info.yml
new file mode 100644
index 0000000..628b5f2
--- /dev/null
+++ b/modules/receipt/commerce_pos_receipt.info.yml
@@ -0,0 +1,8 @@
+name: commerce_pos_receipt
+description: Provides receipts printing for Commerce Point of Sale
+type: module
+core: 8.x
+package: Commerce (POS)
+configure: commerce_pos_receipt.settings
+dependencies:
+  - commerce_pos
diff --git a/modules/receipt/commerce_pos_receipt.install b/modules/receipt/commerce_pos_receipt.install
new file mode 100644
index 0000000..256dcee
--- /dev/null
+++ b/modules/receipt/commerce_pos_receipt.install
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Contains commerce_pos_receipt.install.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function commerce_pos_receipt_uninstall() {
+  // Remove states.
+  Drupal::state()->delete('commerce_pos_receipt.header');
+  Drupal::state()->delete('commerce_pos_receipt.footer');
+}
diff --git a/modules/receipt/commerce_pos_receipt.libraries.yml b/modules/receipt/commerce_pos_receipt.libraries.yml
new file mode 100755
index 0000000..e1dbffa
--- /dev/null
+++ b/modules/receipt/commerce_pos_receipt.libraries.yml
@@ -0,0 +1,21 @@
+receipt:
+  version: 1.x
+  css:
+    theme:
+      css/commerce_pos_receipt.css: {}
+  js:
+    js/commerce_pos_receipt.js: {}
+  dependencies:
+    - core/jquery
+    - core/drupal
+    - core/drupalSettings
+    - core/drupal.ajax
+jQuery.print:
+  remote: https://github.com/DoersGuild/jQuery.print
+  version: 1.5.1
+  license:
+    name: CC BY 3.0
+    url: https://github.com/DoersGuild/jQuery.print/blob/master/LICENSE
+    gpl-compatible: no
+  js:
+    /libraries/jquery.print/jQuery.print.js: {}
diff --git a/modules/receipt/commerce_pos_receipt.links.action.yml b/modules/receipt/commerce_pos_receipt.links.action.yml
new file mode 100644
index 0000000..2535083
--- /dev/null
+++ b/modules/receipt/commerce_pos_receipt.links.action.yml
@@ -0,0 +1,5 @@
+commerce_pos_receipt_show_action:
+  route_name: commerce_pos_receipt.show
+  title: 'Show receipt'
+  appears_on:
+    - entity.commerce_order.canonical
diff --git a/modules/receipt/commerce_pos_receipt.links.menu.yml b/modules/receipt/commerce_pos_receipt.links.menu.yml
new file mode 100755
index 0000000..9553324
--- /dev/null
+++ b/modules/receipt/commerce_pos_receipt.links.menu.yml
@@ -0,0 +1,6 @@
+commerce_pos_receipt.configuration:
+  title: 'Receipt Configuration'
+  parent: commerce_pos.configuration
+  description: 'Configure the header and footers of the POS receipts.'
+  route_name: commerce_pos_receipt.settings
+  weight: 25
diff --git a/modules/receipt/commerce_pos_receipt.module b/modules/receipt/commerce_pos_receipt.module
new file mode 100644
index 0000000..d55640b
--- /dev/null
+++ b/modules/receipt/commerce_pos_receipt.module
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains commerce_pos_receipt.module.
+ */
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
+
+/**
+ * Implements hook_help().
+ */
+function commerce_pos_receipt_help($route_name, RouteMatchInterface $route_match) {
+  switch ($route_name) {
+    // Main module help for the commerce_pos_receipt module.
+    case 'help.page.commerce_pos_receipt':
+      $output = '';
+      $output .= '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('Provides receipt printing for Commerce Point of Sale') . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function commerce_pos_receipt_theme() {
+  $theme['commerce_pos_receipt'] = [
+    'variables' => [
+      'receipt' => NULL,
+    ],
+    'template' => 'commerce-pos-receipt'
+  ];
+
+  return $theme;
+}
+
+/**
+ * Implements hook_operations.
+ *
+ * @param EntityInterface $entity The order entity.
+ *
+ * @return array
+ */
+function commerce_pos_receipt_entity_operation(EntityInterface $entity) {
+  $operations = [];
+
+  if ($entity->getEntityTypeId() == 'commerce_order') {
+    $operations['show_receipt'] = [
+      'title' => t('Show receipt'),
+      'url' => Url::fromRoute('commerce_pos_receipt.show', ['commerce_order' => $entity->id()]),
+      'weight' => 50,
+    ];
+  }
+
+  return $operations;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for form changing.
+ */
+function commerce_pos_receipt_form_commerce_pos_alter(&$form, FormStateInterface $form_state, $form_id) {
+  $form['receipt'] = [
+    '#type' => 'container',
+  ];
+
+  $form['#attached']['library'][] = 'commerce_pos_receipt/receipt';
+  $form['#attached']['library'][] = 'commerce_pos_receipt/jQuery.print';
+
+  $module_handler = \Drupal::service('module_handler');
+  $module_path = $module_handler->getModule('commerce_pos_receipt')->getPath();
+
+  $form['#attached']['drupalSettings']['commercePosReceipt'] = [
+    'cssUrl' => Url::fromUri('base:' . $module_path . '/css/commerce_pos_receipt_print.css', ['absolute' => TRUE])->toString()
+  ];
+
+  $form['receipt']['contents'] = [
+    '#markup' => '<div id="commerce-pos-receipt"></div>',
+  ];
+
+  $order_id = $form_state->get('commerce_pos_order_id');
+  $ajax_url = URL::fromRoute('commerce_pos_receipt.ajax', ['commerce_order' => $order_id], [
+    'attributes' => [
+      'class' => ['use-ajax'],
+    ],
+  ]);
+  $form['receipt']['reprint'] = [
+    '#title' => t('Reprint receipt'),
+    '#type' => 'link',
+    '#url' => $ajax_url,
+  ];
+
+}
diff --git a/modules/receipt/commerce_pos_receipt.permissions.yml b/modules/receipt/commerce_pos_receipt.permissions.yml
new file mode 100644
index 0000000..5d5153e
--- /dev/null
+++ b/modules/receipt/commerce_pos_receipt.permissions.yml
@@ -0,0 +1,3 @@
+administer pos receipt:
+  title: 'Administer POS receipt settings'
+  restrict access: TRUE
diff --git a/modules/receipt/commerce_pos_receipt.routing.yml b/modules/receipt/commerce_pos_receipt.routing.yml
new file mode 100644
index 0000000..c975e5b
--- /dev/null
+++ b/modules/receipt/commerce_pos_receipt.routing.yml
@@ -0,0 +1,30 @@
+commerce_pos_receipt.ajax:
+  path: '/admin/commerce/pos/{commerce_order}/ajax-receipt'
+  defaults:
+    _controller: '\Drupal\commerce_pos_receipt\Controller\PrintController::ajaxReceipt'
+    _title: 'Order Receipt'
+  options:
+    parameters:
+      commerce_order:
+        type: 'entity:commerce_order'
+  requirements:
+    _permission: 'administer commerce_order'
+
+commerce_pos_receipt.show:
+  path: '/admin/commerce/pos/{commerce_order}/show-receipt'
+  defaults:
+    _controller: '\Drupal\commerce_pos_receipt\Controller\PrintController::showReceipt'
+    _title: 'Order Receipt'
+  options:
+    parameters:
+      commerce_order:
+        type: 'entity:commerce_order'
+  requirements:
+    _permission: 'administer commerce_order'
+
+commerce_pos_receipt.settings:
+  path: '/admin/commerce/config/pos/receipt'
+  defaults:
+    _form: '\Drupal\commerce_pos_receipt\Form\ReceiptSettingsForm'
+  requirements:
+    _permission: 'administer pos receipt'
diff --git a/modules/receipt/config/install/commerce_pos_receipt.settings.yml b/modules/receipt/config/install/commerce_pos_receipt.settings.yml
new file mode 100644
index 0000000..c05db45
--- /dev/null
+++ b/modules/receipt/config/install/commerce_pos_receipt.settings.yml
@@ -0,0 +1,8 @@
+dependencies:
+  module:
+    - commerce_pos_receipt
+    - commerce_pos
+header: "(logo??)<br />Company Name/Store#<br />Address line 1<br />Address line 2<br />Phone #"
+footer: "Thanks for shopping at CompanyName!<br />Return Policy --<br />Exchange Policy --<br />Company Motto<br />Website --<br /><br />(optional)<br />Visit our website for a chance to --"
+header_format: "html"
+footer_format: "html"
diff --git a/modules/receipt/config/schema/commerce_pos_receipt.schema.yml b/modules/receipt/config/schema/commerce_pos_receipt.schema.yml
new file mode 100644
index 0000000..640f15d
--- /dev/null
+++ b/modules/receipt/config/schema/commerce_pos_receipt.schema.yml
@@ -0,0 +1,18 @@
+# Schema for the configuration files of the Receipt module.
+
+commerce_pos_receipt.settings:
+  type: config_object
+  label: 'Receipt settings'
+  mapping:
+    header:
+      type: string
+      label: 'Header'
+    footer:
+      type: string
+      label: 'Footer'
+    header_format:
+      type: string
+      label: Header format
+    footer_format:
+      type: string
+      label: Footer format
diff --git a/modules/receipt/css/commerce_pos_receipt.css b/modules/receipt/css/commerce_pos_receipt.css
new file mode 100644
index 0000000..342d086
--- /dev/null
+++ b/modules/receipt/css/commerce_pos_receipt.css
@@ -0,0 +1,3 @@
+#commerce-pos-receipt{display:none}
+
+/*# sourceMappingURL=commerce_pos_receipt.css.map */
diff --git a/modules/receipt/css/commerce_pos_receipt.css.map b/modules/receipt/css/commerce_pos_receipt.css.map
new file mode 100644
index 0000000..9d4296f
--- /dev/null
+++ b/modules/receipt/css/commerce_pos_receipt.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"commerce_pos_receipt.css","sources":["commerce_pos_receipt.scss"],"sourcesContent":["#commerce-pos-receipt {\n  display: none;\n}\n"],"mappings":"AAAA,AAAA,qBAAqB,AAAC,CACpB,OAAO,CAAE,IAAK,CACf","names":[]}
\ No newline at end of file
diff --git a/modules/receipt/css/commerce_pos_receipt_print.css b/modules/receipt/css/commerce_pos_receipt_print.css
new file mode 100644
index 0000000..b13e3c2
--- /dev/null
+++ b/modules/receipt/css/commerce_pos_receipt_print.css
@@ -0,0 +1,3 @@
+@page{margin:5mm;size:72mm 200mm}.pos-receipt{color:#000;font-family:Arial, sans-serif;font-size:16px;font-weight:bold;text-transform:uppercase}.pos-receipt .pos-order-info{margin-bottom:1em;padding-bottom:1em;border-bottom:1px dashed #000}.pos-receipt table{width:100%;padding-bottom:1em;margin-bottom:1em;border-bottom:1px dashed #000}.pos-receipt td,.pos-receipt th{text-align:left;font-size:16px;font-weight:bold;text-transform:uppercase}.pos-receipt .payment-no-total td .payment-name,.pos-receipt .payment-no-total td.component-total{text-decoration:line-through}.pos-receipt .payment-no-total td span.commerce-pos-receipt-void-message{text-decoration:none}.pos-receipt .payment-status-void td span.commerce-pos-receipt-void-message{text-decoration:none}.pos-receipt td.component-total{text-align:right}.pos-receipt .receipt-hide{display:none}.pos-receipt .receipt-header,.pos-receipt .receipt-footer{text-align:center}.pos-receipt .receipt-header{margin-bottom:1em;padding-bottom:1em;border-bottom:1px dashed black}.pos-receipt .receipt-footer{margin-top:1em;padding-top:1em;margin-bottom:1em}.pos-receipt tr.line-item td,.pos-receipt tr.line-item-details td{border-bottom:1px solid #000}.pos-receipt tr.line-item.has-details td{border-bottom:0}.pos-receipt tr.last td{border-bottom:0}
+
+/*# sourceMappingURL=commerce_pos_receipt_print.css.map */
diff --git a/modules/receipt/css/commerce_pos_receipt_print.css.map b/modules/receipt/css/commerce_pos_receipt_print.css.map
new file mode 100644
index 0000000..04e15e8
--- /dev/null
+++ b/modules/receipt/css/commerce_pos_receipt_print.css.map
@@ -0,0 +1 @@
+{"version":3,"file":"commerce_pos_receipt_print.css","sources":["commerce_pos_receipt_print.scss"],"sourcesContent":["@page {\n  margin: 5mm;\n  size: 72mm 200mm;\n}\n\n.pos-receipt {\n  color: #000;\n  font-family: Arial, sans-serif;\n  font-size: 16px;\n  font-weight: bold;\n  text-transform: uppercase;\n}\n\n.pos-receipt .pos-order-info {\n  margin-bottom: 1em;\n  padding-bottom: 1em;\n  border-bottom: 1px dashed #000;\n}\n\n.pos-receipt .pos-order-info .transaction-type {\n}\n\n/* BASIC TABLE STYLES */\n.pos-receipt table {\n  width: 100%;\n  padding-bottom: 1em;\n  margin-bottom: 1em;\n  border-bottom: 1px dashed #000;\n}\n\n.pos-receipt td,\n\n.pos-receipt th {\n  text-align: left;\n  font-size: 16px;\n  font-weight: bold;\n  text-transform: uppercase;\n}\n\n.pos-receipt .payment-no-total td .payment-name,\n\n.pos-receipt .payment-no-total td.component-total {\n  text-decoration: line-through;\n}\n\n.pos-receipt .payment-no-total td span.commerce-pos-receipt-void-message {\n  text-decoration: none;\n}\n.pos-receipt .payment-status-void td span.commerce-pos-receipt-void-message {\n  text-decoration: none;\n}\n.pos-receipt td.component-total {\n  text-align: right;\n}\n.pos-receipt .receipt-hide {\n  display: none;\n}\n.pos-receipt .receipt-header,\n.pos-receipt .receipt-footer {\n  text-align: center;\n}\n.pos-receipt .receipt-header {\n  margin-bottom: 1em;\n  padding-bottom: 1em;\n  border-bottom: 1px dashed black;\n}\n.pos-receipt .receipt-footer {\n  margin-top: 1em;\n  padding-top: 1em;\n  margin-bottom: 1em;\n}\n.pos-receipt table.commerce-pos-order {\n  /*margin: 1em 0;*/\n}\n.pos-receipt tr.line-item td,\n.pos-receipt tr.line-item-details td {\n  border-bottom: 1px solid #000;\n}\n.pos-receipt tr.line-item.has-details td {\n  border-bottom: 0;\n}\n.pos-receipt tr.last td {\n  border-bottom: 0;\n}\n"],"mappings":"AAAA,KAAK,CACH,MAAM,CAAE,GAAI,CACZ,IAAI,CAAE,UAAW,CAGnB,AAAA,YAAY,AAAC,CACX,KAAK,CAAE,IAAK,CACZ,WAAW,CAAE,iBAAkB,CAC/B,SAAS,CAAE,IAAK,CAChB,WAAW,CAAE,IAAK,CAClB,cAAc,CAAE,SAAU,CAC3B,AAED,AAAa,YAAD,CAAC,eAAe,AAAC,CAC3B,aAAa,CAAE,GAAI,CACnB,cAAc,CAAE,GAAI,CACpB,aAAa,CAAE,eAAgB,CAChC,AAMD,AAAa,YAAD,CAAC,KAAK,AAAC,CACjB,KAAK,CAAE,IAAK,CACZ,cAAc,CAAE,GAAI,CACpB,aAAa,CAAE,GAAI,CACnB,aAAa,CAAE,eAAgB,CAChC,AAED,AAAa,YAAD,CAAC,EAAE,CAEf,AAAa,YAAD,CAAC,EAAE,AAAC,CACd,UAAU,CAAE,IAAK,CACjB,SAAS,CAAE,IAAK,CAChB,WAAW,CAAE,IAAK,CAClB,cAAc,CAAE,SAAU,CAC3B,AAED,AAAkC,YAAtB,CAAC,iBAAiB,CAAC,EAAE,CAAC,aAAa,CAE/C,AAAiC,YAArB,CAAC,iBAAiB,CAAC,EAAE,AAAA,gBAAgB,AAAC,CAChD,eAAe,CAAE,YAAa,CAC/B,AAED,AAAsC,YAA1B,CAAC,iBAAiB,CAAC,EAAE,CAAC,IAAI,AAAA,kCAAkC,AAAC,CACvE,eAAe,CAAE,IAAK,CACvB,AACD,AAAyC,YAA7B,CAAC,oBAAoB,CAAC,EAAE,CAAC,IAAI,AAAA,kCAAkC,AAAC,CAC1E,eAAe,CAAE,IAAK,CACvB,AACD,AAAe,YAAH,CAAC,EAAE,AAAA,gBAAgB,AAAC,CAC9B,UAAU,CAAE,KAAM,CACnB,AACD,AAAa,YAAD,CAAC,aAAa,AAAC,CACzB,OAAO,CAAE,IAAK,CACf,AACD,AAAa,YAAD,CAAC,eAAe,CAC5B,AAAa,YAAD,CAAC,eAAe,AAAC,CAC3B,UAAU,CAAE,MAAO,CACpB,AACD,AAAa,YAAD,CAAC,eAAe,AAAC,CAC3B,aAAa,CAAE,GAAI,CACnB,cAAc,CAAE,GAAI,CACpB,aAAa,CAAE,gBAAiB,CACjC,AACD,AAAa,YAAD,CAAC,eAAe,AAAC,CAC3B,UAAU,CAAE,GAAI,CAChB,WAAW,CAAE,GAAI,CACjB,aAAa,CAAE,GAAI,CACpB,AAID,AAA0B,YAAd,CAAC,EAAE,AAAA,UAAU,CAAC,EAAE,CAC5B,AAAkC,YAAtB,CAAC,EAAE,AAAA,kBAAkB,CAAC,EAAE,AAAC,CACnC,aAAa,CAAE,cAAe,CAC/B,AACD,AAAsC,YAA1B,CAAC,EAAE,AAAA,UAAU,AAAA,YAAY,CAAC,EAAE,AAAC,CACvC,aAAa,CAAE,CAAE,CAClB,AACD,AAAqB,YAAT,CAAC,EAAE,AAAA,KAAK,CAAC,EAAE,AAAC,CACtB,aAAa,CAAE,CAAE,CAClB","names":[]}
\ No newline at end of file
diff --git a/modules/receipt/js/commerce_pos_receipt.js b/modules/receipt/js/commerce_pos_receipt.js
new file mode 100644
index 0000000..dfee73e
--- /dev/null
+++ b/modules/receipt/js/commerce_pos_receipt.js
@@ -0,0 +1,20 @@
+(function ($, Drupal, drupalSettings) {
+
+  /**
+   * Ajax command to set the toolbar subtrees.
+   *
+   * @param {Drupal.Ajax} ajax
+   *   {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
+   * @param {object} response
+   *   JSON response from the Ajax request.
+   * @param {number} [status]
+   *   XMLHttpRequest status.
+   */
+  Drupal.AjaxCommands.prototype.printReceipt = function (ajax, response, status) {
+    $(response.content).print({
+      globalStyles: false,
+      stylesheet: drupalSettings.commercePosReceipt.cssUrl
+    });
+  };
+
+}(jQuery, Drupal, drupalSettings));
diff --git a/modules/receipt/sass/commerce_pos_receipt.scss b/modules/receipt/sass/commerce_pos_receipt.scss
new file mode 100644
index 0000000..19c1a54
--- /dev/null
+++ b/modules/receipt/sass/commerce_pos_receipt.scss
@@ -0,0 +1,3 @@
+#commerce-pos-receipt {
+  display: none;
+}
diff --git a/modules/receipt/sass/commerce_pos_receipt_print.scss b/modules/receipt/sass/commerce_pos_receipt_print.scss
new file mode 100644
index 0000000..4b75325
--- /dev/null
+++ b/modules/receipt/sass/commerce_pos_receipt_print.scss
@@ -0,0 +1,84 @@
+@page {
+  margin: 5mm;
+  size: 72mm 200mm;
+}
+
+.pos-receipt {
+  color: #000;
+  font-family: Arial, sans-serif;
+  font-size: 16px;
+  font-weight: bold;
+  text-transform: uppercase;
+}
+
+.pos-receipt .pos-order-info {
+  margin-bottom: 1em;
+  padding-bottom: 1em;
+  border-bottom: 1px dashed #000;
+}
+
+.pos-receipt .pos-order-info .transaction-type {
+}
+
+/* BASIC TABLE STYLES */
+.pos-receipt table {
+  width: 100%;
+  padding-bottom: 1em;
+  margin-bottom: 1em;
+  border-bottom: 1px dashed #000;
+}
+
+.pos-receipt td,
+
+.pos-receipt th {
+  text-align: left;
+  font-size: 16px;
+  font-weight: bold;
+  text-transform: uppercase;
+}
+
+.pos-receipt .payment-no-total td .payment-name,
+
+.pos-receipt .payment-no-total td.component-total {
+  text-decoration: line-through;
+}
+
+.pos-receipt .payment-no-total td span.commerce-pos-receipt-void-message {
+  text-decoration: none;
+}
+.pos-receipt .payment-status-void td span.commerce-pos-receipt-void-message {
+  text-decoration: none;
+}
+.pos-receipt td.component-total {
+  text-align: right;
+}
+.pos-receipt .receipt-hide {
+  display: none;
+}
+.pos-receipt .receipt-header,
+.pos-receipt .receipt-footer {
+  text-align: center;
+}
+.pos-receipt .receipt-header {
+  margin-bottom: 1em;
+  padding-bottom: 1em;
+  border-bottom: 1px dashed black;
+}
+.pos-receipt .receipt-footer {
+  margin-top: 1em;
+  padding-top: 1em;
+  margin-bottom: 1em;
+}
+.pos-receipt table.commerce-pos-order {
+  /*margin: 1em 0;*/
+}
+.pos-receipt tr.line-item td,
+.pos-receipt tr.line-item-details td {
+  border-bottom: 1px solid #000;
+}
+.pos-receipt tr.line-item.has-details td {
+  border-bottom: 0;
+}
+.pos-receipt tr.last td {
+  border-bottom: 0;
+}
diff --git a/modules/receipt/src/Ajax/PrintReceiptCommand.php b/modules/receipt/src/Ajax/PrintReceiptCommand.php
new file mode 100644
index 0000000..f46d58d
--- /dev/null
+++ b/modules/receipt/src/Ajax/PrintReceiptCommand.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\commerce_pos_receipt\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+
+/**
+ * AJAX command for retrieving data and printing a receipt.
+ */
+class PrintReceiptCommand implements CommandInterface {
+
+  protected $receipt;
+
+  public function __construct($receipt) {
+    $this->receipt = $receipt;
+  }
+
+  /**
+   * Return an array to be run through json_encode and sent to the client.
+   */
+  public function render() {
+    return [
+      'command' => 'printReceipt',
+      'content' => $this->receipt,
+    ];
+  }
+
+}
diff --git a/modules/receipt/src/Controller/PrintController.php b/modules/receipt/src/Controller/PrintController.php
new file mode 100644
index 0000000..15d3694
--- /dev/null
+++ b/modules/receipt/src/Controller/PrintController.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Drupal\commerce_pos_receipt\Controller;
+
+use Drupal\commerce_order\Entity\OrderInterface;
+use Drupal\commerce_pos_receipt\Ajax\PrintReceiptCommand;
+use Drupal\commerce_price\Entity\Currency;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\HtmlCommand;
+use Drupal\Core\Ajax\InsertCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Class PrintController.
+ */
+class PrintController extends ControllerBase {
+
+  /**
+   * A controller callback.
+   */
+  public function ajaxReceipt(OrderInterface $commerce_order) {
+    $renderer = \Drupal::service('renderer');
+
+    $build = $this->showReceipt($commerce_order);
+
+    $response = new AjaxResponse();
+
+    // TODO: could this be turned into 1 command, and if so, is that better?
+    $response->addCommand(new HtmlCommand('#commerce-pos-receipt', $renderer->render($build)));
+    $response->addCommand(new PrintReceiptCommand('#commerce-pos-receipt'));
+
+    return $response;
+  }
+
+  /**
+   * A controller callback.
+   */
+  public function showReceipt(OrderInterface $commerce_order) {
+
+    $number_formatter_factory = \Drupal::service('commerce_price.number_formatter_factory');
+    $number_formatter = $number_formatter_factory->createInstance();
+
+    $sub_total_price = $commerce_order->getSubtotalPrice();
+    $currency = Currency::load($sub_total_price->getCurrencyCode());
+    $formatted_amount = $number_formatter->formatCurrency($sub_total_price->getNumber(), $currency);
+
+    $items = $commerce_order->getItems();
+    foreach ($items as $item) {
+      $totals[] = [
+        $item->getTitle(),
+        $number_formatter->formatCurrency($item->getAdjustedTotalPrice()->getNumber(), $currency)
+      ];
+    }
+
+    $totals[] = ['Subtotal', $formatted_amount];
+
+    // Commerce appears to have a bug where if no adjustments exist, it will
+    // return a 0 => null array, which will still trigger a foreach loop.
+    foreach ($commerce_order->collectAdjustments() as $key => $adjustment) {
+      if (!empty($adjustment)) {
+        $amount = $adjustment->getAmount();
+        $currency = Currency::load($amount->getCurrencyCode());
+        $formatted_amount = $number_formatter->formatCurrency($amount->getNumber(), $currency);
+
+        $totals[] = [
+          $adjustment->getLabel(),
+          $formatted_amount,
+        ];
+      }
+    }
+
+    // Collecting the total price on the cart.
+    $total_price = $commerce_order->getTotalPrice();
+    $formatted_amount = $number_formatter->formatCurrency($total_price->getNumber(), $currency);
+    $totals[] = ['Total', $formatted_amount];
+
+    $payment_storage = \Drupal::entityTypeManager()->getStorage('commerce_payment');
+    $payments = $payment_storage->loadMultipleByOrder($commerce_order);
+    foreach ($payments as $payment) {
+      $totals[] = ['Payment', $payment->getState()->getLabel()];
+    }
+
+    $config = \Drupal::config('commerce_pos_receipt.settings');
+    $build = ['#theme' => 'commerce_pos_receipt'];
+    $build['#receipt'] = [
+      'header' => [
+        '#markup' => check_markup($config->get('header'), $config->get('header_format')),
+      ],
+      'body' => [
+        '#type' => 'table',
+        '#rows' => $totals,
+      ],
+      'footer' => [
+        '#markup' => check_markup($config->get('footer'), $config->get('footer_format')),
+      ],
+    ];
+
+    return $build;
+  }
+
+}
diff --git a/modules/receipt/src/Form/ReceiptSettingsForm.php b/modules/receipt/src/Form/ReceiptSettingsForm.php
new file mode 100644
index 0000000..9a8e467
--- /dev/null
+++ b/modules/receipt/src/Form/ReceiptSettingsForm.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\commerce_pos_receipt\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Define a Configuration form for persisting header/footer.
+ */
+class ReceiptSettingsForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+
+    $header = $this->config('commerce_pos_receipt.settings')->get('header');
+    $footer = $this->config('commerce_pos_receipt.settings')->get('footer');
+
+    $form['commerce_pos_receipt_header'] = [
+      '#type' => 'text_format',
+      '#title' => t('Header text'),
+      '#description' => t('This text will appear at the top of printed receipts.'),
+      '#default_value' => $header,
+      '#format' => NULL,
+    ];
+
+    $form['commerce_pos_receipt_footer'] = [
+      '#type' => 'text_format',
+      '#title' => t('Footer text'),
+      '#description' => t('This text will appear at the bottom of printed receipts.'),
+      '#default_value' => $footer,
+      '#format' => NULL,
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    $values = $form_state->getValues();
+    $this->config('commerce_pos_receipt.settings')
+      ->set('header', $values['commerce_pos_receipt_header']['value'])
+      ->set('footer', $values['commerce_pos_receipt_footer']['value'])
+      ->set('header_format', $values['commerce_pos_receipt_header']['format'])
+      ->set('footer_format', $values['commerce_pos_receipt_footer']['format'])
+      ->save();
+  }
+
+  /**
+   * Gets the configuration names that will be editable.
+   *
+   * @return array
+   *   An array of configuration object names that are editable if called in
+   *   conjunction with the trait's config() method.
+   */
+  protected function getEditableConfigNames() {
+    return [
+      'commerce_pos_receipt.settings',
+    ];
+  }
+
+  /**
+   * Returns a unique string identifying the form.
+   *
+   * @return string
+   *   The unique string identifying the form.
+   */
+  public function getFormId() {
+    return 'commerce_pos_receipt_settings';
+  }
+
+}
diff --git a/modules/receipt/templates/commerce-pos-receipt.html.twig b/modules/receipt/templates/commerce-pos-receipt.html.twig
new file mode 100644
index 0000000..f6a40c7
--- /dev/null
+++ b/modules/receipt/templates/commerce-pos-receipt.html.twig
@@ -0,0 +1,18 @@
+{#
+/**
+* @file
+* Receipt document.
+*
+* Available variables:
+* - receipt: The receipt.
+*
+* @ingroup themeable
+*/
+#}
+
+{{ attach_library('commerce_pos_receipt/receipt') }}
+<div class="commerce-pos-receipt">
+    <div class="receipt-header">{{ receipt.header }}</div>
+    <div class="receipt-body">{{ receipt.body }}</div>
+    <div class="receipt-footer">{{ receipt.footer }}</div>
+</div>
diff --git a/modules/receipt/tests/src/Functional/LoadTest.php b/modules/receipt/tests/src/Functional/LoadTest.php
new file mode 100644
index 0000000..9e6fe69
--- /dev/null
+++ b/modules/receipt/tests/src/Functional/LoadTest.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\Tests\commerce_pos_receipt\Functional;
+
+use Drupal\Core\Url;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Simple test to ensure that main page loads with module enabled.
+ *
+ * @group commerce_pos_receipt
+ */
+class LoadTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['commerce_pos_receipt'];
+
+  /**
+   * A user with permission to administer site configuration.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $user;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->user = $this->drupalCreateUser(['administer site configuration']);
+    $this->drupalLogin($this->user);
+  }
+
+  /**
+   * Tests that the home page loads with a 200 response.
+   */
+  public function testLoad() {
+    $this->drupalGet(Url::fromRoute('<front>'));
+    $this->assertResponse(200);
+  }
+
+}
diff --git a/src/Form/POSForm.php b/src/Form/POSForm.php
index 6fc5ff7..ea1caf7 100644
--- a/src/Form/POSForm.php
+++ b/src/Form/POSForm.php
@@ -68,6 +68,7 @@ class POSForm extends ContentEntityForm {
   protected function buildOrderForm(array $form, FormStateInterface $form_state) {
     /* @var \Drupal\commerce_order\Entity\Order $order */
     $order = $this->entity;
+    $form_state->set('commerce_pos_order_id', $order->id());
 
     $form = parent::buildForm($form, $form_state);
 
