https://stripe.com/docs/terminal/example-applications를 시작으로 왼쪽 사이드바에 있는 내용을 순서대로 읽어 나가면 된다. 

구성은 

터미널리더, stripe SDK, 어플리케이션(android pos), 서버(nodejs), stipe 회사

이면 중심에는 stripe SDK가 있고 리더, 어플리케이션, 서버와의 통신을 관장한다.

.

.

참고사항) 

터미널 연동시 확인사항 목록 https://stripe.com/docs/terminal/checklist

original source : https://ui.dev/var-let-const/

var은 함수 전체에서 참조 가능. 

function discountPrices (prices, discount) {
  var discounted = []

  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

for loop안에서 정의된 변수값을 밖에서 참조 가능. 함수 scope이기 때문이다.

.

function discountPrices (prices, discount) {
  console.log(discounted) // undefined
  var discounted = []

  for (var i = 0; i < prices.length; i++) {
    var discountedPrice = prices[i] * (1 - discount)
    var finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

var의 경우 정의하기 전에 참조 시도하면 undefined값을 기본을 가짐. 이는 interpreter가 뒤에서 하는 작업으로 다른 언어를 생각하면 이해하기 힘들지만 그렇다. 이렇게 미리 기본값을 넣어 놓는 것을 hoisting이라고 한다. 

.

.

.

let 블락 scope

function discountPrices (prices, discount) {
  let discounted = []

  for (let i = 0; i < prices.length; i++) {
    let discountedPrice = prices[i] * (1 - discount)
    let finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i)  console.log(discountedPrice)  console.log(finalPrice)
  return discounted
}

for loop안의 변수 밖에서 참조 못함.

.

function discountPrices (prices, discount) {
  console.log(discounted) // ❌ ReferenceError
  let discounted = []

  for (let i = 0; i < prices.length; i++) {
    let discountedPrice = prices[i] * (1 - discount)
    let finalPrice = Math.round(discountedPrice * 100) / 100
    discounted.push(finalPrice)
  }

  console.log(i) // 3
  console.log(discountedPrice) // 150
  console.log(finalPrice) // 150

  return discounted
}

let 은 hoisting작업을 안하게 한다.

.

.

const는 let가 비슷하나 reasign이 되지 않는다.

let name = 'Tyler'
const handle = 'tylermcginnis'

name = 'Tyler McGinnis' // ✅
handle = '@tylermcginnis' // ❌ TypeError: Assignment to constant variable.

Promise
https://youtu.be/_JOP8rcDjJE

참고사항)

https://stackoverflow.com/a/46719997

Promise로 되돌아 오는 결과는 await을 통하거나 .then( data => {}) 를 통해서 얻어낼수 있다

.

async, await
https://youtu.be/RXN7169vBGw

node js crypto documentations

https://nodejs.org/api/crypto.html

.

.

.

.

crypto 라이브러리를 이용한 rsa구현

https://www.sohamkamani.com/nodejs/rsa-encryption/

Key Generation

const crypto = require("crypto")

// The `generateKeyPairSync` method accepts two arguments:
// 1. The type ok keys we want, which in this case is "rsa"
// 2. An object with the properties of the key
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
	// The standard secure default length for RSA keys is 2048 bits
	modulusLength: 2048,
})

// use the public and private keys
// ...

Encryption

// This is the data we want to encrypt
const data = "my secret data"

const encryptedData = crypto.publicEncrypt(
	{
		key: publicKey,
		padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
		oaepHash: "sha256",
	},
	// We convert the data string to a buffer using `Buffer.from`
	Buffer.from(data)
)

// The encrypted data is in the form of bytes, so we print it in base64 format
// so that it's displayed in a more readable form
console.log("encypted data: ", encryptedData.toString("base64"))

Decryption

const decryptedData = crypto.privateDecrypt(
	{
		key: privateKey,
		// In order to decrypt the data, we need to specify the
		// same hashing function and padding scheme that we used to
		// encrypt the data in the previous step
		padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
		oaepHash: "sha256",
	},
	encryptedData
)

