Giter Site home page Giter Site logo

whammy's Introduction

whammy

Trying things with netlify & stripe

Start the netlify local dev server, watch css and js files

npm start

Build everything, the same way it will be deployed

npm run build

Tests for the buy things page

npm test

Open the cypress test gui

npm run test:open

test cards

4242424242424242

Succeeds and immediately processes the payment.

4000000000003220

3D Secure 2 authentication must be completed for a successful payment.

4000000000009995

Always fails with a decline code of insufficient_funds.

4000002760003184

A test card number that requires authentication.


Products and Prices

If you only have a few products, you should create and manage them in the Dashboard

List all products

require('dotenv').config()
var stripe = require('stripe')(process.env.STRIPE_SECRET);

stripe.products.list({ limit: 3 }, function (err, products) {
    // asynchronously called
});

Create a customer

Before billing a customer, you need to create a Customer object that you can configure with a name, email, and payment method.

// Set your secret key. Remember to switch to your live secret key in production!
// See your keys here: https://dashboard.stripe.com/account/apikeys
const stripe = require('stripe')('sk_test_51GrU9fGmvbUUvDLHxIdVkGP7SsJv9Re4AY6gJ4E9rR55pEIozVyX0BF2H8CO2mpYuZg3eDr4ftjjmTD9GNKsJoMk00wn6cXykX');

const customer = await stripe.customers.create({
  email: '[email protected]',
  payment_method: 'pm_card_visa',
  invoice_settings: {
    default_payment_method: 'pm_card_visa',
  },
});

checkout


TODO today

for the checkout

var stripe = require('stripe')('sk_test_51GrU9fGmvbUUvDLHxIdVkGP7SsJv9Re4AY6gJ4E9rR55pEIozVyX0BF2H8CO2mpYuZg3eDr4ftjjmTD9GNKsJoMk00wn6cXykX');

stripe.products.retrieve('prod_HQTNO4cLeDwzDX', function (err, product) {
    // asynchronously called
});

todo

  • redirect to a good place after purchase
  • make sure product stock is ok
  • sku has a specific meaning. should change it's use in the code
  • use the lookup_key for prices to relate them to a product
  • only list products that have stock

How to access query parameters in Netlify functions

Email receipts

Stripe can automatically send email receipts after a successful payment, or when you refund one. This is done by providing an email address when making the API request, using the email address of a Customer object, or updating a PaymentIntent with a customer’s email address after checkout Receipts for payments created using your test API keys are not sent automatically. Instead, you can view or manually send a receipt using the Dashboard.


  • could get products at build time instead of on page load. But that doesn't work because the stock needs to be up to date.
  • make the sucess/cancel pages

should maybe use fauna + netlify CMS like originally planned b/c stripe doesn't keep stock for us

on product create: netlify CMS -> ntl function -- create fauna doc

on Purchase: ntl fn -> stock -1 in fauna, purchase in stripe Need to use some kind of listener on stripe to know when to -1 the stock

comerce.js

3% transaction fee -- $2000/month sales commerce.js commerce.js docs -- cart api capture order get cart contents


Accept a payment

Use elements in the browser & paymentIntent on the server to make a payment. Use paymentIntent.client_secret


And it's back to using the netlify CMS to create docs in fauna I guess.

Registering to CMS Events

Supported events are prePublish, postPublish, preUnpublish, postUnpublish, preSave and postSave

CMS.registerEventListener({
    name: 'prePublish',
    handler: function ({ author, entry }) {
        var str = JSON.stringify({ author, data: entry.get('data') })
        console.log('prepublish', str)
    }
});

{
  "author":{"login":"[email protected]","name":"nichoth"},
  "data":{
    "name":"two",
    "pic":"images/uploads/vag-eye.jpg",
    "description":"woo two description"
  }
}

Create, retrieve, update, and delete documents in FaunaDB

Quick start with FaunaDB

Create


FaunaDB/Cookbook/Overview

Reading or writing database definitions requires an admin key.

var adminClient = new faunadb.Client({
  secret: adminKey
})

adminClient.query(
  q.CreateDatabase({ name: 'annuvin' })
)
.then((ret) => console.log(ret))

Create a document in a collection -- spells example

Create a post

Building Serverless CRUD apps with Netlify Functions & FaunaDB

If it’s a valid token issued by the Identity instance linked to the site, Netlify will add the user’s claims in a context.clientContext.user object. You can use this object to add a little guard clause to the handler method in slack.js, blocking access for users who haven’t logged in JAMstack architecture on Netlify: How Identity and Functions work together


