diff --git a/packages/extensions/venia-sample-payments-cashondelivery/intercept.js b/packages/extensions/venia-sample-payments-cashondelivery/intercept.js
new file mode 100644
index 0000000000..1042660671
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/intercept.js
@@ -0,0 +1,41 @@
+module.exports = targets => {
+ const { specialFeatures } = targets.of('@magento/pwa-buildpack');
+ specialFeatures.tap(flags => {
+ /**
+ * Wee need to activate esModules, cssModules and GQL Queries to allow build pack to load our extension
+ * {@link https://magento.github.io/pwa-studio/pwa-buildpack/reference/configure-webpack/#special-flags}.
+ */
+ flags[targets.name] = {
+ esModules: true,
+ cssModules: true,
+ graphqlQueries: true
+ };
+ });
+
+ const {
+ checkoutPagePaymentTypes,
+ editablePaymentTypes,
+ summaryPagePaymentTypes
+ } = targets.of('@magento/venia-ui');
+ checkoutPagePaymentTypes.tap(payments =>
+ payments.add({
+ paymentCode: 'cashondelivery',
+ importPath:
+ '@magento/venia-sample-payments-cashondelivery/src/components/cashondelivery.js'
+ })
+ );
+ editablePaymentTypes.tap(editablePaymentTypes => {
+ editablePaymentTypes.add({
+ paymentCode: 'cashondelivery',
+ importPath:
+ '@magento/venia-sample-payments-cashondelivery/src/components/editcod.js'
+ });
+ });
+ summaryPagePaymentTypes.tap(paymentSummaries =>
+ paymentSummaries.add({
+ paymentCode: 'cashondelivery',
+ importPath:
+ '@magento/venia-sample-payments-cashondelivery/src/components/summarycod.js'
+ })
+ );
+};
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/package.json b/packages/extensions/venia-sample-payments-cashondelivery/package.json
new file mode 100644
index 0000000000..eb3b7f2d06
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "@magento/venia-sample-payments-cashondelivery",
+ "version": "0.0.1",
+ "publishConfig": {
+ "access": "public"
+ },
+ "description": "Provides demo Cash On Delivery Payment Method for PWA Studio.",
+ "main": "./intercept.js",
+ "scripts": {
+ "clean": " "
+ },
+ "repository": "github:magento/pwa-studio",
+ "license": "(OSL-3.0 OR AFL-3.0)",
+ "peerDependencies": {
+ "@magento/peregrine": "~14.4.1",
+ "@magento/pwa-buildpack": "~11.5.3",
+ "@magento/venia-ui": "~11.5.0",
+ "react": "~17.0.1",
+ "react-intl": "~5.20.0",
+ "react-router-dom": "~5.2.0"
+ },
+ "pwa-studio": {
+ "targets": {
+ "intercept": "./intercept"
+ }
+ }
+}
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/cashondelivery.spec.js.snap b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/cashondelivery.spec.js.snap
new file mode 100644
index 0000000000..d2e81b2ea4
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/cashondelivery.spec.js.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Should render with mailingAddress payable 1`] = `
+
+
+
+`;
+
+exports[`Should return correct shape 1`] = `
+
+
+
+`;
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/editcod.spec.js.snap b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/editcod.spec.js.snap
new file mode 100644
index 0000000000..83573fbdbc
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/editcod.spec.js.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+`;
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/summarycod.spec.js.snap b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/summarycod.spec.js.snap
new file mode 100644
index 0000000000..26fcf254fd
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/__snapshots__/summarycod.spec.js.snap
@@ -0,0 +1,67 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/cashondelivery.spec.js b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/cashondelivery.spec.js
new file mode 100644
index 0000000000..2ae1723831
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/cashondelivery.spec.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import { createTestInstance } from '@magento/peregrine';
+import { useCashondelivery } from '../../talons/useCashondelivery';
+
+import CashOnDelivery from '../cashondelivery';
+
+jest.mock('@magento/venia-ui/lib/classify');
+jest.mock('../../talons/useCashondelivery', () => {
+ return {
+ useCashondelivery: jest.fn().mockReturnValue({
+ onBillingAddressChangedError: jest.fn(),
+ onBillingAddressChangedSuccess: jest.fn()
+ })
+ };
+});
+
+jest.mock(
+ '@magento/venia-ui/lib/components/CheckoutPage/BillingAddress',
+ () => props =>
+);
+
+const useCashondeliveryReturnValue = {
+ onBillingAddressChangedError: jest.fn(),
+ onBillingAddressChangedSuccess: jest.fn()
+};
+
+test('Should render CashOnDelivery component correctly', () => {
+ useCashondelivery.mockReturnValueOnce({
+ ...useCashondeliveryReturnValue
+ });
+
+ const tree = createTestInstance();
+
+ expect(tree.toJSON()).toMatchSnapshot();
+});
\ No newline at end of file
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/editcod.spec.js b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/editcod.spec.js
new file mode 100644
index 0000000000..baa11cd9da
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/editcod.spec.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import { createTestInstance } from '@magento/peregrine';
+
+import EditCod from '../editcod';
+
+jest.mock('@magento/venia-ui/lib/classify');
+jest.mock('../cashondelivery', () => props => );
+
+const mocks = {
+ onPaymentReady: jest.fn().mockName('onPaymentReady'),
+ onPaymentSuccess: jest.fn().mockName('onPaymentSuccess'),
+ onPaymentError: jest.fn().mockName('onPaymentError'),
+ resetShouldSubmit: jest.fn().mockName('resetShouldSubmit')
+};
+
+test('renders correctly', () => {
+ // Arrange.
+ const props = {
+ ...mocks,
+ shouldSubmit: false
+ };
+
+ // Act.
+ const tree = createTestInstance();
+
+ // Assert.
+ expect(tree.toJSON()).toMatchSnapshot();
+});
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/summarycod.spec.js b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/summarycod.spec.js
new file mode 100644
index 0000000000..8cdf105c3d
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/__tests__/summarycod.spec.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import { createTestInstance } from '@magento/peregrine';
+
+import SummaryCod from '../summarycod';
+
+jest.mock('@magento/venia-ui/lib/classify');
+jest.mock('react-intl', () => ({
+ FormattedMessage: props => (
+
+ )
+}));
+jest.mock('@magento/venia-ui/lib/components/LinkButton', () => props => (
+
+));
+jest.mock('@magento/venia-ui/lib/components/Icon', () => props => (
+
+));
+
+test('renders correctly', () => {
+ // Arrange.
+ const props = {
+ onEdit: jest.fn().mockName('onEdit')
+ };
+
+ // Act.
+ const tree = createTestInstance();
+
+ // Assert.
+ expect(tree.toJSON()).toMatchSnapshot();
+});
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/cashondelivery.js b/packages/extensions/venia-sample-payments-cashondelivery/src/components/cashondelivery.js
new file mode 100644
index 0000000000..9033ae2955
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/cashondelivery.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import { useStyle } from '@magento/venia-ui/lib/classify';
+import { shape, string, bool, func } from 'prop-types';
+import BillingAddress from '@magento/venia-ui/lib/components/CheckoutPage/BillingAddress';
+import { useCashondelivery } from '../talons/useCashondelivery';
+import defaultClasses from './cashondelivery.module.css';
+
+/**
+ * The CashOnDelivery component renders all information to handle cashondelivery payment.
+ *
+ * @param {Boolean} props.shouldSubmit boolean value which represents if a payment nonce request has been submitted
+ * @param {Function} props.onPaymentSuccess callback to invoke when the a payment nonce has been generated
+ * @param {Function} props.onPaymentReady callback to invoke when the component is ready
+ * @param {Function} props.onPaymentError callback to invoke when component throws an error
+ * @param {Function} props.resetShouldSubmit callback to reset the shouldSubmit flag
+ */
+const CashOnDelivery = props => {
+ const classes = useStyle(defaultClasses, props.classes);
+
+ const {
+ onBillingAddressChangedError,
+ onBillingAddressChangedSuccess
+ } = useCashondelivery(props);
+
+ return (
+
+
+
+
+ );
+};
+
+CashOnDelivery.propTypes = {
+ classes: shape({ root: string }),
+ shouldSubmit: bool.isRequired,
+ onPaymentSuccess: func,
+ onPaymentReady: func,
+ onPaymentError: func,
+ resetShouldSubmit: func.isRequired
+};
+
+export default CashOnDelivery;
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/cashondelivery.module.css b/packages/extensions/venia-sample-payments-cashondelivery/src/components/cashondelivery.module.css
new file mode 100644
index 0000000000..74b6bf3b50
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/cashondelivery.module.css
@@ -0,0 +1,3 @@
+.cod_root {
+ padding-top: 1.125rem;
+}
\ No newline at end of file
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/editcod.js b/packages/extensions/venia-sample-payments-cashondelivery/src/components/editcod.js
new file mode 100644
index 0000000000..4c2a7ec7f7
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/editcod.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import { shape, string, bool, func } from 'prop-types';
+
+import { useStyle } from '@magento/venia-ui/lib/classify';
+
+import CashOnDelivery from './cashondelivery';
+import defaultClasses from './cashondelivery.module.css';
+
+/**
+ * The edit view for the Cod payment method.
+ */
+const EditCod = props => {
+ const {
+ onPaymentReady,
+ onPaymentSuccess,
+ onPaymentError,
+ resetShouldSubmit,
+ shouldSubmit
+ } = props;
+
+ const classes = useStyle(defaultClasses, props.classes);
+
+ return (
+
+
+
+ );
+};
+
+export default EditCod;
+
+EditCod.propTypes = {
+ classes: shape({
+ root: string
+ }),
+ onPaymentReady: func.isRequired,
+ onPaymentSuccess: func.isRequired,
+ onPaymentError: func.isRequired,
+ resetShouldSubmit: func.isRequired,
+ shouldSubmit: bool
+};
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/summary.module.css b/packages/extensions/venia-sample-payments-cashondelivery/src/components/summary.module.css
new file mode 100644
index 0000000000..179532469f
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/summary.module.css
@@ -0,0 +1,42 @@
+.root {
+ display: grid;
+ gap: 1.125rem;
+ padding: 2rem;
+}
+
+.heading_container {
+ display: grid;
+ grid-auto-flow: column;
+ grid-template-columns: 1fr;
+}
+
+.heading {
+ font-weight: 600;
+}
+
+.edit_button {
+ color: rgb(var(--venia-brand-color-1-700));
+ padding: 1rem;
+ margin: -1rem;
+}
+
+.edit_icon {
+ stroke: rgb(var(--venia-brand-color-1-700));
+}
+
+.edit_text {
+}
+
+.cod_details_container {
+ display: grid;
+ gap: 0.5rem;
+}
+
+.payment_type {
+}
+
+@media screen(-lg) {
+ .edit_text {
+ display: none;
+ }
+}
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/components/summarycod.js b/packages/extensions/venia-sample-payments-cashondelivery/src/components/summarycod.js
new file mode 100644
index 0000000000..346514048d
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/components/summarycod.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import { func, shape, string } from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+import { Edit2 as EditIcon } from 'react-feather';
+import { useStyle } from '@magento/venia-ui/lib/classify';
+import Icon from '@magento/venia-ui/lib/components/Icon';
+import LinkButton from '@magento/venia-ui/lib/components/LinkButton';
+
+import defaultClasses from './summary.module.css';
+
+/**
+ * The SummaryCod component of the Cash On Delivery payment method extension.
+ */
+const SummaryCod = props => {
+ const { onEdit } = props;
+
+ const classes = useStyle(defaultClasses, props.classes);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SummaryCod;
+
+SummaryCod.propTypes = {
+ classes: shape({
+ root: string,
+ cod_details_container: string,
+ edit_button: string,
+ edit_icon: string,
+ edit_text: string,
+ heading_container: string,
+ heading: string,
+ payment_type: string
+ }),
+ onEdit: func
+};
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/talons/cashondelivery.gql.js b/packages/extensions/venia-sample-payments-cashondelivery/src/talons/cashondelivery.gql.js
new file mode 100644
index 0000000000..fe2cff7490
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/talons/cashondelivery.gql.js
@@ -0,0 +1,21 @@
+import { gql } from '@apollo/client';
+
+export const SET_COD_PAYMENT_METHOD_ON_CART = gql`
+ mutation setPaymentMethodOnCart($cartId: String!) {
+ setPaymentMethodOnCart(
+ input: { cart_id: $cartId, payment_method: { code: "cashondelivery" } }
+ ) {
+ cart {
+ id
+ selected_payment_method {
+ code
+ title
+ }
+ }
+ }
+ }
+`;
+
+export default {
+ setCodPaymentMethodOnCartMutation: SET_COD_PAYMENT_METHOD_ON_CART
+};
diff --git a/packages/extensions/venia-sample-payments-cashondelivery/src/talons/useCashondelivery.js b/packages/extensions/venia-sample-payments-cashondelivery/src/talons/useCashondelivery.js
new file mode 100644
index 0000000000..623c9bb16a
--- /dev/null
+++ b/packages/extensions/venia-sample-payments-cashondelivery/src/talons/useCashondelivery.js
@@ -0,0 +1,82 @@
+import { useCallback, useEffect } from 'react';
+import { useQuery, useMutation } from '@apollo/client';
+import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
+import { useCartContext } from '@magento/peregrine/lib/context/cart';
+
+import DEFAULT_OPERATIONS from './cashondelivery.gql';
+
+/**
+ * Talon to handle cod payment.
+ *
+ * @param {Boolean} props.shouldSubmit boolean value which represents if a payment nonce request has been submitted
+ * @param {Function} props.onPaymentSuccess callback to invoke when the a payment nonce has been generated
+ * @param {Function} props.onPaymentError callback to invoke when component throws an error
+ * @param {Function} props.resetShouldSubmit callback to reset the shouldSubmit flag
+ * @param {DocumentNode} props.operations.setCodPaymentMethodOnCartMutation mutation to set cashondelivery as payment
+ *
+ * @returns {
+ * onBillingAddressChangedError: Function,
+ * onBillingAddressChangedSuccess: Function
+ * }
+ */
+export const useCashondelivery = props => {
+ const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
+
+ const {
+ setCodPaymentMethodOnCartMutation
+ } = operations;
+
+ const [{ cartId }] = useCartContext();
+
+ const { resetShouldSubmit, onPaymentSuccess, onPaymentError } = props;
+
+ const [
+ updatePaymentMethod,
+ {
+ error: paymentMethodMutationError,
+ called: paymentMethodMutationCalled,
+ loading: paymentMethodMutationLoading
+ }
+ ] = useMutation(setCodPaymentMethodOnCartMutation);
+
+ /**
+ * This function will be called if cant not set address.
+ */
+ const onBillingAddressChangedError = useCallback(() => {
+ resetShouldSubmit();
+ }, [resetShouldSubmit]);
+
+ /**
+ * This function will be called if address was successfully set.
+ */
+ const onBillingAddressChangedSuccess = useCallback(() => {
+ updatePaymentMethod({
+ variables: { cartId }
+ });
+ }, [updatePaymentMethod, cartId]);
+
+ useEffect(() => {
+ const paymentMethodMutationCompleted =
+ paymentMethodMutationCalled && !paymentMethodMutationLoading;
+
+ if (paymentMethodMutationCompleted && !paymentMethodMutationError) {
+ onPaymentSuccess();
+ }
+
+ if (paymentMethodMutationCompleted && paymentMethodMutationError) {
+ onPaymentError();
+ }
+ }, [
+ paymentMethodMutationError,
+ paymentMethodMutationLoading,
+ paymentMethodMutationCalled,
+ onPaymentSuccess,
+ onPaymentError,
+ resetShouldSubmit
+ ]);
+
+ return {
+ onBillingAddressChangedError,
+ onBillingAddressChangedSuccess
+ };
+};