SlideShare a Scribd company logo
Creating an Uber Clone - Part XXXI
The last two "big ticket items" are billing and social login. I won't implement them with adherence to the way they were implemented by Uber. I want to keep both of
these features simple as they are both very volatile. Features and requirements within both of these API's can change literally overnight.
Billing
✦A bit different from the native Uber app
✦To keep billing simple I'll just charge $1 per minute
✦I won’t use in-app-purchase
✦I’ll use Braintree
© Codename One 2017 all rights reserved
I will implement billing as a request before the ride starts. I'll use Braintree to do this mostly because it's already implemented in Codename One. The original
implementation in Uber checks whether a billing method already exists. This is possible to do in Braintree but it would require some extra work.

To keep billing simple I'll just charge $1 per minute and do the charge in the server side.

In app purchase is one of the big ticket features in mobile apps. We support this rather well in Codename One but we can't use in app purchase for this case.

In app purchase was devised as a tool to buy "virtual goods" inside an application. This reduces friction as no credit card is needed (Google/Apple already have it) and
makes purchases more fluid. The definition of "virtual goods" has some gray areas but generally the idea is that a good or service sold would be something that has no
immediate physical cost.

Good examples for virtual goods would be: in game item, upgrade of software functionality, app subscription etc.

However, physical items and services are explicitly prohibited from using in app purchase. This isn't a bad thing. In app purchase takes a hefty commission of 30% which
isn't viable for most physical goods sold.

Braintree is a part of PayPal and provides an easy to integrate mobile payment SDK for selling physical goods and services. In theory we could just collect a credit card
and call it a day but that's naive. Securing online transactions is a nuanced task by using a trusted 3rd party a great deal of the risk and liability is transferred to them.

One of the core concepts when working with Braintree is opacity. The developer doesn't get access to the credit card or billing information. Instead a nonce & token are
passed between the client and server. Even if a security flaw exists in the app a hacker wouldn't gain access to any valuable information as the values expire.
Braintree
© Codename One 2017 all rights reserved
This diagram and covers the process of purchasing via Braintree. Lets dig into the pieces within it
Braintree
© Codename One 2017 all rights reserved
The client code (our mobile app) asks our server for a token.

The server generates a token with the Braintree server code and returns it. A client token is a signed data blob that includes configuration and authorization information
needed by Braintree to associate the transaction correctly. It can't be reused and should be hard to spoof in an attack
Braintree
© Codename One 2017 all rights reserved
The mobile app invokes the Braintree UI with the token. That UI lets the user pick a credit card or other payment option (e.g. PayPal, Android Pay, Apple Pay etc.) then
communicates with Braintree's servers. The result of all this is a nonce which is a unique key that allows you to charge this payment method
Braintree
© Codename One 2017 all rights reserved
Our app now sends the nonce our Spring Boot server
Braintree
© Codename One 2017 all rights reserved
The server uses the server side Braintree API and the nonce to charge an amount to the payment method. Notice that the amount charged is completely up to the server
and isn't a part of the client side UI!
<dependency>
<groupId>com.braintreepayments.gateway</groupId>
<artifactId>braintree-java</artifactId>
<version>2.71.0</version>
</dependency>
POM
The Braintree SDK for Java is pretty easy to use. We already have it in Maven but just in case you skipped those lines this needs to be in the POM file
@Service
public class BraintreeService {
private final static BraintreeGateway gateway = new BraintreeGateway(
Environment.SANDBOX,
"your_merchant_id",
"your_public_key",
"your_private_key"
);
@Autowired
private RideRepository rides;
public String getClientToken() {
return gateway.clientToken().generate();
}
public void saveNonce(long rideId, String nonce) {
Ride r = rides.findOne(rideId);
r.setNonce(nonce);
rides.save(r);
}
public void pay(BigDecimal amount, String nonce) {
TransactionRequest requestT = new TransactionRequest()
.amount(amount)
.paymentMethodNonce(nonce)
.options()
.submitForSettlement(true)
.done();
}
}
BraintreeService
Next we add a `BraintreeService` class which is remarkably simple.

