Engineering Notes on Set Up

 
docker ps -f "name=db"
docker inspect <container_id> # 
Update the .env file, below is for local instance of PostGres
DATABASE_URL=postgres://postgres:postgres@172.30.0.2:5432/bankless-checkout
 
API Notes
API Deprecation Notice: {
┃   message: 'https://shopify.dev/api/usage/versioning#deprecation-practices',
┃   path: 'https://rosemary-consulting.myshopify.com/admin/api/2021-10/graphql.json'}API Deprecation Notice: {
┃   message: 'https://shopify.dev/changelog/online-store-2-0-new-metafields-type-system-and-dynamic-sources',
┃   path: 'https://rosemary-consulting.myshopify.com/admin/api/2021-10/metafields.json?limit=1'}
 
Heroku
==> heroku
To use the Heroku CLI's autocomplete --
  Via homebrew's shell completion:
    1) Follow homebrew's install instructions https://docs.brew.sh/Shell-Completion
        NOTE: For zsh, as the instructions mention, be sure compinit is autoloaded
              and called, either explicitly or via a framework like oh-my-zsh.
    2) Then run
      $ heroku autocomplete --refresh-cache
  OR
  Use our standalone setup:
    1) Run and follow the install steps:
      $ heroku autocomplete

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d
Rosemarys-MBP-2:bankless-checkout rose$
 
// Deleting Store from Heroku DB
bankless-checkout-dev::DATABASE=> select * from shops;
                  id                  |                  name                  |          online_access_token           |          offline_access_token          |          updated_at           |          created_at
--------------------------------------+----------------------------------------+----------------------------------------+----------------------------------------+-------------------------------+-------------------------------
 f39c617e-dcf6-46c1-b7e4-62e43a902447 | bankless-dao-merch-store.myshopify.com | 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 | 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 | 2022-03-25 17:44:45.086136+00 | 2022-03-25 17:44:42.755031+00
 9863555b-7bfc-4b39-839f-772df25c9225 | banklessdao.myshopify.com              | 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 | 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 | 2022-04-08 18:52:23.048793+00 | 2022-03-29 23:13:32.391683+00
(2 rows)

bankless-checkout-dev::DATABASE=> DELETE FROM shops WHERE name='banklessdao.myshopify.com';
Shopify Set Up
import { Provider, useAppBridge } from "@shopify/app-bridge-react";
 
https://51e1-71-172-157-52.ngrok.io/offline/auth/callback
https://51e1-71-172-157-52.ngrok.io/online/auth/callback
https://51e1-71-172-157-52.ngrok.io/auth/callback
 
Update for koa-auth
Database Schema
This needs to be performed on the DB manually.
-- shops table requires Extension CITEXT --
CREATE EXTENSION IF NOT EXISTS citext;

-- shops table requies Extension uuid_generate_v4 --
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- public.shops definition

-- Drop table

-- DROP TABLE public.shops;

CREATE TABLE public.shops (
	id uuid NOT NULL DEFAULT uuid_generate_v4(),
	"name" public."citext" NOT NULL,
	online_access_token text NULL,
	offline_access_token text NULL,
	updated_at timestamptz NOT NULL DEFAULT now(),
	created_at timestamptz NOT NULL DEFAULT now(),
	CONSTRAINT shops_name_key UNIQUE (name),
	CONSTRAINT shops_pkey PRIMARY KEY (id)
);

-- public.redeemed_tokens definition

-- Drop table

-- DROP TABLE public.redeemed_tokens;

CREATE TABLE public.redeemed_tokens (
	id uuid NOT NULL DEFAULT uuid_generate_v4(),
	deployed_contract_address text NOT NULL,
	token_id text NOT NULL,
	shopify_product_id text NOT NULL,
	redeemed_quantity int4 NULL,
	shopify_customer_id text NOT NULL,
	redeemed_at timestamptz NOT NULL DEFAULT now(),
	updated_at timestamptz NOT NULL DEFAULT now(),
	created_at timestamptz NOT NULL DEFAULT now(),
	CONSTRAINT redeemed_tokens_pkey PRIMARY KEY (id)
);
Error with GraphQL
 
notion image
 
 
• Custom apps created in the Partner Dashboard that are then embedded in the Shopify admin use OAuth and session tokens.
Error Data: {
	"graphQLErrors":[],
	"clientErrors":[],
	"networkError":{
		"name":"ServerParseError",
		"response":{},
		"statusCode":405,
		"bodyText":"Method Not Allowed"
	},
	"message":"Unexpected token M in JSON at position 0"
}