Thur 6-18 Authenticate users before allowing product create URL call.

identity -- example of event listeners on global identity


today

  • automated tests
    • need to know which directory is served as root from tape-run
    • write tests
  • list the products
  • make a quantity field in the products

  • list the products

  • front-end can display products

  • write a test

  • there is a slug for the product URL -- put it in DB slug = URL fetch('get-product', { body: { slug } })

    or

    --get them all in eleventy and make a separate page for each with the slug as path--

Need to do it at run time so stock is up to date

Building Serverless CRUD apps with Netlify Functions & FaunaDB

Create, retrieve, update, and delete documents in FaunaDB

You can query for posts with a specific title using the match function and the index we created earlier


  • has a 404 page for missing slug
  • make page for single product

  • use a separate html page for the product page. Without the logo
  • css for single product page
  • stripe purchase works

https://stripe.com/docs/payments/accept-a-payment#web


6-28

  • should show error messages for bad payment
  • show a success page after payment
  • should show CMS error states
  • diff pages by genre. Need genre input in CMS
  • can "tag" things
  • shipping cost calculator
  • can delete things
  • description in CMS is optional
  • localhost identity widget forwards to the right place
  • pagination is ok in fauna query
  • create an order
  • fix config.yml

Registering to CMS Events -- list of CMS events

Supported events are prePublish, postPublish, preUnpublish, postUnpublish, preSave and postSave.

https://github.com/netlify/netlify-cms/blob/2b46608f86d22c8ad34f75e396be7c34462d9e99/packages/netlify-cms-core/src/lib/registry.js#L6

const allowedEvents = [
  'prePublish',
  'postPublish',
  'preUnpublish',
  'postUnpublish',
  'preSave',
  'postSave',
];
  console.log('slug', entry.get('slug'))

https://docs.fauna.com/fauna/current/tutorials/indexes/pagination.html#query-large

The Paginate function defaults to returning up to 64 documents in a "page", which is a subset of the results.

If you use the github or google button on the identity login, it will redirect to the live site URL instead of localhost, but it works with the password.


fauna ecommerce delete a post delete


stripe webhook stuff


webhook test

Serve the functions locally:

npm start

Setup the stripe webhook destination

stripe listen --forward-to localhost:54901/stripe-webhook

|^ You need to change the port to the right one (the one returned by npm start) |^ Need to get the secret that is returned from this and put it in env for the function

Send the webhook request (it has the right body & headers automatically)

$ stripe trigger payment_intent.succeeded
serveFunctions({
    functionsDir: path.join(__dirname, '..', 'functions'),
    port: 9090
});

netlify/cli#566 https://stripe.com/docs/stripe-cli/webhooks#forward-events https://stripe.com/docs/api/events/types https://stripe.com/docs/api/payment_intents/object https://stripe.com/docs/webhooks/test?lang=node https://stripe.com/docs/webhooks/build?lang=node

stripeEv {
  id: 'evt_1H0yxlBnqQbRlIvQOcDabxh4',
  object: 'event',
  api_version: '2020-03-02',
  created: 1593821435,
  data: {
    object: {
      id: 'pi_1H0yxjBnqQbRlIvQWlHcTMCr',
      object: 'payment_intent',
      amount: 2000,
      amount_capturable: 0,
      amount_received: 0,
      application: null,
      application_fee_amount: null,
      canceled_at: null,
      cancellation_reason: null,
      capture_method: 'automatic',
      charges: [Object],
      client_secret: 'pi_1H0yxjBnqQbRlIvQWlHcTMCr_secret_mXGt3NGtYkEjDEqGrOVE2QUQk',
      confirmation_method: 'automatic',
      created: 1593821435,
      currency: 'usd',
      customer: null,
      description: '(created by Stripe CLI)',
      invoice: null,
      last_payment_error: null,
      livemode: false,
      metadata: {},
      next_action: null,
      on_behalf_of: null,
      payment_method: null,
      payment_method_options: [Object],
      payment_method_types: [Array],
      receipt_email: null,
      review: null,
      setup_future_usage: null,
      shipping: [Object],
      source: null,
      statement_descriptor: null,
      statement_descriptor_suffix: null,
      status: 'requires_payment_method',
      transfer_data: null,
      transfer_group: null
    }
  },
  livemode: false,
  pending_webhooks: 2,
  request: { id: 'req_JaobDjxa7aP0tW', idempotency_key: null },
  type: 'payment_intent.created'
}