These values should be updated from Braintree and SANDBOX should be updated to production once everything is working
@Service
public class BraintreeService {
private final static BraintreeGateway gateway = new BraintreeGateway(
Environment.SANDBOX,
"your_merchant_id",
"your_public_key",
"your_private_key"
);
@Autowired
private RideRepository rides;
public String getClientToken() {
return gateway.clientToken().generate();
}
public void saveNonce(long rideId, String nonce) {
Ride r = rides.findOne(rideId);
r.setNonce(nonce);
rides.save(r);
}
public void pay(BigDecimal amount, String nonce) {
TransactionRequest requestT = new TransactionRequest()
.amount(amount)
.paymentMethodNonce(nonce)
.options()
.submitForSettlement(true)
.done();
}
}
BraintreeService
This is the client token that we use to identify the transaction. Notice we generate a new one for every request
@Service
public class BraintreeService {
private final static BraintreeGateway gateway = new BraintreeGateway(
Environment.SANDBOX,
"your_merchant_id",
"your_public_key",
"your_private_key"
);
@Autowired
private RideRepository rides;
public String getClientToken() {
return gateway.clientToken().generate();
}
public void saveNonce(long rideId, String nonce) {
Ride r = rides.findOne(rideId);
r.setNonce(nonce);
rides.save(r);
}
public void pay(BigDecimal amount, String nonce) {
TransactionRequest requestT = new TransactionRequest()
.amount(amount)
.paymentMethodNonce(nonce)
.options()
.submitForSettlement(true)
.done();
}
}
BraintreeService
We save the nonce into the Ride object. This assumes payment authorization happens before the ride is completed. Once the ride is finished the nonce is instantly
available to do perform the charge
@Controller
@RequestMapping("/pay")
public class BraintreeWebservice {
@Autowired
private BraintreeService payment;
@RequestMapping(method=RequestMethod.GET,value = "/token")
public @ResponseBody String getClientToken(long id) {
return payment.getClientToken();
}
@RequestMapping(method=RequestMethod.GET,value="/nonce")
public @ResponseBody String nonce(
@RequestParam(name="ride", required = true) long rideId,
@RequestParam(name="nonce", required = true) String nonce) {
payment.saveNonce(rideId, nonce);
return "OK";
}
}
BraintreeWebservice
Before we proceed further the obvious next step is the webservice to match.

It's mostly trivial but I'd like to point out a small nuance: pay isn't mapped. We invoke pay in the server so we don't need to expose it to the client side.
private String nonce;
public String getNonce() {
return nonce;
}
public void setNonce(String nonce) {
this.nonce = nonce;
}
Ride
That code required some unexpected changes which I will get to shortly. The first change was pretty predictable though. We just had to add a nonce field to the Ride
class
private Long currentRide;
public Long getCurrentRide() {
return currentRide;
}
public void setCurrentRide(Long currentRide) {
this.currentRide = currentRide;
}
Ride
Here's the part I didn't expect. I need to add the ride id to the User object. A driver has a reference to the Ride object which is why we didn't need this up until now.
However, when the user tries to pay he can't set this anywhere else...

Unfortunately there is no other place where the nonce would fit. Since it's transient we can't add it to the User as we'd want some logging. The Ride object is the "right
place" for the nonce.
public long acceptRide(String token, long userId) {
User driver = users.findByAuthToken(token).get(0);
User passenger = users.findOne(userId);
if(!passenger.isHailing()) {
throw new RuntimeException("Not hailing");
}
passenger.setHailing(false);
passenger.setAssignedUser(driver.getId());
driver.setAssignedUser(userId);
Ride r = new Ride();
r.setDriver(driver);
r.setPassenger(passenger);
rides.save(r);
driver.setCurrentRide(r.getId());
passenger.setCurrentRide(r.getId());
users.save(driver);
users.save(passenger);
return r.getId();
}
acceptRide
To get this to work I had to make a few changes to the acceptRide method.

