Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Payment service simplification refactor #169

Merged
merged 20 commits into from
Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/paymentservice/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
.dockerignore
Dockerfile
package.json
package-lock.json
README.md
19 changes: 10 additions & 9 deletions src/paymentservice/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM node:16-alpine AS base

FROM base AS builder
FROM node:16-alpine AS build

WORKDIR /usr/src/app/

COPY ./src/paymentservice/package.json ./
COPY ./src/paymentservice/package*.json ./

RUN npm install --only=production
RUN npm ci --omit=dev

# -----------------------------------------------------------------------------

FROM base
FROM node:16-alpine
cartersocha marked this conversation as resolved.
Show resolved Hide resolved

USER node
WORKDIR /usr/src/app/
ENV NODE_ENV production

COPY --from=builder /usr/src/app/node_modules/ ./node_modules/
COPY --chown=node:node --from=build /usr/src/app/node_modules/ ./node_modules/
COPY ./src/paymentservice/ ./
COPY ./pb/ ./proto/
COPY ./pb/demo.proto ./

EXPOSE ${PAYMENT_SERVICE_PORT}
ENTRYPOINT [ "node", "--require", "./tracing.js", "./index.js" ]

ENTRYPOINT [ "node", "./index.js", "--require", "./tracing.js" ]
109 changes: 35 additions & 74 deletions src/paymentservice/charge.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,85 +12,46 @@
// See the License for the specific language governing permissions and
// limitations under the License.

const cardValidator = require('simple-card-validator');
const { v4: uuidv4 } = require('uuid');
const pino = require('pino');
const opentelemetry = require('@opentelemetry/api');
const tracer = opentelemetry.trace.getTracer("paymentservice");

const logger = pino({
name: 'paymentservice-charge',
messageKey: 'message',
levelKey: 'severity',
useLevelLabels: true
});


class CreditCardError extends Error {
constructor (message) {
super(message);
this.code = 400; // Invalid argument error
}
}

class InvalidCreditCard extends CreditCardError {
constructor (cardType) {
super(`Credit card info is invalid`);
}
}

class UnacceptedCreditCard extends CreditCardError {
constructor (cardType) {
super(`Sorry, we cannot process ${cardType} credit cards. Only VISA or MasterCard is accepted.`);
}
}

class ExpiredCreditCard extends CreditCardError {
constructor (number, month, year) {
super(`Your credit card (ending ${number.substr(-4)}) expired on ${month}/${year}`);
}
}