.data {
  object: {
    id: 'pi_1H0yxjBnqQbRlIvQWlHcTMCr',
    object: 'payment_intent',
    amount: 2000,
    amount_capturable: 0,
    amount_received: 0,
    application: null,
    application_fee_amount: null,
    canceled_at: null,
    cancellation_reason: null,
    capture_method: 'automatic',
    charges: {
      object: 'list',
      data: [],
      has_more: false,
      total_count: 0,
      url: '/v1/charges?payment_intent=pi_1H0yxjBnqQbRlIvQWlHcTMCr'
    },
    client_secret: 'pi_1H0yxjBnqQbRlIvQWlHcTMCr_secret_mXGt3NGtYkEjDEqGrOVE2QUQk',
    confirmation_method: 'automatic',
    created: 1593821435,
    currency: 'usd',
    customer: null,
    description: '(created by Stripe CLI)',
    invoice: null,
    last_payment_error: null,
    livemode: false,
    metadata: {},
    next_action: null,
    on_behalf_of: null,
    payment_method: null,
    payment_method_options: { card: [Object] },
    payment_method_types: [ 'card' ],
    receipt_email: null,
    review: null,
    setup_future_usage: null,
    shipping: {
      address: [Object],
      carrier: null,
      name: 'Jenny Rosen',
      phone: null,
      tracking_number: null
    },
    source: null,
    statement_descriptor: null,
    statement_descriptor_suffix: null,
    status: 'requires_payment_method',
    transfer_data: null,
    transfer_group: null
  }
}

Elements validates user input as it is typed. To help your customers catch mistakes, listen to change events on the card Element and display any errors

Use the Dashboard, a custom webhook, or a third-party plugin to handle post-payment events like sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.

^ https://stripe.com/docs/payments/checkout/accept-a-payment?lang=node

metadata


https://stripe.com/docs/payments/checkout/accept-a-payment

To acknowledge receipt of an event, your endpoint must return a 2xx HTTP status code to Stripe. All response codes outside this range, including 3xx codes, indicate to Stripe that you did not receive the event. https://stripe.com/docs/webhooks/build?lang=node#return-a-2xx-status-code-quickly

Shipping address collection


stripe webhook payload