I added the ride reference to both the driver and passenger for future reference.
public long acceptRide(String token, long userId) {
User driver = users.findByAuthToken(token).get(0);
User passenger = users.findOne(userId);
if(!passenger.isHailing()) {
throw new RuntimeException("Not hailing");
}
passenger.setHailing(false);
passenger.setAssignedUser(driver.getId());
driver.setAssignedUser(userId);
Ride r = new Ride();
r.setDriver(driver);
r.setPassenger(passenger);
rides.save(r);
driver.setCurrentRide(r.getId());
passenger.setCurrentRide(r.getId());
users.save(driver);
users.save(passenger);
return r.getId();
}
acceptRide
I moved these lines downward because the ride ID will only be available after the rides.save() call
public void finishRide(long rideId) {
Ride current = rides.findOne(rideId);
current.setFinished(true);
if(current.isStarted() && current.getNonce() != null) {
Set<Waypoint> s = current.getRoute();
Iterator<Waypoint> i = s.iterator();
if(i.hasNext()) {
long startTime = i.next().getTime();
long endTime = -1;
while(i.hasNext()) {
endTime = i.next().getTime();
}
if(endTime > -1) {
BigDecimal cost = BigDecimal.valueOf(endTime - startTime).
divide(BigDecimal.valueOf(60000));
current.setCost(cost);
payments.pay(current.getCost(), current.getNonce());
}
}
}
rides.save(current);
}
finishRide
Since payment is handled on the server side we can go directly to it even before we do the client side... I've decided to do this in the finishRide method.

A ride that was finished before it was started is effectively canceled. A ride without a nonce can't be charged
public void finishRide(long rideId) {
Ride current = rides.findOne(rideId);
current.setFinished(true);
if(current.isStarted() && current.getNonce() != null) {
Set<Waypoint> s = current.getRoute();
Iterator<Waypoint> i = s.iterator();
if(i.hasNext()) {
long startTime = i.next().getTime();
long endTime = -1;
while(i.hasNext()) {
endTime = i.next().getTime();
}
if(endTime > -1) {
BigDecimal cost = BigDecimal.valueOf(endTime - startTime).
divide(BigDecimal.valueOf(60000));
current.setCost(cost);
payments.pay(current.getCost(), current.getNonce());
}
}
}
rides.save(current);
}
finishRide
I use the route which is ordered based on time to find the start time of the ride
public void finishRide(long rideId) {
Ride current = rides.findOne(rideId);
current.setFinished(true);
if(current.isStarted() && current.getNonce() != null) {
Set<Waypoint> s = current.getRoute();
Iterator<Waypoint> i = s.iterator();
if(i.hasNext()) {
long startTime = i.next().getTime();
long endTime = -1;
while(i.hasNext()) {
endTime = i.next().getTime();
}
if(endTime > -1) {
BigDecimal cost = BigDecimal.valueOf(endTime - startTime).
divide(BigDecimal.valueOf(60000));
current.setCost(cost);
payments.pay(current.getCost(), current.getNonce());
}
}
}
rides.save(current);
}
finishRide
I then go to the last element and find the end time of the ride
public void finishRide(long rideId) {
Ride current = rides.findOne(rideId);
current.setFinished(true);
if(current.isStarted() && current.getNonce() != null) {
Set<Waypoint> s = current.getRoute();
Iterator<Waypoint> i = s.iterator();
if(i.hasNext()) {
long startTime = i.next().getTime();
long endTime = -1;
while(i.hasNext()) {
endTime = i.next().getTime();
}
if(endTime > -1) {
BigDecimal cost = BigDecimal.valueOf(endTime - startTime).
divide(BigDecimal.valueOf(60000));
current.setCost(cost);
payments.pay(current.getCost(), current.getNonce());
}
}
}
rides.save(current);
}
finishRide
Assuming the ride has more than one Waypoint (otherwise endTime would be -1) we can just charge 1USD per 60 seconds. And payment is effectively done on the
server. Again I oversimplified a lot and ignored basic complexities like the driver forgetting press “Finish".

More Related Content

PDF
Creating an Uber Clone - Part XXXI.pdf
PDF
Restaurant Server - Transcript.pdf
PDF
Creating an Uber Clone - Part XXXII.pdf
PDF
Restaurant Server.pdf
PPTX
Expanding APIs beyond the Web
PDF
Braintree and our new v.zero SDK for iOS
PDF
Braintree SDK v.zero or "A payment gateway walks into a bar..." - Devfest Nan...
PPTX
Mobile Payments Workshop
Creating an Uber Clone - Part XXXI.pdf
Restaurant Server - Transcript.pdf
Creating an Uber Clone - Part XXXII.pdf
Restaurant Server.pdf
Expanding APIs beyond the Web
Braintree and our new v.zero SDK for iOS
Braintree SDK v.zero or "A payment gateway walks into a bar..." - Devfest Nan...
Mobile Payments Workshop

