0

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

enter image description here

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;

}
  • 2
    Putting so much information into a question is not good. – K.Nicholas Feb 05 '22 at 16:39
  • I did it to explain my problem in a better way. –  Feb 05 '22 at 16:46
  • 1
    Understood, but the question is like "Here is my entire application, what do I do?" Better for you to think about it some more and ask specific questions on stack overflow. – K.Nicholas Feb 05 '22 at 17:07
  • Maybe I wrote the question in a wrong way and so it could be misunderstood... My question is if there is a way to create a Spring boot object from the Angular front-end and I thought that writing down my code would have been a good idea.. Honestly I don't know how to explain my question in another way @K.Nicholas –  Feb 05 '22 at 17:13
  • Your frontend has to send a request containing payment and order... – Elmar Brauch Feb 05 '22 at 21:22
  • So I should create a Payment object and an Order object in the front-end in the following way? `Payment payment=new Payment()` and `Order order=new Order()`. Then I should wrap them into a request? –  Feb 05 '22 at 23:01

1 Answers1

0

Sending a request from FE to BE has essentially 2 steps:

  1. FE sends request to backend:

As you can see in this example from the Angular documentation:

/** POST: add a new hero to the database */
addHero(hero: Hero): Observable<Hero> {
  return this.http.post<Hero>(`http://localhost:portNumber/hero`, hero, httpOptions)
    .pipe(
      catchError(this.handleError('addHero', hero))
    );
}

You just send the object that has all the fields you need to the backend. When it is sent - it is sent as JSON which means if the Hero class has fields name and power it will be sent like this:

{
  "name": "Superman",
  "power": "Can disguise himself by wearing glasses"
}
  1. Consume it on the backend side:

In Spring Controller you just need to define a controller method that expects an object with the same fields. Spring has a dependency of Jackson which automatically will deserialize JSON for you, so as I said you just need to create a DTO that has all the fields you need.

class HeroDTO {
  private String name; 
  private String power;

  //getters, setters and noArg constructor - important to have for deserialization to work!
}

and then in controller method:

@PostMapping(value = "/hero", produces = APPLICATION_JSON_VALUE)
public ResponseEntity createHero(@RequestBody HeroDTO heroDTO) {
    //do whatever
}

btw you might get CORS errors - that is normal and CORS can be disabled by adding @CrossOrigin to the top of your Controller. More here

J Asgarov
  • 2,526
  • 1
  • 8
  • 18