{
  "id": "evt_1H0yxlBnqQbRlIvQHy2QnROF",
  "object": "event",
  "api_version": "2020-03-02",
  "created": 1593821436,
  "data": {
    "object": {
      "id": "pi_1H0yxjBnqQbRlIvQWlHcTMCr",
      "object": "payment_intent",
      "amount": 2000,
      "amount_capturable": 0,
      "amount_received": 2000,
      "application": null,
      "application_fee_amount": null,
      "canceled_at": null,
      "cancellation_reason": null,
      "capture_method": "automatic",
      "charges": {
        "object": "list",
        "data": [
          {
            "id": "ch_1H0yxkBnqQbRlIvQ4E6pnq5s",
            "object": "charge",
            "amount": 2000,
            "amount_refunded": 0,
            "application": null,
            "application_fee": null,
            "application_fee_amount": null,
            "balance_transaction": "txn_1H0yxkBnqQbRlIvQS1apGxi2",
            "billing_details": {
              "address": {
                "city": null,
                "country": null,
                "line1": null,
                "line2": null,
                "postal_code": null,
                "state": null
              },
              "email": null,
              "name": null,
              "phone": null
            },
            "calculated_statement_descriptor": "Stripe",
            "captured": true,
            "created": 1593821436,
            "currency": "usd",
            "customer": null,
            "description": "(created by Stripe CLI)",
            "destination": null,
            "dispute": null,
            "disputed": false,
            "failure_code": null,
            "failure_message": null,
            "fraud_details": {
            },
            "invoice": null,
            "livemode": false,
            "metadata": {
            },
            "on_behalf_of": null,
            "order": null,
            "outcome": {
              "network_status": "approved_by_network",
              "reason": null,
              "risk_level": "normal",
              "risk_score": 7,
              "seller_message": "Payment complete.",
              "type": "authorized"
            },
            "paid": true,
            "payment_intent": "pi_1H0yxjBnqQbRlIvQWlHcTMCr",
            "payment_method": "pm_1H0yxjBnqQbRlIvQckXyRocI",
            "payment_method_details": {
              "card": {
                "brand": "visa",
                "checks": {
                  "address_line1_check": null,
                  "address_postal_code_check": null,
                  "cvc_check": null
                },
                "country": "US",
                "exp_month": 7,
                "exp_year": 2021,
                "fingerprint": "7vCGW0oukBtkxGQE",
                "funding": "credit",
                "installments": null,
                "last4": "4242",
                "network": "visa",
                "three_d_secure": null,
                "wallet": null
              },
              "type": "card"
            },
            "receipt_email": null,
            "receipt_number": null,
            "receipt_url": "https://pay.stripe.com/receipts/acct_1GlJDeBnqQbRlIvQ/ch_1H0yxkBnqQbRlIvQ4E6pnq5s/rcpt_Ha9G7PYQqEdXtO2earxkQhRWF14fXF6",
            "refunded": false,
            "refunds": {
              "object": "list",
              "data": [
              ],
              "has_more": false,
              "total_count": 0,
              "url": "/v1/charges/ch_1H0yxkBnqQbRlIvQ4E6pnq5s/refunds"
            },
            "review": null,
            "shipping": {
              "address": {
                "city": "San Francisco",
                "country": "US",
                "line1": "510 Townsend St",
                "line2": null,
                "postal_code": "94103",
                "state": "CA"
              },
              "carrier": null,
              "name": "Jenny Rosen",
              "phone": null,
              "tracking_number": null
            },
            "source": null,
            "source_transfer": null,
            "statement_descriptor": null,
            "statement_descriptor_suffix": null,
            "status": "succeeded",
            "transfer_data": null,
            "transfer_group": null
          }
        ],
        "has_more": false,
        "total_count": 1,
        "url": "/v1/charges?payment_intent=pi_1H0yxjBnqQbRlIvQWlHcTMCr"
      },
      "client_secret": "pi_1H0yxjBnqQbRlIvQWlHcTMCr_secret_mXGt3NGtYkEjDEqGrOVE2QUQk",
      "confirmation_method": "automatic",
      "created": 1593821435,
      "currency": "usd",
      "customer": null,
      "description": "(created by Stripe CLI)",
      "invoice": null,
      "last_payment_error": null,
      "livemode": false,
      "metadata": {
      },
      "next_action": null,
      "on_behalf_of": null,
      "payment_method": "pm_1H0yxjBnqQbRlIvQckXyRocI",
      "payment_method_options": {
        "card": {
          "installments": null,
          "network": null,
          "request_three_d_secure": "automatic"
        }
      },
      "payment_method_types": [
        "card"
      ],
      "receipt_email": null,
      "review": null,
      "setup_future_usage": null,
      "shipping": {
        "address": {
          "city": "San Francisco",
          "country": "US",
          "line1": "510 Townsend St",
          "line2": null,
          "postal_code": "94103",
          "state": "CA"
        },
        "carrier": null,
        "name": "Jenny Rosen",
        "phone": null,
        "tracking_number": null
      },
      "source": null,
      "statement_descriptor": null,
      "statement_descriptor_suffix": null,
      "status": "succeeded",
      "transfer_data": null,
      "transfer_group": null
    }
  },
  "livemode": false,
  "pending_webhooks": 1,
  "request": {
    "id": "req_JaobDjxa7aP0tW",
    "idempotency_key": null
  },
  "type": "payment_intent.succeeded"
}

e2e test

npm start

Visit localhost:8888/. Submit the test CC number.

  • webhook works -- check in stripe/netlify dashboard
  • success page shows -- need to redirect/re-render in client side
  • product stock goes -1 -- do this in create-order function

Decrement operation

https://stackoverflow.com/questions/56894199/how-to-increment-value-in-faunadb-using-javascript-and-serverside-functions


From Static Sites To End User JAMstack Apps With FaunaDB

E-commerce with fauna tutorial


synchronous, server side payments

checkout version -- Where you forward the customer to the stripe page. Note the webhook happens before the response in the picture diagram. This is not ideal because the stock decrment/purchase operation are not atomic. You have to buy the thing, then stock is decremented later.

payment intent -- idenpotency key, etc

Idempotency keys are sent in the Idempotency-Key header, and you should use them for all POST requests to Stripe’s API. idempotency key

the plan Do the 'synchronous' type of payment process, which happens server-side, and create an order record as well. See accept-a-payment-synchronously

Use stripe.createPaymentMethod on the client

Send the payment method ID to the server where it pays for things

fetch('/pay', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        payment_method_id: result.paymentMethod.id,
      })
    }).then(function(result) {
      // Handle server response (see Step 4)
      result.json().then(function(json) {
        handleServerResponse(json);
      })
    });

On the server, create and confirm the paymentIntent