Similar to Creating an Uber Clone - Part XXXI - Transcript.pdf (20)

PDF
R.Grassi - P.Sardo - One integration: every wat to pay
PDF
Creating an Uber Clone - Part XI - Transcript.pdf
PDF
Cómo integrar un método de pago en nuestros desarrollos.
PPTX
Designing REST API automation tests in Kotlin
PDF
Payments in Mobile Apps
PDF
Insert Token: APIDays London Remix
PDF
Insert Token: API Days Remix
PDF
Monetising your Web Applications with Braintree
PDF
Payment api basics
PDF
Present and future of mCommerce in Spain
PDF
On Demand Taxi Platform Customizable & Feature-Rich App.pdf
PPTX
Increase payment platform adoption by growing partner/client categories
PDF
DWS Mobile Payments Workshop
PDF
Creating an Uber Clone - Part XXVI - Transcript.pdf
PPTX
Bootstrapping an App for Launch
PPTX
Payment Integration using Braintree Connector | MuleSoft Mysore Meetup #37
PPTX
Making Payments in Android Easy
PDF
Cross-Platform Data Access for Android and iPhone
PPTX
Mobile payments at Droidcon Eastern Europe
PDF
Implementing_Domain_Driven_Desing_with_Spring.pdf
R.Grassi - P.Sardo - One integration: every wat to pay
Creating an Uber Clone - Part XI - Transcript.pdf
Cómo integrar un método de pago en nuestros desarrollos.
Designing REST API automation tests in Kotlin
Payments in Mobile Apps
Insert Token: APIDays London Remix
Insert Token: API Days Remix
Monetising your Web Applications with Braintree
Payment api basics
Present and future of mCommerce in Spain
On Demand Taxi Platform Customizable & Feature-Rich App.pdf
Increase payment platform adoption by growing partner/client categories
DWS Mobile Payments Workshop
Creating an Uber Clone - Part XXVI - Transcript.pdf
Bootstrapping an App for Launch
Payment Integration using Braintree Connector | MuleSoft Mysore Meetup #37
Making Payments in Android Easy
Cross-Platform Data Access for Android and iPhone
Mobile payments at Droidcon Eastern Europe
Implementing_Domain_Driven_Desing_with_Spring.pdf
Ad

More from ShaiAlmog1 (20)

PDF
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
PDF
create-netflix-clone-06-client-ui.pdf
PDF
create-netflix-clone-01-introduction_transcript.pdf
PDF
create-netflix-clone-02-server_transcript.pdf
PDF
create-netflix-clone-04-server-continued_transcript.pdf
PDF
create-netflix-clone-01-introduction.pdf
PDF
create-netflix-clone-06-client-ui_transcript.pdf
PDF
create-netflix-clone-03-server.pdf
PDF
create-netflix-clone-04-server-continued.pdf
PDF
create-netflix-clone-05-client-model_transcript.pdf
PDF
create-netflix-clone-03-server_transcript.pdf
PDF
create-netflix-clone-02-server.pdf
PDF
create-netflix-clone-05-client-model.pdf
PDF
Creating a Whatsapp Clone - Part II.pdf
PDF
Creating a Whatsapp Clone - Part IX - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part II - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part V - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV.pdf
PDF
Creating a Whatsapp Clone - Part I - Transcript.pdf
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-01-introduction.pdf
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-03-server.pdf
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-02-server.pdf
create-netflix-clone-05-client-model.pdf
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part II - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdf
Ad

Recently uploaded (20)