const client = new ApolloClient({
    cache: new InMemoryCache(),
    link: from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          graphQLErrors.map(({ message, locations, path}) => 
          console.log(`[GraphQL error] : Message ${message},
          Location:
          ${locations}, Path ${path}`));
          if (networkError) {
            console.log(`[Network Error]: ${networkError}`);
          }
        }
      }),
      new HttpLink({
        uri: process.env.HOST + "/admin/api/graphql.json"
      })
    ])
  });

const client = new ApolloClient({
    cache: new InMemoryCache(),
    ssrMode: true,
    link: from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          graphQLErrors.map(({ message, locations, path}) => 
          console.log(`[GraphQL error] : Message ${message},
          Location:
          ${locations}, Path ${path}`));
          if (networkError) {
            console.log(`[Network Error]: ${networkError}`);
          }
        }
      }),
      new HttpLink({
        uri: process.env.HOST + "/admin/api/graphql.json"
      })
    ])
  });

// Loading... always true
 const client = new ApolloClient({
    link: new HttpLink({
      credentials: "same-origin",
      fetch: authenticatedFetch(app, userLoggedInFetch), // ensures that your custom fetch wrapper is authenticated
    }),
    cache: new InMemoryCache(),
  });

 
Note that mode: "no-cors" only allows a limited set of headers in the request:
  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type with a value of application/x-www-form-urlencoded, multipart/form-data, or text/plain