app.post('/pay', async (request, response) => {
    let intent;
    if (request.body.payment_method_id) {
      // Create the PaymentIntent
      intent = await stripe.paymentIntents.create({
        payment_method: request.body.payment_method_id,
        amount: 1099,
        currency: 'usd',
        confirmation_method: 'manual',
        confirm: true
      });

First check/decrement the stock, then if it's ok, create an order record. Then call stripe.pay and set confirm: true on the paymentIntent call

Then when you get the webhook sucess for the payment, mark the order as paid, and start shipping things

Stripe sends a payment_intent.succeeded event when the payment completes. Use the Dashboard, a custom webhook, or a partner solution to receive these events and run actions, like sending an order confirmation email to your customer, logging the sale in a database, or starting a shipping workflow.


server side confirm payment


If you call this with a non-existant slug, it will return an error and say "Calling the function resulted in an error."

Exanmple that works:

Call(
  Function("submit_order"),
    [
      [Object({
        "slug": "aaaaaaaaaaa",
        "quantity": 1
      })]
    ]
)

res:

{
  ref: Ref(Collection("orders"), "271505982105846280"),
  ts: 1595187131910000,
  data: {
    line: [
      {
        product: Ref(Collection("products"), "269177027692593672"),
        quantity: 1,
        price: 10
      }
    ],
    status: "new",
    creationDate: Time("2020-07-19T19:32:11.640455Z"),
    shipAddress: "123 street",
    creditCard: "4425"
  }
}

form validation validation tips


metadata


testing the 'buy' page

need to use the buy-things page return something from the buy page js


How to get the iframe to work Testing Stripe Elements with Cypress

whammy's People

Contributors

nichoth avatar dependabot[bot] avatar

Stargazers

Andrejs Agejevs avatar

Watchers

James Cloos avatar  avatar

whammy's Issues

Shopping cart

There should be a shopping cart widget

  • can purchase several things at once
  • UI is a separate package
  • can add things
  • remove things
  • show the cart icon all the time
  • there is a separate cart page with more detail

e2e order test

  • check that shipping address is recorded in the DB
  • should calculate the total price server-side
  • should not show the product you ordered in the store anymore

'add to cart' button

  • can add things to the cart
  • check if something is in the cart already, and if so, change the button that is 'add to cart'

Shipping calculation

$3 flat rate for one item, $4 for two, and 3-8 items is $5, and 8+ would be $6

start a CMS project

a separate repo that would replace the /admin route

  • need to save and read from faunaDB

CMS genre input

Should be a text input, but show terms you have previously entered, and you can cycle through the terms

Show payment errors to user

After you submit the stripe info

Could try cypress

  • should have a generic, re-usable error screen component

Create an order

The create-order page could be a new page or a different rendering of the cart page.

  • get the paymentIntent when someone click the "buy the things" button
  • this then shows a shipping address input and the stripe card input
  • clicking 'confirm' then does the stripe .confirmCardPayment thing
    • ideally this would happen server-side so you could decrement stock at the same time

Add to cart nav

itd be cool if when you added something to the cart it took you directly to the cart, and then on the cart page there was a “continue shopping” button that brought you back to the store

cart page

  • show details of each product
  • can remove things
  • the icon links to the page
  • can create an order (buy the things)
  • quantity input with a limit equal to the total stock available. Only show if there are more than 1 available
  • show prices and total price

desktop/mobile layout

See chat

  • should have single column on mobile
  • more horizontal on desktop

I like how it goes horizontal and layed out on desktop

signal-2020-07-21-091255_001

signal-2020-07-21-091255_002

setup dev vs production

  • show a preview site of the dev branch at a separate URL
  • separate dev & production branches

should show a loading screen during payment

  • show a waiting screen while waiting for the payment response
  • some kind of spinning icon
  • should put an element over everything with a semi-opaque background. that way you can't click things

record the shipping address in backend

  • get the shipping address when the order form is submitted
  • should be in the DB with the order record
  • test this by sending a payment request

Need to make a form on the order page

Different pages for genres

  • each genre should have it's own view -- need to fetch items by genre depending on the URL
  • should be links in the site nav for genres

automated tests

See tape-run and cypress for testing the browser side.

for webhooks

netlify/cli#566

  1. serve the functions locally
  2. Setup the stripe webhook destination
stripe listen --forward-to localhost:54901/stripe-webhook
  1. Send the webhook request (it has the right body & headers automatically)
$ stripe trigger payment_intent.succeeded

dreaming code:

stripe.trigger('payment_intent.succeeded', { body, headers }, { port })

Checkout page

  • has a link from the cart page
  • gets a payment intent
  • this is where you can actually pay for things, enter shipping and payment info

quantity view

  • should show the available quantity in the single-product page
  • should disable the 'add to cart' button if stock is 0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.