Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

43 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stack Backend 3 - The Billing Service

  1. POST: Cart Insert
  2. POST: Cart Update
  3. DELETE: Cart Delete
  4. GET: Cart Retrieve
  5. POST: Cart Clear
  6. GET: Order Payment
  7. POST: Order Complete
  8. GET: Order List
  9. GET: Order Detail

Stripe

Before we can start on this backend we will need a developer account from Stripe, and get our api keys:

  1. Go to Stripe Developer Docs and create an account.
  2. Click on the Dashboard -> button in the top right hand corner to access our Dashboard.
  3. Click on the Developers button in the top right hand corner to access out developer info.
  4. Click on the API keys tab on the left menu.
  5. Copy the Secret key (you will need to press Revel test key to see it) and paste it as your STRIPE_API_KEY enviroment variable in your configuration settings. (Just like how we did with our database credentials)

We will need the public key later for our front end so refrence this guide when you want to return to that webpage. We just want to use the test data and test keys. You may close your account after the course if you would like.

Application

Our application depends on a lot of files and resources to be able to run correctly. These files have been provided for you and are listed here for your reference. These files should NEVER be modified and must be left AS IS.

pom.xml

Maven gets all its settings from a file called pom.xml. This file determines the dependencies we will use in our project as well as the plugins we use for compiling, testing, building, ect..

application.yml

Spring Boot has a large number of settings that can be set with a file called application.yml. We have already created this file for you and have filled it with some settings. There is a file for the main application as well as one for the tests.

Resources

There are two folders in this project that contain resources, and application settings, as well as files required for the tests.

Tests

There is a Single class that contain all of our test cases:

Database

Schemas

🗄 idm 🗄 movies 🗄 billing

Tables

💾 billing.cart
Column Name Type Attributes
user_id INT NOT NULL
movie_id INT NOT NULL
quantity INT NOT NULL
Constraints
PRIMARY KEY (user_id, movie_id)
FOREIGN KEY (user_id) REFERENCES idm.user (id) ON UPDATE CASCADE ON DELETE CASCADE
FOREIGN KEY (movie_id) REFERENCES movies.movie (id) ON UPDATE CASCADE ON DELETE CASCADE
💾 billing.sale
Column Name Type Attributes
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT
user_id INT NOT NULL
total DECMIAL(19,4) NOT NULL
order_date TIMESTAMP NOT NULL
Constraints
FOREIGN KEY (user_id) REFERENCES idm.user (id) ON UPDATE CASCADE ON DELETE CASCADE
💾 billing.sale_item
Column Name Type Attributes
sale_id INT NOT NULL
movie_id INT NOT NULL
quantity INT NOT NULL
Constraints
PRIMARY KEY (sale_id, movie_id)
FOREIGN KEY (sale_id) REFERENCES billing.sale (id) ON UPDATE CASCADE ON DELETE CASCADE
FOREIGN KEY (movie_id) REFERENCES movies.movie (id) ON UPDATE CASCADE ON DELETE CASCADE
💾 billing.movie_price
Column Name Type Attributes
movie_id INT NOT NULL PRIMARY KEY
unit_price DECIMAL(19,4) NOT NULL
premium_discount INT NOT NULL
Constraints
CHECK (premium_discount BETWEEN 0 AND 25)
FOREIGN KEY (movie_id) REFERENCES movies.movie (id) ON UPDATE CASCADE ON DELETE CASCADE

Initial Data

All the data to initialize your database is found in the db folder here: db folder.

Notes

Order of Validation

All ❗ 400: Bad Request Results must be checked first, and returned before any other action is made.
The order of the checks within ❗ 400: Bad Request is not tested as each Result is tested individually.

JsonInclude

In the case of non-successful results, where values are expected, the values should not be included, for example.

{
   "result": {
      "code": 32,
      "message": "Data contains invalid integers"
   },
   "value": null 
}

the value key should not be included:

{
   "result": {
      "code": 32,
      "message": "Data contains invalid integers"
   }
}

This is done by insuring that all null values are dropped by either:

  • Having your Model extend ResponseModel<Model>, or
  • Putting the @JsonInclude(JsonInclude.Include.NON_NULL) on your Model class

Result

All Result objects are available as static constants inside of the com.gitcodings.stack.core.result.BillingResults class. These can be used rather than creating your own.

SignedJWT

All endpoints in this service are considered 'privilged' as in, the user calling the endpoint must be authorized and as such must included their serialized SignedJWT inlcuded in the header of the request under the Authorization header. In the test cases you'll see that we are including these headers with JWT's for your convenience when testing.

In Spring there is a way to automatically take this header and turn it into a SignedJWT (This is already done for you by a provided filter here: JWTAuthenticationFilter). There is also a way to "ask" spring for this SignedJWT by using the @AuthenticationPrincipal SignedJWT user function parameter in the endpoint like so:

@GetMapping("/path")
public ResponseEntity<ResponseModel> endpoint(@AuthenticationPrincipal SignedJWT user)
{
    ...
}

