I'm developing an e-commerce with back-end in Spring boot and front-end in Angular material. When I'm in the payment page of the browser, after a click event on the button Pay
, I'd like that an order, that is related to the payment on which the click event occured, is created but I don't know how to do this thing.
The problem is that, when I create an object of type Payment
after the click event, this object (due to the 1:1 relationship between Payment
and Order
) needs an object of type Order
(as shown in the following entities) and I don't know how to create it from the front-end and connect it with the relative Payment
object.
BACK-END
Order entity:
@Getter
@Setter
@EqualsAndHashCode
@ToString
@Entity
@Table(name = "order", schema = "purchase")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private int id;
@Basic
@CreationTimestamp
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "order_time")
private Date orderTime;
@OneToMany(mappedBy = "order", cascade = CascadeType.MERGE)
private List<ProductInOrder> productsInOrder;
@ManyToOne
@JoinColumn(name = "buyer")
private User buyer;
@OneToOne(mappedBy= "order")
private TheReturn theReturn;
@OneToOne
@JoinColumn(name = "payment_id")
@JsonIgnore
private Payment payment;
@OneToOne
@JoinColumn(name = "cart_id")
private Cart cart;
}
Payment entity:
@Getter
@Setter
@EqualsAndHashCode
@ToString
@Entity
@Table(name = "payment", schema = "purchase")
public class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private int id;
@Basic
@Column(name = "owner_name", nullable = true, length = 70)
private String ownerName;
@Basic
@Column(name = "owner_last_name", nullable = true, length = 70)
private String ownerLastName;
@Basic
@Column(name = "card_number", nullable = true, length = 16)
//il numero di una carta PayPal è di 16 cifre
private String cardNumber;
@Basic
@Temporal(TemporalType.DATE)
@Column(name = "expiration", nullable = true)
private Date expiration;
@Basic
@Column(name = "cvv", nullable = true, length = 3)
private String cvv;
@Basic
@Column(name = "total", nullable = true)
private float total;
@OneToOne(mappedBy = "payment")
private Order order;
@OneToOne(mappedBy = "payment")
private TheReturn theReturn;
}
OrderService:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ProductInOrderRepository productInOrderRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private CartRepository cartRepository;
@Autowired
private PaymentRepository paymentRepository;
@Autowired
private EntityManager entityManager;
@Transactional(readOnly = false)
public Order addOrder(Order order) throws ProductUnavailableException, EmptyOrderException, UserNotFoundException, CartNotFoundException, CartAlreadyExistsException{//
if(order.getProductsInOrder()==null || order.getProductsInOrder().size()<=0){
throw new EmptyOrderException();
}
if(!userRepository.existsById(order.getBuyer().getId())){
throw new UserNotFoundException();
}
if(!cartRepository.existsById(order.getCart().getId())){
throw new CartNotFoundException();
}
if(orderRepository.findByCart(order.getCart())!=null){
throw new CartAlreadyExistsException();
}
Order result = orderRepository.save(order);
for(ProductInOrder p : result.getProductsInOrder()){
p.setOrder(result);
ProductInOrder justAdded = productInOrderRepository.save(p);
entityManager.refresh(justAdded);
Product product = justAdded.getProduct();
int newQuantity = product.getQuantity() - p.getQuantity();
if(newQuantity < 0){
orderRepository.delete(order);
p.setOrder(null);
productInOrderRepository.delete(p);
throw new ProductUnavailableException(product);
}
product.setQuantity(newQuantity);
entityManager.refresh(p);
}
entityManager.refresh(result);
return result;
}
OrderController:
@RestController
@RequestMapping("/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/createOrder")
@ResponseStatus(code = HttpStatus.OK)
public ResponseEntity create(@RequestBody @Valid Order order){//Funziona
//System.out.println("Ordine in orderController: "+order+"\n");
try{
return new ResponseEntity<>(orderService.addOrder(order), HttpStatus.OK);
}catch (ProductUnavailableException e){
return new ResponseEntity<>(new ResponseMessage("The quantity of product: "+e.getProduct().getName()+" is unavailable!"), HttpStatus.BAD_REQUEST);
} catch (EmptyOrderException e) {
return new ResponseEntity<>(new ResponseMessage("The order has no products."), HttpStatus.BAD_REQUEST);
} catch (UserNotFoundException e) {
return new ResponseEntity<>(new ResponseMessage("User not found."), HttpStatus.BAD_REQUEST);
} catch (CartNotFoundException e) {
return new ResponseEntity<>(new ResponseMessage("Cart not found."), HttpStatus.BAD_REQUEST);
} catch (CartAlreadyExistsException e) {
return new ResponseEntity<>(new ResponseMessage("Cannot associate the same cart cart to more than one order."), HttpStatus.BAD_REQUEST);
}
}
PaymentService:
@Service
public class PaymentService {
@Autowired
private PaymentRepository paymentRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private EntityManager entityManager;
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public Payment addPayment(Payment p) throws PaymentAlreadyExistsException, IncorrectCardNumberException, IncorrectCvvException{//
if(paymentRepository.existsByCardNumber(p.getCardNumber())){
throw new PaymentAlreadyExistsException();
}
if(p.getCardNumber().length()!=16){
throw new IncorrectCardNumberException();
}
if(p.getCvv().length()!=3)
{
throw new IncorrectCvvException();
}
Order newOrder = new Order();
newOrder.setPayment(p);
Order justAdded=orderRepository.save(newOrder);
entityManager.refresh(justAdded);
p.setOrder(justAdded);
return paymentRepository.save(p);
}
PaymentController:
@RestController
@RequestMapping("/payments")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping("/createPayment")//funziona
public ResponseEntity<Payment> create(@RequestBody @Valid Payment payment){
System.out.print("Sono in paymentController.");
try {
Payment added=paymentService.addPayment(payment);
return new ResponseEntity<>(added, HttpStatus.OK);
} catch (PaymentAlreadyExistsException e) {
return new ResponseEntity(new ResponseMessage("Payment already exists!"), HttpStatus.BAD_REQUEST);
} catch (IncorrectCardNumberException e) {
return new ResponseEntity(new ResponseMessage("Incorrect card number!"), HttpStatus.BAD_REQUEST);
} catch (IncorrectCvvException e) {
return new ResponseEntity(new ResponseMessage("Incorrect CVV"), HttpStatus.BAD_REQUEST);
}
}
FRONT-END
payment.component.html (in this html file there is the click event I was talking about):
<form #paymentForm="ngForm">
<div class="form-group">
<label for="ownerName">OwnerName*</label>
<input type="text" ngModel name="ownerName" class="form-control" id="ownerName" placeholder="OwnerName" required>
</div>
<div class="form-group">
<label for="ownerLastName">OwnerLastName*</label>
<input type="text" ngModel name="ownerLastName" class="form-control" id="ownerLastName" placeholder="OwnerLastName" required>
</div>
<div class="form-group">
<label for="cardNumber">CardNumber*</label>
<input type="text" ngModel name="cardNumber" class="form-control" id="cardNumber" placeholder="CardNumber" required>
</div>
<div class="form-group">
<label for="cvv">Cvv*</label>
<input type="text" ngModel name="cvv" class="form-control" id="cvv" placeholder="cvv" required>
</div>
<div class="form-group">
<label for="expiration">Expiration*</label>
<input type="date" ngModel name="expiration" class="form-control" id="expiration" placeholder="expiration" required>
</div>
<p>(Tutti i campi contrassegnati con * devono essere riempiti)</p>
<div class="modal-footer">
<button [disabled]="paymentForm.invalid" type="submit" class="btn btn-primary" (click)="createPayment('Payment successful!', 'ok', paymentForm)" routerLink="/orders" routerLinkActive="active">Pay</button>
</div>
</form>
payment.component.ts:
export class PaymentComponent implements OnInit {
public total:number=0;
public payment!: Payment;
ownerNameFormControl = new FormControl('', [Validators.required]);
ownerLastNameFormControl = new FormControl('', [Validators.required]);
constructor(private _snackBar: MatSnackBar, private paymentService:PaymentService, private shared: SharedService) { }
ngOnInit(): void {
this.shared.castTotal.subscribe(total=>this.total=total);
this.shared.castPayment.subscribe(payment=>this.payment=payment);
}
public createPayment(message:string, action:string, createForm:NgForm ) {
this.paymentService.createPayment(createForm.value).subscribe(
(response: Payment) => {
this.payment=response;
this.shared.setPayment(this.payment);
console.log(response);
this._snackBar.open(message, action);
createForm.reset();
},
(error: HttpErrorResponse)=>{
alert(error.message);
createForm.reset();
}
)
}
}
payment.service.ts:
@Injectable({
providedIn: 'root'
})
export class PaymentService {
constructor(private http: HttpClient) { }
public createPayment(payment: Payment): Observable<Payment> {
console.log("Sono in payment service");
return this.http.post<Payment>('http://localhost:8080/payments/createPayment', payment);
}
shared.service.ts:
export class SharedService {
public defaultPayment!: Payment;
private payment= new BehaviorSubject<Payment>(this.defaultPayment);
castPayment = this.payment.asObservable();
constructor() { }
setPayment(data: Payment){
this.payment.next(data); }
order.component.ts:
export class OrderComponent implements OnInit {
public payment!: Payment;
public cart!: Cart;
displayedColumns = ['img', 'name', 'description', 'price', 'quantity'];
constructor(private shared: SharedService) { }
ngOnInit(): void {
this.shared.castPayment.subscribe(payment=>this.payment=payment);
this.shared.castCart.subscribe(cart=>this.cart=cart);
}
}
order.service.ts:
export class OrderService {
constructor(private http: HttpClient) { }
public createOrder(order: Order): Observable<void> {
return this.http.post<void>('http://localhost:8080/orders/createOrder', order);
}
payment.ts:
export interface Payment{
id: number;
ownerName: string;
ownerLastName: string;
cardNumber: string;
expiration: Date;
cvv: string;
total: number;
order: Order;
theReturn: TheReturn;
}