shipping complete

This commit is contained in:
Steve Waterworth
2018-01-25 17:05:28 +00:00
parent fff1116848
commit 5539d06bb5
11 changed files with 187 additions and 31 deletions

View File

@@ -100,7 +100,7 @@ app.get('/add/:id/:sku/:qty', (req, res) => {
cart.items = list;
cart.total = calcTotal(cart.items);
// work out tax @ 20%
cart.tax = (cart.total - (cart.total / 1.2)).toFixed(2);
cart.tax = (cart.total - (cart.total / 1.2));
// save the new cart
saveCart(req.params.id, cart);
@@ -159,6 +159,45 @@ app.get('/update/:id/:sku/:qty', (req, res) => {
});
});
// add shipping
app.post('/shipping/:id', (req, res) => {
var shipping = req.body;
if(shipping.distance === undefined || shipping.cost === undefined || shipping.location == undefined) {
console.log('bad shipping data', shipping);
res.status(400).send('shipping data missing');
} else {
// get the cart
redisClient.get(req.params.id, (err, data) => {
if(err) {
console.log('ERROR', err);
res.status(500).send(err);
} else {
if(data == null) {
console.log('no cart for', req.params.id);
res.status(404).send('cart not found');
} else {
var cart = JSON.parse(data);
var item = {
qty: 1,
sku: 'SHIP',
name: 'shipping to ' + shipping.location,
price: shipping.cost,
subtotal: shipping.cost
};
cart.items.push(item);
cart.total = calcTotal(cart.items);
// work out tax @ 20%
cart.tax = (cart.total - (cart.total / 1.2));
// save the updated cart
saveCart(req.params.id, cart);
res.json(cart);
}
}
});
}
});
function mergeList(list, product, qty) {
var inlist = false;
// loop through looking for sku

View File

@@ -6,7 +6,8 @@ db.products.insertMany([
{sku: 'PB-1', name: 'Positronic Brain', description: 'Highly advanced sentient processing unit', price: 42000, instock: 0, categories: ['components']},
{sku: 'SVO-980', name: 'Servo 980Nm', description: 'Servo actuator with 980Nm of torque. Needs 24V 10A supply', price: 50, instock: 32, categories: ['components']},
{sku: 'ROB-1', name: 'Robbie', description: 'Large mechanical workhorse, crude but effective', price: 1200, instock: 12, categories: ['complete']},
{sku: 'EVE-1', name: 'Eve', description: 'Extraterrestrial Vegetation Evaluator', price: 5000, instock: 10, categories: ['complete']}
{sku: 'EVE-1', name: 'Eve', description: 'Extraterrestrial Vegetation Evaluator', price: 5000, instock: 10, categories: ['complete']},
{sku: 'HAL-1', name: 'HAL', description: 'Sorry Dave, I cant do that', price: 7500, instock: 2, categories: ['complete']}
]);
// full text index for searching

View File

@@ -42,6 +42,11 @@
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
</dependencies>
<build>

View File

@@ -20,7 +20,7 @@ public class Location {
/**
* Calculate the distance in km between this location and the given coordinates
**/
public double getDistance(double targetLatitude, double targetLongitude) {
public long getDistance(double targetLatitude, double targetLongitude) {
double distance = 0.0;
double diffLat = Math.pow(this.latitude - targetLatitude, 2);
@@ -30,6 +30,6 @@ public class Location {
// 1 nautical mile == 1.852km
distance = distance * 60.0 / 1.852;
return Math.round(distance);
return (long)(Math.round(distance));
}
}

View File

@@ -6,11 +6,20 @@ import spark.Spark;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.DbUtils;
import com.google.gson.Gson;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
@@ -21,6 +30,7 @@ import java.util.List;
import java.util.Map;
public class Main {
private static String CART_URL = "http://cart:8080/shipping/";
private static Logger logger = LoggerFactory.getLogger(Main.class);
private static ComboPooledDataSource cpds = null;
@@ -53,8 +63,7 @@ public class Main {
Spark.get("/count", (req, res) -> {
String data;
try {
Connection conn = cpds.getConnection();
data = queryToJson(conn, "select count(*) as count from cities");
data = queryToJson("select count(*) as count from cities");
res.header("Content-Type", "application/json");
} catch(Exception e) {
logger.error("count", e);
@@ -68,9 +77,8 @@ public class Main {
Spark.get("/codes", (req, res) -> {
String data;
try {
Connection conn = cpds.getConnection();
String query = "select code, name from codes order by name asc";
data = queryToJson(conn, query);
data = queryToJson(query);
res.header("Content-Type", "application/json");
} catch(Exception e) {
logger.error("codes", e);
@@ -84,10 +92,9 @@ public class Main {
Spark.get("/match/:code/:text", (req, res) -> {
String data;
try {
Connection conn = cpds.getConnection();
String query = "select uuid, name from cities where country_code ='" + req.params(":code") + "' and city like '" + req.params(":text") + "%' order by name asc limit 10";
logger.info("Query " + query);
data = queryToJson(conn, query);
data = queryToJson(query);
res.header("Content-Type", "application/json");
} catch(Exception e) {
logger.error("match", e);
@@ -107,9 +114,10 @@ public class Main {
buffer.append('{');
Location location = getLocation(req.params(":uuid"));
if(location != null) {
long distance = location.getDistance(homeLat, homeLong);
// charge 0.05 Euro per km
double distance = location.getDistance(homeLat, homeLong);
double cost = distance * 0.05;
// try to avoid rounding errors
double cost = ((double)(distance * 5)) / 100.0;
buffer.append(write("distance", distance)).append(',');
buffer.append(write("cost", cost));
} else {
@@ -120,9 +128,18 @@ public class Main {
return buffer.toString();
});
Spark.post("/confirm", (req, res) -> {
logger.info("confirm " + req.body());
return "OK";
Spark.post("/confirm/:id", (req, res) -> {
logger.info("confirm " + req.params(":id") + " - " + req.body());
String cart = addToCart(req.params(":id"), req.body());
logger.info("new cart " + cart);
if(cart.equals("")) {
res.status(404);
} else {
res.header("Content-Type", "application/json");
}
return cart;
});
logger.info("Ready");
@@ -133,16 +150,15 @@ public class Main {
/**
* Query to Json - QED
**/
private static String queryToJson(Connection connection, String query) {
private static String queryToJson(String query) {
List<Map<String, Object>> listOfMaps = null;
try {
QueryRunner queryRunner = new QueryRunner();
listOfMaps = queryRunner.query(connection, query, new MapListHandler());
QueryRunner queryRunner = new QueryRunner(cpds);
listOfMaps = queryRunner.query(query, new MapListHandler());
} catch (SQLException se) {
throw new RuntimeException("Couldn't query the database.", se);
} finally {
DbUtils.closeQuietly(connection);
}
return new Gson().toJson(listOfMaps);
}
@@ -180,4 +196,40 @@ public class Main {
return buffer.toString();
}
private static String addToCart(String id, String data) {
StringBuilder buffer = new StringBuilder();
DefaultHttpClient httpClient = null;
try {
// set timeout to 5 secs
HttpParams httpParams = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
httpClient = new DefaultHttpClient(httpParams);
HttpPost postRequest = new HttpPost(CART_URL + id);
StringEntity payload = new StringEntity(data);
payload.setContentType("application/json");
postRequest.setEntity(payload);
HttpResponse res = httpClient.execute(postRequest);
if(res.getStatusLine().getStatusCode() == 200) {
BufferedReader in = new BufferedReader(new InputStreamReader(res.getEntity().getContent()));
String line;
while((line = in.readLine()) != null) {
buffer.append(line);
}
} else {
logger.warn("Failed with code: " + res.getStatusLine().getStatusCode());
}
} catch(Exception e) {
logger.error("http client exception", e);
} finally {
if(httpClient != null) {
httpClient.getConnectionManager().shutdown();
}
}
return buffer.toString();
}
}

View File

@@ -14,7 +14,7 @@
<tr ng-repeat="item in data.cart.items">
<td><input type="number" size="2" min="0" max="10" ng-model="item.qty" ng-change="change(item.sku, item.qty);"/></td>
<td>{{ item.name }}</td>
<td class="currency">&euro;{{ item.subtotal }}</td>
<td class="currency">&euro;{{ item.subtotal.toFixed(2) }}</td>
</tr>
<tr>
<td colspan="3">&nbsp;</td>
@@ -22,12 +22,12 @@
<tr>
<td>&nbsp;</td>
<td>Tax</td>
<td class="currency">&euro;{{ data.cart.tax }}</td>
<td class="currency">&euro;{{ data.cart.tax.toFixed(2) }}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>Total</td>
<td class="currency">&euro;{{ data.cart.total }}</td>
<td class="currency">&euro;{{ data.cart.total.toFixed(2) }}</td>
</tr>
<tr>
<td colspan="3">

View File

@@ -33,7 +33,7 @@
Empty
</div>
<div ng-if="data.cart.total != 0" class="carttotal">
&euro;{{ data.cart.total }}
&euro;{{ data.cart.total.toFixed(2) }}
</div>
<h3>Categories</h3>
<ul class="products">

View File

@@ -32,6 +32,9 @@
}).when('/shipping', {
templateUrl: 'shipping.html',
controller: 'shipform'
}).when('/payment', {
templateUrl: 'payment.html',
controller: 'paymentform'
});
// needed for URL rewrite hash
@@ -209,7 +212,7 @@
loadCart($scope.data.uniqueid);
});
robotshop.controller('shipform', function($scope, $http, currentUser) {
robotshop.controller('shipform', function($scope, $http, $location, currentUser) {
$scope.data = {};
$scope.data.countries = [];
$scope.data.selectedCountry = '';
@@ -226,6 +229,7 @@
}).then((res) => {
console.log('shipping data', res.data);
$scope.data.shipping = res.data;
$scope.data.shipping.location = $scope.data.selectedCountry.name + ' ' + autoLocation;
}).catch((e) => {
console.log('ERROR', e);
});
@@ -234,11 +238,15 @@
$scope.confirmShipping = function() {
console.log('shipping confirmed');
$http({
url: '/api/shipping/confirm',
url: '/api/shipping/confirm/' + currentUser.uniqueid,
method: 'POST',
data: $scope.data.shipping
}).then((res) => {
// go to final confirmation
console.log('confirm cart', res.data);
// save new cart
currentUser.cart = res.data;
$location.url('/payment');
}).catch((e) => {
console.log('ERROR', e);
});
@@ -251,6 +259,7 @@
}
$scope.data.selectedLocation = '';
$scope.data.disableCalc = true;
$scope.data.shipping = '';
};
// auto-complete
@@ -290,10 +299,10 @@
},
onSelect: (e, term, item) => {
console.log('select', term, item);
console.log('content', item.textContent);
console.log('attrib', item.getAttribute('loc-uuid'));
uuid = item.getAttribute('loc-uuid');
autoLocation = item.getAttribute('data-val');
$scope.data.disableCalc = false;
$scope.data.shipping = '';
// synchronise angular
$scope.$apply();
}
@@ -305,6 +314,17 @@
buildauto();
});
robotshop.controller('paymentform', function($scope, $http, currentUser) {
$scope.data = {};
$scope.data.uniqueid = currentUser.uniqueid;
$scope.data.cart = currentUser.cart;
$scope.pay = function() {
};
console.log('paymentform init');
});
robotshop.controller('loginform', function($scope, $http, currentUser) {
$scope.data = {};
$scope.data.name = '';

39
web/static/payment.html Normal file
View File

@@ -0,0 +1,39 @@
<!-- payment template -->
<div>
<h3>Review your order</h3>
<div ng-if="data.cart.total == 0">
Your cart is empty, get shopping
</div>
<div ng-if="data.cart.total != 0">
<table>
<tr>
<th>QTY</th>
<th>Name</th>
<th>Sub Total</th>
</tr>
<tr ng-repeat="item in data.cart.items">
<td>{{ item.qty }}</td>
<td>{{ item.name }}</td>
<td class="currency">&euro;{{ item.subtotal.toFixed(2) }}</td>
</tr>
<tr>
<td colspan="3">&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>Tax</td>
<td class="currency">&euro;{{ data.cart.tax.toFixed(2) }}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>Total</td>
<td class="currency">&euro;{{ data.cart.total.toFixed(2) }}</td>
</tr>
<tr>
<td colspan="3">
<button ng-click="pay();">Pay with Paypal</button>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -12,7 +12,7 @@
{{ data.product.description }}
</div>
<div class="productcart">
Price &euro;{{ data.product.price }}
Price &euro;{{ data.product.price.toFixed(2) }}
<span ng-if="data.product.instock == 0">
Out of stock
</span>

View File

@@ -12,8 +12,8 @@
</div>
<!-- add in shipping distance and cost -->
<div ng-if="data.shipping">
<div>Distance {{ data.shipping.distance }}</div>
<div>Cost &euro;{{ data.shipping.cost }}</div>
<div>Distance {{ data.shipping.distance }}km</div>
<div>Cost &euro;{{ data.shipping.cost.toFixed(2) }}</div>
<div>
<button ng-click="confirmShipping();">Confirm</button>
</div>