// The decrypted data is of the Buffer type, which we can convert to a
// string to reveal the original data
console.log("decrypted data: ", decryptedData.toString())

.

.

.

.

https://www.freecodecamp.org/news/md5-vs-sha-1-vs-sha-2-which-is-the-most-secure-encryption-hash-and-how-to-check-them/

SHA-3 (2020 12 sha-3, sha256이 가장 믿을만한 알고리즘)

This hash method was developed in late 2015, and has not seen widespread use yet. Its algorithm is unrelated to the one used by its predecessor, SHA-2.

The SHA3-256 algorithm is a variant with equivalent applicability to that of the earlier SHA-256, with the former taking slightly longer to calculate than the later.

.

.

.

.

https://stackabuse.com/encoding-and-decoding-base64-strings-in-node-js/

Binary Data to Base64 Strings


'use strict'; const fs = require('fs'); let buff = fs.readFileSync('stack-abuse-logo.png'); let base64data = buff.toString('base64'); console.log('Image converted to base 64 is:nn' + base64data);

Base64 Strings to Binary Data

The reverse process here is very similar to how we decode Base64 strings, as we saw in an earlier section. The biggest difference is the output destination and how data is written there. Let’s see the example:

'use strict';

const fs = require('fs');

let data = 'iVBORw0KGgoAAAANSUhEUgAAABkAAAATCAYAAABlcqYFAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAA' + 
'CA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAACXBIWXMAAAsTAAALEwEAmpwYAAABWWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0' +
'YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly' +
'93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAg' +
'ICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyI+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZm' +
'Y6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpMwidZAAADuUlEQVQ4EbVU' +
'TUtcZxR+7ufkXp1SZ4iZRE1EDVQRnTAhowsZMFm40I2rNqUIIev8hvoPQroQXBTqwiAWcd0EglEhiZNajVZrQGXAWAzaZpzMnZn7lXPeeIe5Da' +
'Wb9Ax33vOec8/znI/3vVI6nfbxP4v8b/iSJIGfzyGfkPi+D13XUalUBL6qqmIvy5+8WuX/r2RCkUzAoIuLi2hqaoLrutjb28P6+josyxJkiqJA' +
'07SQXiqVwHaOZYx/itLc3Px9YIxEIlheXsbExATGxsYwMjIiwEdHRwXA/Pw8EokEcrkcDg4OYJomVlZWMDU1JSqfmZlBR0cHbNsOtVoNCHjlTF' +
'iSySQMwxAVxONxQbi0tIRMJoPe3l5MT0+jtbUVg4ODYGImY18qlcL4+DhisZjoggCjv1C7uOyenh7Mzs5iY2ND6FQpdnd3sba2JloSjUYxPDyM' +
'/v5+TE5OYn9/X9jZtrOzg+3t7WqyAUmoEu419/+HBw9E+eVymbJqAJP39fWBCR3HEU+hUMDQ0JCYGc8um81iYGAAjY2N8DwvwBdraCY8tHhDA1' +
'Y3N9Hd3S2yvH37O7RcbsF7AuUsD9+8wdOFBTx/8QJtbW1C5/nMzc3R0D2UyxXk83lRXcAk1V5GCT5sSUGDbeHxy9/EO98M9OOXzT9wfHISxKC1' +
'vR0GHfOtrS2g/SouWwU0Xkggu7qO9PUkJFULnbIQyTm6ewu2hF+vnOIIUQwdGlg8f4QF6wvMWBq+pAkaskSnx4FFVUf0CNpcC797KizXQ4oAHh' +
'VdXJJ81F7j6kwUynPHlXDPdFB2fRj+KVK0KvT2rbp3uKYryJU11Cke8qqMuOoioeeJ1MPDYxM36m1cNSq4GdFx58RAWvbx8TrXnK4IgR16Em5G' +
'K4iqHi5GHHxLgcSDn97WgZPoND+GGZRpPYH85cgiiRQl1ltXxmFFQ5PuopP8TrW5ZyRcWp7AbmkeZefg5+N6PPnbRJdpw/YlfB0vQiPQZwVdZN' +
'tFZEVK6D1VTnccJlXzuqTjvOZiq6Rhj2KqLSJsofOHgIl8+t0/qsfDioxmSUWGjrRFzhYi/5Oynrdl3KXHIZDXtF6hil8R6I9FBV/RvDLnXKxS' +
'bAdVYhNeINXBMwmXWCTQGG2Y+Jj+dFrfEmiMAtmeowpo9ojTvkD+A/L1UJUMmiVfkuz6WTyZhFRJAgP33j3bsM5k/Fng68UP21hYJyyxZwLWuS' +
'2cKMfUSm3rhD0g4E2g197fwMZ+Bgt8rNe2iP2BhL5dgfFzrx8AfECEDdx45a0AAAAASUVORK5CYII=';