function MyProvider(props) {
  const app = useAppBridge();
  console.log("update 5");
  const httpLink = new HttpLink({
    credentials: 'include',
    uri: "https://rosemary-consulting.myshopify.com/admin/api/graphql.json",
    headers: {
      "Content-Type": "application/graphql",
      // "X-Shopify-Access-Token": "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"
    },
    fetchOptions: {
      mode:"cors"
    },
  });
  
  const errorLink = onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors)
      graphQLErrors.forEach(({ message, locations, path }) =>
        console.log(
          `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
        ),
      );
  
    if (networkError) console.log(`[Network error]: ${networkError}`);
  });
  
  // If you provide a link chain to ApolloClient, you
  // don't provide the `uri` option.
  const client = new ApolloClient({
    // The `from` function combines an array of individual links
    // into a link chain
    link: from([errorLink, httpLink]),
    cache: new InMemoryCache()
  });
---
[Network error]: TypeError: Failed to fetch
help.js?ca16:14 Error: Failed to fetch
help.js?ca16:15 Error Data: {"graphQLErrors":[],"clientErrors":[],"networkError":{},"message":"Failed to fetch"}
 
Koa Router Issues
Custom Code updates to Shopify Templates
Modify cart-notification to not always show the checkout button. cart-notification.liquid
{%- assign show_checkout_button = true -%}
{% for line_item in cart.items %}
	{% if line_item.product.metafields.banklessCheckout.redeemable_with_nft.value == true %}
		{%- assign show_checkout_button = false -%}
	{% endif %}
{% endfor %}
...
{% if show_checkout_button %}
   <form action="{{ routes.cart_url }}" method="post" id="cart-notification-form">
      <button class="button button--primary button--full-width" name="checkout">{{ 'sections.cart.checkout' | t }}</button>
   </form>
{% endif %}
Sections/main-cart-footer.liquid
{%- assign show_checkout_button = true -%}
{% if cart != empty %}
{% for line_item in cart.items %}
	{% if line_item.product.metafields.banklessCheckout.redeemable_with_nft.value %}
		{%- assign show_checkout_button = false -%}
	{% endif %}
{% endfor %}
{% endif %}

...
{%- if show_checkout_button -%}
   <button type="submit" id="checkout" class="cart__checkout-button button" name="checkout"{% if cart == empty %} disabled{% endif %} form="cart">
      {{ 'sections.cart.checkout' | t }}
   </button>
{%- endif -%}
footer.liquid → Adding a discord link
{%- if settings.social_discord_link != blank -%}
    <li class="list-social__item">
      <a href="{{ settings.social_discord_link }}" class="link list-social__link" >
        {%- render 'icon-discord' -%}
        <span class="visually-hidden">{{ 'general.social.links.discord' | t }}</span>
      </a>
    </li>
{%- endif -%}
icon-discord.liquid
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="icon icon-discord" viewBox="0 0 16 16">
  <path fill-rule="evenodd" d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z" fill="currentColor"/>
</svg>
 
notion image
Add the updates to the social schema and locale files for discord link.
 
Security Checklist
What data is ok to store in a database?
Hash an email address in the database
What data to store in database
Do we need to do analysis of 3rd party vendors?
AWS account can also be an option to securely store our data, if we decide to move over there.
 
GDPR →
 
 

Engineering Notes on Set Up

 
docker ps -f "name=db"
docker inspect <container_id> # 
Update the .env file, below is for local instance of PostGres
DATABASE_URL=postgres://postgres:postgres@172.30.0.2:5432/bankless-checkout
 
API Notes
API Deprecation Notice: {
┃   message: 'https://shopify.dev/api/usage/versioning#deprecation-practices',
┃   path: 'https://rosemary-consulting.myshopify.com/admin/api/2021-10/graphql.json'}API Deprecation Notice: {
┃   message: 'https://shopify.dev/changelog/online-store-2-0-new-metafields-type-system-and-dynamic-sources',
┃   path: 'https://rosemary-consulting.myshopify.com/admin/api/2021-10/metafields.json?limit=1'}
 
Heroku
==> heroku
To use the Heroku CLI's autocomplete --
  Via homebrew's shell completion:
    1) Follow homebrew's install instructions https://docs.brew.sh/Shell-Completion
        NOTE: For zsh, as the instructions mention, be sure compinit is autoloaded
              and called, either explicitly or via a framework like oh-my-zsh.
    2) Then run
      $ heroku autocomplete --refresh-cache
  OR
  Use our standalone setup:
    1) Run and follow the install steps:
      $ heroku autocomplete

Bash completion has been installed to:
  /usr/local/etc/bash_completion.d
Rosemarys-MBP-2:bankless-checkout rose$
 
// Deleting Store from Heroku DB
bankless-checkout-dev::DATABASE=> select * from shops;
                  id                  |                  name                  |          online_access_token           |          offline_access_token          |          updated_at           |          created_at
--------------------------------------+----------------------------------------+----------------------------------------+----------------------------------------+-------------------------------+-------------------------------
 f39c617e-dcf6-46c1-b7e4-62e43a902447 | bankless-dao-merch-store.myshopify.com | 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 | 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 | 2022-03-25 17:44:45.086136+00 | 2022-03-25 17:44:42.755031+00
 9863555b-7bfc-4b39-839f-772df25c9225 | banklessdao.myshopify.com              | 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 | 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 | 2022-04-08 18:52:23.048793+00 | 2022-03-29 23:13:32.391683+00
(2 rows)

bankless-checkout-dev::DATABASE=> DELETE FROM shops WHERE name='banklessdao.myshopify.com';
Shopify Set Up
import { Provider, useAppBridge } from "@shopify/app-bridge-react";
 
https://51e1-71-172-157-52.ngrok.io/offline/auth/callback
https://51e1-71-172-157-52.ngrok.io/online/auth/callback
https://51e1-71-172-157-52.ngrok.io/auth/callback
 
Update for koa-auth
Database Schema
This needs to be performed on the DB manually.
-- shops table requires Extension CITEXT --
CREATE EXTENSION IF NOT EXISTS citext;

-- shops table requies Extension uuid_generate_v4 --
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- public.shops definition

-- Drop table

-- DROP TABLE public.shops;

CREATE TABLE public.shops (
	id uuid NOT NULL DEFAULT uuid_generate_v4(),
	"name" public."citext" NOT NULL,
	online_access_token text NULL,
	offline_access_token text NULL,
	updated_at timestamptz NOT NULL DEFAULT now(),
	created_at timestamptz NOT NULL DEFAULT now(),
	CONSTRAINT shops_name_key UNIQUE (name),
	CONSTRAINT shops_pkey PRIMARY KEY (id)
);

-- public.redeemed_tokens definition

-- Drop table

-- DROP TABLE public.redeemed_tokens;

CREATE TABLE public.redeemed_tokens (
	id uuid NOT NULL DEFAULT uuid_generate_v4(),
	deployed_contract_address text NOT NULL,
	token_id text NOT NULL,
	shopify_product_id text NOT NULL,
	redeemed_quantity int4 NULL,
	shopify_customer_id text NOT NULL,
	redeemed_at timestamptz NOT NULL DEFAULT now(),
	updated_at timestamptz NOT NULL DEFAULT now(),
	created_at timestamptz NOT NULL DEFAULT now(),
	CONSTRAINT redeemed_tokens_pkey PRIMARY KEY (id)
);
Error with GraphQL
 
notion image
 
 
• Custom apps created in the Partner Dashboard that are then embedded in the Shopify admin use OAuth and session tokens.
Error Data: {
	"graphQLErrors":[],
	"clientErrors":[],
	"networkError":{
		"name":"ServerParseError",
		"response":{},
		"statusCode":405,
		"bodyText":"Method Not Allowed"
	},
	"message":"Unexpected token M in JSON at position 0"
}

const client = new ApolloClient({
    cache: new InMemoryCache(),
    link: from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          graphQLErrors.map(({ message, locations, path}) => 
          console.log(`[GraphQL error] : Message ${message},
          Location:
          ${locations}, Path ${path}`));
          if (networkError) {
            console.log(`[Network Error]: ${networkError}`);
          }
        }
      }),
      new HttpLink({
        uri: process.env.HOST + "/admin/api/graphql.json"
      })
    ])
  });

const client = new ApolloClient({
    cache: new InMemoryCache(),
    ssrMode: true,
    link: from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          graphQLErrors.map(({ message, locations, path}) => 
          console.log(`[GraphQL error] : Message ${message},
          Location:
          ${locations}, Path ${path}`));
          if (networkError) {
            console.log(`[Network Error]: ${networkError}`);
          }
        }
      }),
      new HttpLink({
        uri: process.env.HOST + "/admin/api/graphql.json"
      })
    ])
  });

// Loading... always true
 const client = new ApolloClient({
    link: new HttpLink({
      credentials: "same-origin",
      fetch: authenticatedFetch(app, userLoggedInFetch), // ensures that your custom fetch wrapper is authenticated
    }),
    cache: new InMemoryCache(),
  });

 
Note that mode: "no-cors" only allows a limited set of headers in the request:
  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type with a value of application/x-www-form-urlencoded, multipart/form-data, or text/plain
function MyProvider(props) {
  const app = useAppBridge();
  console.log("update 5");
  const httpLink = new HttpLink({
    credentials: 'include',
    uri: "https://rosemary-consulting.myshopify.com/admin/api/graphql.json",
    headers: {
      "Content-Type": "application/graphql",
      // "X-Shopify-Access-Token": "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"
    },
    fetchOptions: {
      mode:"cors"
    },
  });
  
  const errorLink = onError(({ graphQLErrors, networkError }) => {
    if (graphQLErrors)
      graphQLErrors.forEach(({ message, locations, path }) =>
        console.log(
          `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
        ),
      );
  
    if (networkError) console.log(`[Network error]: ${networkError}`);
  });
  
  // If you provide a link chain to ApolloClient, you
  // don't provide the `uri` option.
  const client = new ApolloClient({
    // The `from` function combines an array of individual links
    // into a link chain
    link: from([errorLink, httpLink]),
    cache: new InMemoryCache()
  });