PDF
A comparative analysis of optical character recognition models for extracting...
DOCX
The AUB Centre for AI in Media Proposal.docx
PPTX
Machine Learning_overview_presentation.pptx
PDF
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
PDF
cuic standard and advanced reporting.pdf
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPT
Teaching material agriculture food technology
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PPTX
Big Data Technologies - Introduction.pptx
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Encapsulation_ Review paper, used for researhc scholars
PPTX
sap open course for s4hana steps from ECC to s4
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
Empathic Computing: Creating Shared Understanding
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PPTX
Cloud computing and distributed systems.
A comparative analysis of optical character recognition models for extracting...
The AUB Centre for AI in Media Proposal.docx
Machine Learning_overview_presentation.pptx
Optimiser vos workloads AI/ML sur Amazon EC2 et AWS Graviton
cuic standard and advanced reporting.pdf
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Advanced methodologies resolving dimensionality complications for autism neur...
Teaching material agriculture food technology
Diabetes mellitus diagnosis method based random forest with bat algorithm
Big Data Technologies - Introduction.pptx
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
MYSQL Presentation for SQL database connectivity
Encapsulation_ Review paper, used for researhc scholars
sap open course for s4hana steps from ECC to s4
Spectral efficient network and resource selection model in 5G networks
Empathic Computing: Creating Shared Understanding
Mobile App Security Testing_ A Comprehensive Guide.pdf
Chapter 3 Spatial Domain Image Processing.pdf
Cloud computing and distributed systems.