/**
* Verifies the credit card number and (pretend) charges the card.
*
* @param {*} request
* @return transaction_id - a random uuid v4.
*/
module.exports = function charge (request) {
// create and start span
const span = tracer.startSpan("charge")

const { amount, credit_card: creditCard } = request;
const cardNumber = creditCard.credit_card_number;
const cardInfo = cardValidator(cardNumber);
const {
card_type: cardType,
valid
} = cardInfo.getCardDetails();
// Npm
const opentelemetry = require('@opentelemetry/api')
const cardValidator = require('simple-card-validator')
const pino = require('pino')
const { v4: uuidv4 } = require('uuid')

// Setup
const logger = pino()
const tracer = opentelemetry.trace.getTracer('paymentservice')

// Functions
module.exports.charge = request => {
const span = tracer.startSpan('charge')

const { amount, credit_card: creditCard } = request
const cardNumber = creditCard.credit_card_number
const card = cardValidator(cardNumber)
const {card_type: cardType, valid } = card.getCardDetails()
span.setAttributes({
"app.payment.charge.cardType": cardType,
"app.payment.charge.valid": valid
'app.payment.charge.cardType': cardType,
'app.payment.charge.valid': valid
})

if (!valid) { throw new InvalidCreditCard(); }
if (!valid)
throw new Error('Credit card info is invalid.')

// Only VISA and mastercard is accepted, other card types (AMEX, dinersclub) will
// throw UnacceptedCreditCard error.
if (!(cardType === 'visa' || cardType === 'mastercard')) { throw new UnacceptedCreditCard(cardType); }
if (!['visa', 'mastercard'].includes(cardType))
throw new Error(`Sorry, we cannot process ${cardType} credit cards. Only VISA or MasterCard is accepted.`)

// Also validate expiration is > today.
const currentMonth = new Date().getMonth() + 1;
const currentYear = new Date().getFullYear();
const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard;
if ((currentYear * 12 + currentMonth) > (year * 12 + month)) { throw new ExpiredCreditCard(cardNumber.replace('-', ''), month, year); }
const currentMonth = new Date().getMonth() + 1
const currentYear = new Date().getFullYear()
const { credit_card_expiration_year: year, credit_card_expiration_month: month } = creditCard
const lastFourDigits = cardNumber.substr(-4)
if ((currentYear * 12 + currentMonth) > (year * 12 + month))
throw new Error(`The credit card (ending ${lastFourDigits}) expired on ${month}/${year}.`)

logger.info(`Transaction processed: ${cardType} ending ${cardNumber.substr(-4)} \
Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`);
span.setAttribute('app.payment.charged', true)
span.end()

span.setAttribute("app.payment.charged", true);
// a manually created span needs to be ended
span.end();
logger.info(`Transaction processed: ${cardType} ending ${lastFourDigits} | Amount: ${amount.currency_code}${amount.units}.${amount.nanos}`)

return { transaction_id: uuidv4() };
};
return { transaction_id: uuidv4() }
}
102 changes: 75 additions & 27 deletions src/paymentservice/index.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,75 @@
/*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const path = require('path');
const HipsterShopServer = require('./server');

const PORT = process.env['PAYMENT_SERVICE_PORT'];
const PROTO_PATH = path.join(__dirname, '/proto/');

const server = new HipsterShopServer(PROTO_PATH, PORT);

server.listen();
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Npm
const grpc = require('@grpc/grpc-js')
const protoLoader = require('@grpc/proto-loader')
const health = require('grpc-health-check')
const opentelemetry = require('@opentelemetry/api')
const pino = require('pino')

// Local
const charge = require('./charge')

// Functions
function chargeServiceHandler(call, callback) {
const span = opentelemetry.trace.getSpan(opentelemetry.context.active())

try {
const amount = call.request.amount
span.setAttributes({
'app.payment.currency': amount.currency_code,
'app.payment.cost': parseFloat(`${amount.units}.${amount.nanos}`)
})
logger.info(`PaymentService#Charge invoked by: ${JSON.stringify(call.request)}`)

const response = charge(call.request)
callback(null, response)

} catch (err) {
logger.warn(err)

span.recordException(err)
span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR })

callback(err)
}
}

// Functions
async function closeGracefully(signal) {
server.forceShutdown()
process.kill(process.pid, signal)
}

// Main
const logger = pino()
const port = process.env['PAYMENT_SERVICE_PORT']
const hipsterShopPackage = grpc.loadPackageDefinition(protoLoader.loadSync('demo.proto'))
const server = new grpc.Server()

server.addService(health.service, new health.Implementation({
'': proto.grpc.health.v1.HealthCheckResponse.ServingStatus.SERVING
}))

server.addService(hipsterShopPackage.hipstershop.PaymentService.service, { charge: chargeServiceHandler })

server.bindAsync(`0.0.0.0:${port}`, grpc.ServerCredentials.createInsecure(), () => {
logger.info(`PaymentService gRPC server started on port ${port}`)
server.start()
}
)

process.once('SIGINT', closeGracefully)
process.once('SIGTERM', closeGracefully)
21 changes: 9 additions & 12 deletions src/paymentservice/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "paymentservice",
"version": "0.0.1",
"description": "Payment Microservice demo",
"description": "Payment Microservice",
"repository": "https://github.com/opentelemetry/opentelemetry-demo-webstore",
"main": "index.js",
"scripts": {
Expand All @@ -11,16 +11,13 @@
"author": "Jonathan Lui",
"license": "ISC",
"dependencies": {
"@grpc/grpc-js": "^1.5.7",
"@grpc/proto-loader": "^0.6.9",
"@opentelemetry/auto-instrumentations-node": "^0.27.3",
"@opentelemetry/exporter-trace-otlp-grpc": "^0.27.0",
"@opentelemetry/sdk-node": "^0.27.0",
"pino": "^7.8.0",
"simple-card-validator": "^1.1.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"semistandard": "^16.0.1"
"@grpc/grpc-js": "1.6.7",
"@opentelemetry/auto-instrumentations-node": "0.31.0",
"@opentelemetry/exporter-trace-otlp-grpc": "0.29.2",
"@opentelemetry/sdk-node": "0.29.2",
"grpc-health-check": "1.8.0",
"pino": "8.1.0",
"simple-card-validator": "1.1.0",
"uuid": "8.3.2"
}
}
Loading