---
[Network error]: TypeError: Failed to fetch
help.js?ca16:14 Error: Failed to fetch
help.js?ca16:15 Error Data: {"graphQLErrors":[],"clientErrors":[],"networkError":{},"message":"Failed to fetch"}
 
Koa Router Issues
Custom Code updates to Shopify Templates
Modify cart-notification to not always show the checkout button. cart-notification.liquid
{%- assign show_checkout_button = true -%}
{% for line_item in cart.items %}
	{% if line_item.product.metafields.banklessCheckout.redeemable_with_nft.value == true %}
		{%- assign show_checkout_button = false -%}
	{% endif %}
{% endfor %}
...
{% if show_checkout_button %}
   <form action="{{ routes.cart_url }}" method="post" id="cart-notification-form">
      <button class="button button--primary button--full-width" name="checkout">{{ 'sections.cart.checkout' | t }}</button>
   </form>
{% endif %}
Sections/main-cart-footer.liquid
{%- assign show_checkout_button = true -%}
{% if cart != empty %}
{% for line_item in cart.items %}
	{% if line_item.product.metafields.banklessCheckout.redeemable_with_nft.value %}
		{%- assign show_checkout_button = false -%}
	{% endif %}
{% endfor %}
{% endif %}

...
{%- if show_checkout_button -%}
   <button type="submit" id="checkout" class="cart__checkout-button button" name="checkout"{% if cart == empty %} disabled{% endif %} form="cart">
      {{ 'sections.cart.checkout' | t }}
   </button>
{%- endif -%}
footer.liquid → Adding a discord link
{%- if settings.social_discord_link != blank -%}
    <li class="list-social__item">
      <a href="{{ settings.social_discord_link }}" class="link list-social__link" >
        {%- render 'icon-discord' -%}
        <span class="visually-hidden">{{ 'general.social.links.discord' | t }}</span>
      </a>
    </li>
{%- endif -%}
icon-discord.liquid
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="icon icon-discord" viewBox="0 0 16 16">
  <path fill-rule="evenodd" d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z" fill="currentColor"/>
</svg>
 
notion image
Add the updates to the social schema and locale files for discord link.
 
Security Checklist
What data is ok to store in a database?
Hash an email address in the database
What data to store in database
Do we need to do analysis of 3rd party vendors?
AWS account can also be an option to securely store our data, if we decide to move over there.
 
GDPR →