Creating an Uber Clone - Part XXXI - Transcript.pdf

  • 1. Creating an Uber Clone - Part XXXI The last two "big ticket items" are billing and social login. I won't implement them with adherence to the way they were implemented by Uber. I want to keep both of these features simple as they are both very volatile. Features and requirements within both of these API's can change literally overnight.
  • 2. Billing ✦A bit different from the native Uber app ✦To keep billing simple I'll just charge $1 per minute ✦I won’t use in-app-purchase ✦I’ll use Braintree © Codename One 2017 all rights reserved I will implement billing as a request before the ride starts. I'll use Braintree to do this mostly because it's already implemented in Codename One. The original implementation in Uber checks whether a billing method already exists. This is possible to do in Braintree but it would require some extra work. To keep billing simple I'll just charge $1 per minute and do the charge in the server side. In app purchase is one of the big ticket features in mobile apps. We support this rather well in Codename One but we can't use in app purchase for this case. In app purchase was devised as a tool to buy "virtual goods" inside an application. This reduces friction as no credit card is needed (Google/Apple already have it) and makes purchases more fluid. The definition of "virtual goods" has some gray areas but generally the idea is that a good or service sold would be something that has no immediate physical cost. Good examples for virtual goods would be: in game item, upgrade of software functionality, app subscription etc. However, physical items and services are explicitly prohibited from using in app purchase. This isn't a bad thing. In app purchase takes a hefty commission of 30% which isn't viable for most physical goods sold. Braintree is a part of PayPal and provides an easy to integrate mobile payment SDK for selling physical goods and services. In theory we could just collect a credit card and call it a day but that's naive. Securing online transactions is a nuanced task by using a trusted 3rd party a great deal of the risk and liability is transferred to them.
 One of the core concepts when working with Braintree is opacity. The developer doesn't get access to the credit card or billing information. Instead a nonce & token are passed between the client and server. Even if a security flaw exists in the app a hacker wouldn't gain access to any valuable information as the values expire.
  • 3. Braintree © Codename One 2017 all rights reserved This diagram and covers the process of purchasing via Braintree. Lets dig into the pieces within it
  • 4. Braintree © Codename One 2017 all rights reserved The client code (our mobile app) asks our server for a token. The server generates a token with the Braintree server code and returns it. A client token is a signed data blob that includes configuration and authorization information needed by Braintree to associate the transaction correctly. It can't be reused and should be hard to spoof in an attack
  • 5. Braintree © Codename One 2017 all rights reserved The mobile app invokes the Braintree UI with the token. That UI lets the user pick a credit card or other payment option (e.g. PayPal, Android Pay, Apple Pay etc.) then communicates with Braintree's servers. The result of all this is a nonce which is a unique key that allows you to charge this payment method
  • 6. Braintree © Codename One 2017 all rights reserved Our app now sends the nonce our Spring Boot server
  • 7. Braintree © Codename One 2017 all rights reserved The server uses the server side Braintree API and the nonce to charge an amount to the payment method. Notice that the amount charged is completely up to the server and isn't a part of the client side UI!
  • 8. <dependency> <groupId>com.braintreepayments.gateway</groupId> <artifactId>braintree-java</artifactId> <version>2.71.0</version> </dependency> POM The Braintree SDK for Java is pretty easy to use. We already have it in Maven but just in case you skipped those lines this needs to be in the POM file
  • 9. @Service public class BraintreeService { private final static BraintreeGateway gateway = new BraintreeGateway( Environment.SANDBOX, "your_merchant_id", "your_public_key", "your_private_key" ); @Autowired private RideRepository rides; public String getClientToken() { return gateway.clientToken().generate(); } public void saveNonce(long rideId, String nonce) { Ride r = rides.findOne(rideId); r.setNonce(nonce); rides.save(r); } public void pay(BigDecimal amount, String nonce) { TransactionRequest requestT = new TransactionRequest() .amount(amount) .paymentMethodNonce(nonce) .options() .submitForSettlement(true) .done(); } } BraintreeService Next we add a `BraintreeService` class which is remarkably simple. These values should be updated from Braintree and SANDBOX should be updated to production once everything is working
  • 10. @Service public class BraintreeService { private final static BraintreeGateway gateway = new BraintreeGateway( Environment.SANDBOX, "your_merchant_id", "your_public_key", "your_private_key" ); @Autowired private RideRepository rides; public String getClientToken() { return gateway.clientToken().generate(); } public void saveNonce(long rideId, String nonce) { Ride r = rides.findOne(rideId); r.setNonce(nonce); rides.save(r); } public void pay(BigDecimal amount, String nonce) { TransactionRequest requestT = new TransactionRequest() .amount(amount) .paymentMethodNonce(nonce) .options() .submitForSettlement(true) .done(); } } BraintreeService This is the client token that we use to identify the transaction. Notice we generate a new one for every request
  • 11. @Service public class BraintreeService { private final static BraintreeGateway gateway = new BraintreeGateway( Environment.SANDBOX, "your_merchant_id", "your_public_key", "your_private_key" ); @Autowired private RideRepository rides; public String getClientToken() { return gateway.clientToken().generate(); } public void saveNonce(long rideId, String nonce) { Ride r = rides.findOne(rideId); r.setNonce(nonce); rides.save(r); } public void pay(BigDecimal amount, String nonce) { TransactionRequest requestT = new TransactionRequest() .amount(amount) .paymentMethodNonce(nonce) .options() .submitForSettlement(true) .done(); } } BraintreeService We save the nonce into the Ride object. This assumes payment authorization happens before the ride is completed. Once the ride is finished the nonce is instantly available to do perform the charge
  • 12. @Controller @RequestMapping("/pay") public class BraintreeWebservice { @Autowired private BraintreeService payment; @RequestMapping(method=RequestMethod.GET,value = "/token") public @ResponseBody String getClientToken(long id) { return payment.getClientToken(); } @RequestMapping(method=RequestMethod.GET,value="/nonce") public @ResponseBody String nonce( @RequestParam(name="ride", required = true) long rideId, @RequestParam(name="nonce", required = true) String nonce) { payment.saveNonce(rideId, nonce); return "OK"; } } BraintreeWebservice Before we proceed further the obvious next step is the webservice to match. It's mostly trivial but I'd like to point out a small nuance: pay isn't mapped. We invoke pay in the server so we don't need to expose it to the client side.
  • 13. private String nonce; public String getNonce() { return nonce; } public void setNonce(String nonce) { this.nonce = nonce; } Ride That code required some unexpected changes which I will get to shortly. The first change was pretty predictable though. We just had to add a nonce field to the Ride class
  • 14. private Long currentRide; public Long getCurrentRide() { return currentRide; } public void setCurrentRide(Long currentRide) { this.currentRide = currentRide; } Ride Here's the part I didn't expect. I need to add the ride id to the User object. A driver has a reference to the Ride object which is why we didn't need this up until now. However, when the user tries to pay he can't set this anywhere else... Unfortunately there is no other place where the nonce would fit. Since it's transient we can't add it to the User as we'd want some logging. The Ride object is the "right place" for the nonce.
  • 15. public long acceptRide(String token, long userId) { User driver = users.findByAuthToken(token).get(0); User passenger = users.findOne(userId); if(!passenger.isHailing()) { throw new RuntimeException("Not hailing"); } passenger.setHailing(false); passenger.setAssignedUser(driver.getId()); driver.setAssignedUser(userId); Ride r = new Ride(); r.setDriver(driver); r.setPassenger(passenger); rides.save(r); driver.setCurrentRide(r.getId()); passenger.setCurrentRide(r.getId()); users.save(driver); users.save(passenger); return r.getId(); } acceptRide To get this to work I had to make a few changes to the acceptRide method. I added the ride reference to both the driver and passenger for future reference.
  • 16. public long acceptRide(String token, long userId) { User driver = users.findByAuthToken(token).get(0); User passenger = users.findOne(userId); if(!passenger.isHailing()) { throw new RuntimeException("Not hailing"); } passenger.setHailing(false); passenger.setAssignedUser(driver.getId()); driver.setAssignedUser(userId); Ride r = new Ride(); r.setDriver(driver); r.setPassenger(passenger); rides.save(r); driver.setCurrentRide(r.getId()); passenger.setCurrentRide(r.getId()); users.save(driver); users.save(passenger); return r.getId(); } acceptRide I moved these lines downward because the ride ID will only be available after the rides.save() call
  • 17. public void finishRide(long rideId) { Ride current = rides.findOne(rideId); current.setFinished(true); if(current.isStarted() && current.getNonce() != null) { Set<Waypoint> s = current.getRoute(); Iterator<Waypoint> i = s.iterator(); if(i.hasNext()) { long startTime = i.next().getTime(); long endTime = -1; while(i.hasNext()) { endTime = i.next().getTime(); } if(endTime > -1) { BigDecimal cost = BigDecimal.valueOf(endTime - startTime). divide(BigDecimal.valueOf(60000)); current.setCost(cost); payments.pay(current.getCost(), current.getNonce()); } } } rides.save(current); } finishRide Since payment is handled on the server side we can go directly to it even before we do the client side... I've decided to do this in the finishRide method. A ride that was finished before it was started is effectively canceled. A ride without a nonce can't be charged
  • 18. public void finishRide(long rideId) { Ride current = rides.findOne(rideId); current.setFinished(true); if(current.isStarted() && current.getNonce() != null) { Set<Waypoint> s = current.getRoute(); Iterator<Waypoint> i = s.iterator(); if(i.hasNext()) { long startTime = i.next().getTime(); long endTime = -1; while(i.hasNext()) { endTime = i.next().getTime(); } if(endTime > -1) { BigDecimal cost = BigDecimal.valueOf(endTime - startTime). divide(BigDecimal.valueOf(60000)); current.setCost(cost); payments.pay(current.getCost(), current.getNonce()); } } } rides.save(current); } finishRide I use the route which is ordered based on time to find the start time of the ride
  • 19. public void finishRide(long rideId) { Ride current = rides.findOne(rideId); current.setFinished(true); if(current.isStarted() && current.getNonce() != null) { Set<Waypoint> s = current.getRoute(); Iterator<Waypoint> i = s.iterator(); if(i.hasNext()) { long startTime = i.next().getTime(); long endTime = -1; while(i.hasNext()) { endTime = i.next().getTime(); } if(endTime > -1) { BigDecimal cost = BigDecimal.valueOf(endTime - startTime). divide(BigDecimal.valueOf(60000)); current.setCost(cost); payments.pay(current.getCost(), current.getNonce()); } } } rides.save(current); } finishRide I then go to the last element and find the end time of the ride
  • 20. public void finishRide(long rideId) { Ride current = rides.findOne(rideId); current.setFinished(true); if(current.isStarted() && current.getNonce() != null) { Set<Waypoint> s = current.getRoute(); Iterator<Waypoint> i = s.iterator(); if(i.hasNext()) { long startTime = i.next().getTime(); long endTime = -1; while(i.hasNext()) { endTime = i.next().getTime(); } if(endTime > -1) { BigDecimal cost = BigDecimal.valueOf(endTime - startTime). divide(BigDecimal.valueOf(60000)); current.setCost(cost); payments.pay(current.getCost(), current.getNonce()); } } } rides.save(current); } finishRide Assuming the ride has more than one Waypoint (otherwise endTime would be -1) we can just charge 1USD per 60 seconds. And payment is effectively done on the server. Again I oversimplified a lot and ignored basic complexities like the driver forgetting press “Finish".