We can then get information out of the SignedJWT by using user.getJWTClaimsSet() and then getting the corrosponging claim by using getClaim(). Note that we get the claim by the key we used when we set the claim in the idm.

We set our userId using .claim(JWTManager.CLAIM_ID, userId) we can get that id by using getCustomClaim(JWTManager.CLAIM_ID). However this returns a type of Object if we know what the type of the specific claim is we can use the helper functions. We know we set JWTManager.CLAIM_ID as a Long so we can use getLongClaim(JWTManager.CLAIM_ID) to get it as a type of Long.

A convient function to use to get our roles our of our SignedJWT is .getStringListClaim(JWTManager.CLAIM_ROLES) which returns our user's roles as a list of Strings

BigDecimal

Very Important For all values that deal with money, Including those in our ResponseModel We want to make sure we are returning a type of BigDecimal with a scale set to 2. The tests will fail if we do not have it set to this scale as json considers: 14.5500 different than 14.55.

Refer to Activity 5 for how to deal with BigDecimal on how to deal with BigDecimal's especially the last part with dealing with scale and rounding.

Applying Discount For Premium Users

User's that have the Premium role have a diffrent UnitPrice that must be calculated by using the discount column in the table. Calculating the DiscountedUnitPrice requires we follow a very specific algorithm that is described below:

Formula for applying the discount

  1. DiscountedUnitPrice = ( UnitPrice * (1 - (Discount / 100.0)))
    • Notice we use 100.0 (A double with the .0 at the end) this is VERY important, We want discount to become a double! We will get the wrong answer if we do not do this.
  2. DiscountedUnitPrice scale set to 2 with RounderingMode.DOWN
  3. Total for each movie = NewUnitPrice * Quantity
  4. Total for order is the sum of all the totals;

Endpoints

Cart Insert

Insert a given movieId into a user's cart with the given quantity

Path

POST /cart/insert

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
📥 Request
Model Example
movieId: Long
quantity: Integer
{
     "movieId": 4154796,
     "quantity": 2
}
Key Required Description
movieIdYesMovie Id of the movie to insert
quantityYesQuantity of the movie to add, must be between [1-10] (inclusive)
📤 Response
Model Example
result: Result
    code: Integer
    message: String
{
    "result": {
        "code": 3010,
        "message": "Item inserted into cart"
    }
}
📦 Results
Status Code Message
✅ 200: Ok 3010 Item inserted into cart
❗ 400: Bad Request 3000 Quantity cannot be zero or less
❗ 400: Bad Request 3001 Quantity exceeded max limit
❗ 409: Conflict 3002 Item already in cart

Cart Update

Updates the given movieId quantity in the user's cart.

Path

POST /cart/update

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
📥 Request
Model Example
movieId: Long
quantity: Integer
{
     "movieId": 4154796,
     "quantity": 2
}
Key Required Description
movieIdYesMovie Id of the movie to update
quantityYesQuantity of the movie to update, must be between [1-10] (inclusive)
📤 Response
Model Example
result: Result
    code: Integer
    message: String
{
    "result": {
        "code": 3020,
        "message": "Item in cart updated"
    }
}
📦 Results
Status Code Message
✅ 200: Ok 3020 Item in cart updated
❗ 400: Bad Request 3000 Quantity cannot be zero or less
❗ 400: Bad Request 3001 Quantity exceeded max limit
❗ 409: Conflict 3003 Item not in cart

Cart Delete

Deletes the given movieId from the user's cart

Path

DELETE /cart/delete/{movieId}

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
🔖  Path
Parameter Type Description
movieId Long Movie id of the movie to delete from cart
📤 Response
Model Example
result: Result
    code: Integer
    message: String
{
    "result": {
        "code": 3030,
        "message": "Item deleted from cart"
    }
}
📦 Results
Status Code Message
✅ 200: Ok 3030 Item deleted from cart
❗ 409: Conflict 3003 Item not in cart

Cart Retrieve

Retrieve's all items from the user's cart with some movie details. If the user has the Premium Role then we should report back with the "discounted rate". (Refer to Formula for applying the discount on how to do this for Premium users)

Path

GET /cart/retrieve

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
📤 Response
Model Example
result: Result
    code: Integer
    message: String
total: BigDecimal (Set to a scale of 2)
items: Item[]
    unitPrice: BigDecimal (Set to a scale of 2)
    quantity: Integer
    movieId: Long
    movieTitle: String
    backdropPath: String
    posterPath: String
{
    "result": {
        "code": 3040,
        "message": "Cart items retrieved"
    },
    "total": 10.25,
    "items": [
        {
            "unitPrice": 10.25,
            "quantity": 2,
            "movieId": 4154796,
            "movieTitle": "Avengers: Endgame",
            "backdropPath": "/7RyHsO4yDXtBv1zUU3mTpHeQ0d5.jpg",
            "posterPath": "/or06FN3Dka5tukK1e9sl16pB3iy.jpg",
        }
    ]
}
📦 Results
Status Code Message
✅ 200: Ok 3040 Cart items retrieved
✅ 200: Ok 3004 Cart is empty