let buff = new Buffer(data, 'base64');
fs.writeFileSync('stack-abuse-logo-out.png', buff);

console.log('Base64 image data converted to file: stack-abuse-logo-out.png');

.

.

.

.

참고사항) 

https://www.geeksforgeeks.org/node-js-crypto-randombytes-method/

install firebase-admin SDK 
original source : https://www.skysilk.com/blog/2019/install-firebase-admin-sdk/

admin sdk를 이용해서 할수 있는 일들

https://firebase.google.com/docs/admin/setup

  • Read and write Realtime Database data with full admin privileges.
  • Programmatically send Firebase Cloud Messaging messages using a simple, alternative approach to the Firebase Cloud Messaging server protocols.
  • Generate and verify Firebase auth tokens.
  • Access Google Cloud Platform resources like Cloud Storage buckets and Cloud Firestore databases associated with your Firebase projects.
  • Create your own simplified admin console to do things like look up user data or change a user’s email address for authentication.

.

.

firebase dashboard에 가서 private key를 만들고 다운 받은 다음 다른 사람이 접근할수 없는 폴더에 보관한다. 경로를 아래에서 입력 연결하는 코드 작업할것이다.

firebase-admin 라이브러리 설치는

npm install firebase-admin –save

.

firebase-admin 을 import하는 과정은

var admin = require(“firebase-admin”);

.

위에서 받은 private key 연결

var serviceAccount = require(“./firebaselogin.json”);

.

init 하는 과정

admin.initializeApp({

 credential: admin.credential.cert(serviceAccount),

 databaseURL: “https://yourproject.firebaseio.com”

});

webhook는 stripe에서 발생한 이벤트를 내 개발자 서버로 통신이 오게 하는 것이며

관련 이벤트 데이터를 담겨진 것을 webhook event 라고 한다. 

image

stripe 계정에서 위와 같이 확인 가능하다. 

.

.

.

개발시 webhook이 잘 작동하는지 알수 있는 방법 설명

https://stripe.com/docs/webhooks/test

There are several ways to test your webhook endpoint:

  • Create test activity on your account
  • Manually send test events from the Dashboard
  • Use the Stripe CLI

이렇게 있는데 cli 를 이용하는 것이 가장 간단하다. 실제 stripe에 설정한 webhook과는 무관하게 모든 webhook을 테스트 할수 있다. 단순히 내 server(webhook event를 받아들일)작업만 해 놓으면 바로 확인 가능하다.

stripe cli 설치

brew install stripe/stripe-cli/stripe

stripe 연결

stripe login
Your pairing code is: humour-nifty-finer-magic
Press Enter to open up the browser (^C to quit)

내서버에서 오는 결과를 보여줄 터미얼에 아래와 같이 입력

stripe listen --forward-to localhost:5000/hooks
Ready! Your webhook signing secret is '{{WEBHOOK_SIGNING_SECRET}}' (^C to quit)

내서버에 webhook event를 전달하는 명령어를 위와는 다른 터미널에 아래와 같이 입력

stripe trigger payment_intent.created

그러면 아래와 같이 결과가 나온다

image

.

.

.

.

내 개발자 서버로 오는 webhook event가 맞는 것인지 확인을 위해서는 

stripe에서 오는 key (Stripe-Signature header 안에 들어 있음) 를 내 서버에 있는 endpointSecret (stripe CLI에서 stripe login 입력하면 나오는 키값, stripe 웹사이트 계정페이지에서 signing secret으로 표기된 키값)을 비교한다. 

https://stripe.com/docs/webhooks/signatures#verify-official-libraries

예시 코드는 아래와 같다.

// 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_51HsFV3EpgfvwVVPojh8CroIb788ovjYa3fB04FcVPs7EpnLwxihHzuO85R9p5b3H6qULpSyZu0432qvnovTPrKpe00NLL0RJne');

// If you are testing your webhook locally with the Stripe CLI you
// can find the endpoint's secret by running `stripe listen`
// Otherwise, find your endpoint's secret in your webhook settings in the Developer Dashboard
const endpointSecret = 'whsec_...';

// This example uses Express to receive webhooks
const app = require('express')();

// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');

// Match the raw body to content type application/json
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
  const sig = request.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
  }
  catch (err) {
    response.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object;
      console.log('PaymentIntent was successful!');
      break;
    case 'payment_method.attached':
      const paymentMethod = event.data.object;
      console.log('PaymentMethod was attached to a Customer!');
      break;
    // ... handle other event types
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  // Return a response to acknowledge receipt of the event
  response.json({received: true});
});

app.listen(4242, () => console.log('Running on port 4242'));

단 switch구문내 const paymentIntent 는 var paymentIntent로 바꾼다.

const paymentMethod도 var paymentMethod로 수정한다. (lint error를 발생시킨다.)

그리고 firebase functions과 같이 사용하는 경우 

event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);

event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret);

로 수정하고 require(’body-parser’)부분도 삭제한다. firebase는 자동으로 json parsing작업을 한다.  https://stackoverflow.com/a/53899407

.

.

.

.

https://stripe.com/docs/api/webhook_endpoints/update

아래와 같은 webhook event type 목록

image

.

.

.

.

webhook을 사용하는 경우 단하나의 webhook event가 오는 것이 아니고 여러 webhook event가 부수적으로 연달아 발생해서 전달 된다. 이를 방지하기 위한 방법

https://stackoverflow.com/a/62849673

When you trigger an event with the Stripe CLI, under the hood it’s making all of the requisite API methods to result in that event firing. In this case, it is creating and confirming a PaymentIntent.

To get a resulting payment_intent.succeeded event, you’ll need to create a PaymentIntent (payment_intent.created will fire for this one). Then you need to confirm the payment intent and actually collect payment (which results in: charge.created, charge.succeeded, and payment_intent.succeeded).

For other event types like checkout.session.completed, many other events will fire that represent the objects that are prerequisite to get to a valid checkout.session.completed event.

If you only want to forward the payment_intent.succeeded event locally, (and it’s a good idea when in production to enable only selected event types for your webhook endpoint), then you might want to pass the -e argument to stripe listen with the comma separated list of specific events you want to listen for. In this case you might update your listen command to:

stripe listen --forward-to http://localhost:3000/webhook -e payment_intent.succeeded

.

.

.

.

참고사항)

javascript에서 object logging하는 방법

https://levelup.gitconnected.com/5-ways-to-log-an-object-to-the-console-in-javascript-7b995c56af5a

.

.

firebase functions은 console.log하면 내용보기가 매우 어려웠다. 그래서

firebase-loggin 모듈을 설치하고 이용하면 좀 편하다.

https://www.npmjs.com/package/firebase-logging

.

.

firebase function은 body에 있는 json string을 자동으로 parsing해서 request.body에 obj로 제공한다. 그러므로 body-parser 미들웨어를 사용할 필요 없다.

https://stackoverflow.com/a/53899407