Cart Clear

Clears all movies from the user's cart

Path

POST /cart/clear

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
📤 Response
Model Example
result: Result
    code: Integer
    message: String
{
    "result": {
        "code": 3050,
        "message": "Items have been cleared from cart"
    }
}
📦 Results
Status Code Message
✅ 200: Ok 3050 Items have been cleared from cart
✅ 200: Ok 3004 Cart is empty

Order Payment

Creates a PaymentIntent with Stripe returning the newly created PaymentIntent's id and clientSecret

PaymentIntent

The PaymentIntent should be created with these three properties

  1. Amount: The total amount of the carts contents. (Refer to Formula for applying the discount on how to do this for Premium users)
  2. Description: The description of the movie's titles in list format (<title>, <title>, ... , <title>).
  3. Metadata: The key-value pair of "userId": <userId stored in the users JWT>

Refer to Activity 5 for how to deal with BigDecimal on how to deal with BigDecimal's especially the last part with dealing with scale and rounding.

Path

GET /order/payment

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
📤 Response
Model Example
result: Result
    code: Integer
    message: String
paymentIntentId: String
clientSecret: String
{
    "result": {
        "code": 3060,
        "message": "Order Payment Intent Created"
    },
    "paymentIntentId": "pi_..."
    "clientSecret": "pi_..."
}
📦 Results
Status Code Message
✅ 200: Ok 3060 Order Payment Intent Created
✅ 200: Ok 3004 Cart is empty
❗ 500: Internal Server Error 3005 Stripe encountered and error

Order Complete

Once the order payment is accepted on the frontend we must retrieve the PaymentIntent from Stripe by calling PaymentIntent.retrieve("paymentIntentId") and verifying it.

Verification

PaymentIntent is verified by checking these two things:

  1. Payment Status: We verify the status by ensuring that paymentIntent.getStatus() is "succeeded".
  2. Correct User: We verify that the user's userId calling this endpoint has the same id as the one stored in this PaymentIntent's meta data by calling paymentIntent.getMetadata().get("userId")

Once we verify that the payment has succeeded then we create a new billing.sale record and then populate the billing.sale_item with the contents of the user's billing.cart. Once that is done the current users cart must be cleared.

Path

POST /order/complete

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
📥 Request
Model Example
paymentIntentId: String
{
    "paymentIntentId": "pi_..."
}
Key Required Description
paymentIntentIdYespaymentIntentId that was returned in /order/payment
📤 Response
Model Example
result: Result
    code: Integer
    message: String
{
    "result": {
        "code": 3070,
        "message": "Order Completed"
    }
}
📦 Results
Status Code Message
✅ 200: Ok 3070 Order Completed
❗ 400: Bad Request 3071 The order has not yet been paid
❗ 400: Bad Request 3072 The order is not meant for this user

Order List

Return a list of sales found for the given user. To keep this endpoint simple return only the last five sales ordered by the most recent sales first.

Path

GET /order/list

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
📤 Response
Model Example
result: Result
    code: Integer
    message: String
sales: Sale[]
    saleId: Long
    total: BigDecimal (Set to a scale of 2)
    orderDate: Instant
{
    "result": {
        "code": 3080,
        "message": "Sales found for user"
    },
    "sales": [
        {
          "saleId": 2,
          "total": 299.30,
          "orderDate": "2022-01-02T20:00:00Z"
        },
        {
          "saleId": 1,
          "total": 234.45,
          "orderDate": "2022-01-01T20:00:00Z"
        }
    ]
}
📦 Results
Status Code Message
✅ 200: Ok 3080 Sales found for user
✅ 200: Ok 3081 No sales found for user

Order Detail

Return a detailed view of a given sale by its saleId.

Path

GET /order/detail/{saleId}

API

📨  Headers
Header Type Description
Authorization String User's bearer token
Transaction-ID String Request's transaction id from the gateway service
🔖  Path
Parameter Type Description
saleId Long Sale Id of the sale to retrieve
📤 Response
Model Example
result: Result
    code: Integer
    message: String
total: BigDecimal (Set to a scale of 2)
items: Item[]
    unitPrice: BigDecimal (Set to a scale of 2)
    quantity: Integer
    movieId: Long
    movieTitle: String
    backdropPath: String
    posterPath: String
{
    "result": {
        "code": 3090,
        "message": "Sale details found for saleId"
    },
    "total": 10.25,
    "items": [
        {
            "unitPrice": 10.25,
            "quantity": 2,
            "movieId": 4154796,
            "movieTitle": "Avengers: Endgame",
            "backdropPath": "/7RyHsO4yDXtBv1zUU3mTpHeQ0d5.jpg",
            "posterPath": "/or06FN3Dka5tukK1e9sl16pB3iy.jpg",
        }
    ]
}
📦 Results
Status Code Message
✅ 200: Ok 3090 Sale details found for saleId
✅ 200: Ok 3091 No sale details found for saleId

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages