I'm trying to implement PayPal's API, but as this is the first time im using it, I get some errors.
This is the code:
Controller
#PostMapping("/checkout/paypal")
public String checkout_paypal(#RequestParam(value = "id") Integer id) throws Exception {
Order order = orderServices.findOrderById(id);
try {
Payment payment = service.createPayment(order.getTotalPrice().doubleValue(), "EUR", "PAYPAL",
"ORDER", "Order id:"+order.getId(), "http://localhost:4200/checkout?id="+order.getId(),
"http://localhost:4200/checkout?id="+order.getId());
for(Links link:payment.getLinks()) {
if(link.getRel().equals("approval_url")) {
return "redirect:"+link.getHref();
}
}
} catch (PayPalRESTException e) {
e.printStackTrace();
}
System.out.println("Success");
return "Success";
}
#GetMapping("/successPaymentPaypal")
public String successPayment(#RequestParam(value = "id") Integer id,#RequestParam(value = "paymentId") String paymentId,#RequestParam(value = "PayerID") String PayerID) throws Exception {
System.out.println(id+" "+paymentId+" "+PayerID);
try {
Payment payment = service.executePayment(paymentId, PayerID);
if(payment.getState().equals("approved")){
Order order = orderServices.findOrderById(id);
order.setOrderState(OrderState.PAID);
orderServices.saveOrder(order);
return "success";
}
} catch (PayPalRESTException e) {
throw new Exception("Error occured while processing payment!");
}
return "Done";
}
Service
#Autowired
private APIContext apiContext;
public Payment createPayment(
Double total,
String currency,
String method,
String intent,
String description,
String cancelUrl,
String successUrl) throws PayPalRESTException {
Amount amount = new Amount();
amount.setCurrency(currency);
total = new BigDecimal(total).setScale(2, RoundingMode.HALF_UP).doubleValue();
amount.setTotal(String.format("%.2f", total));
Transaction transaction = new Transaction();
transaction.setDescription(description);
transaction.setAmount(amount);
List<Transaction> transactions = new ArrayList<>();
transactions.add(transaction);
Payer payer = new Payer();
payer.setPaymentMethod(method.toString());
Payment payment = new Payment();
payment.setIntent(intent.toString());
payment.setPayer(payer);
payment.setTransactions(transactions);
RedirectUrls redirectUrls = new RedirectUrls();
redirectUrls.setCancelUrl(cancelUrl);
redirectUrls.setReturnUrl(successUrl);
payment.setRedirectUrls(redirectUrls);
return payment.create(apiContext);
}
public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException{
Payment payment = new Payment();
payment.setId(paymentId);
PaymentExecution paymentExecute = new PaymentExecution();
paymentExecute.setPayerId(payerId);
return payment.execute(apiContext, paymentExecute);
}
Config
#Value("${paypal.client.id}")
private String clientId;
#Value("${paypal.mode}")
private String paypalMode;
#Value("${paypal.client.secret}")
private String clientSecret;
#Bean
public Map<String, String> paypalSdkConfig(){
Map<String, String> sdkConfig = new HashMap<>();
sdkConfig.put("mode", paypalMode);
return sdkConfig;
}
#Bean
public OAuthTokenCredential authTokenCredential(){
return new OAuthTokenCredential(clientId, clientSecret, paypalSdkConfig());
}
#Bean
public APIContext apiContext() throws PayPalRESTException{
APIContext apiContext = new APIContext(authTokenCredential().getAccessToken());
apiContext.setConfigurationMap(paypalSdkConfig());
return apiContext;
}
This is the error:
Response code: 404 Error response: {"name":"INVALID_RESOURCE_ID","message":"Requested resource ID was not found.","information_link":"https://developer.paypal.com/docs/api/payments/#errors","debug_id":"c2dc1e86af7fe"}
The checkout/paypal dont give any error, and works well i think, it got me redirect to my frontend, where i make second request, but there is when error comes..
I really dont know what is the problem..
It appears you're integrating the old v1/payments API. You should instead integrate using the current v2/checkout/orders.
See the guide at https://developer.paypal.com/docs/checkout/reference/server-integration/
The best approval flow to pair it with is https://developer.paypal.com/demo/checkout/#/pattern/server
With v2/checkout/orders, always use intent:capture unless you have very specific and well-defined business reasons to add an intervening authorization step
Related
I am trying to unit test a put request which takes a file and some json data as request body. following is the method i am trying to test:
#RequestMapping(
value = "/{id}",
method = RequestMethod.PUT,
produces = { "application/json" }
)
public ResponseEntity<UpdateT1Output> update(#PathVariable String id, #ModelAttribute #Valid UpdateT1Input t1) {
// implementation here
}
UpdateT1Input.java
public class UpdateT1Input {
private char[] ca;
private byte[] file;
public void setFile(MultipartFile mpfile) {
try {
file = mpfile.getBytes();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private List<Double> flpa;
private List<Double> fpa;
#NotNull(message = "id Should not be null")
private Long id;
private String str;
private Long versiono;
}
test setup
#Test
public void UpdateT1_T1Exists_ReturnStatusOk() throws Exception {
// create entity obj with default values
T1Entity entity = createUpdateEntity();
entity.setVersiono(0L);
UpdateT1Input t1Input = new UpdateT1Input();
t1Input.setId(entity.getId());
t1Input.setFlpa(entity.getFlpa());
t1Input.setStr(entity.getStr());
ObjectWriter ow = new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.writer()
.withDefaultPrettyPrinter();
String json = ow.writeValueAsString(t1Input);
MockMultipartHttpServletRequestBuilder builder =
MockMvcRequestBuilders.multipart("/t1/" + entity.getId());
builder.with(request -> {
request.setMethod("PUT");
return request;
});
mvc.perform(builder
.file("file", "ABC".getBytes("UTF-8"))
.content(json)
.contentType(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk());
}
but in controller only id and file fields are set in input dto all other fields are null. i am using #ModelAttribute to avoid dividing request into file and data parts. so is there a way that to get all the fields in single object?
response-code: 400 details: name: VALIDATION_ERROR message: Invalid request - see details details: [{
"field": "transactions.amount",
"issue": "Cannot construct instance of com.paypal.platform.payments.model.rest.common.Amount, >problem: INVALID_CURRENCY_AMOUNT_FORMAT"
}] debug-id: 86ad5783892c3 information-link: https://developer.paypal.com/docs/api/payments/#errors
package com.spring.soap.api;
#Configuration
public class PaypalConfig {
#Value("${paypal.client.id}")
private String clientId;
#Value("${paypal.client.secret}")
private String clientSecret;
#Value("${paypal.mode}")
private String mode;
#Bean
public Map<String,String> paypalSdkConfig(){
Map<String,String> configMap= new HashMap<>();
configMap.put("mode",mode);
return configMap;
}
#Bean
public OAuthTokenCredential oAuthTokenCredential() {
return new OAuthTokenCredential(clientId,clientSecret,paypalSdkConfig());
}
#Bean
public APIContext apiContext() throws PayPalRESTException {
APIContext context = new APIContext(oAuthTokenCredential().getAccessToken());
context.setConfigurationMap(paypalSdkConfig());
return context;
}
}
{
#Autowired
PaypalService service;
public static final String SUCCESS_URL = "pay/success";
public static final String CANCEL_URL = "pay/cancel";
#GetMapping("/")
public String home() {
return "home";
}
#PostMapping("/pay")
public String payment(#ModelAttribute("order") Order order) {
try {
Payment payment = service.createPayment(order.getPrice(), order.getCurrency(), order.getMethod(),
order.getIntent(), order.getDescription(), "http://localhost:9090/" + CANCEL_URL,
"http://localhost:9090/" + SUCCESS_URL);
for(Links link:payment.getLinks()) {
if(link.getRel().equals("approval_url")) {
return "redirect:"+link.getHref();
}
}
} catch (PayPalRESTException e) {
e.printStackTrace();
}
return "redirect:/";
}
#GetMapping(value = CANCEL_URL)
public String cancelPay() {
return "cancel";
}
#GetMapping(value = SUCCESS_URL)
public String successPay(#RequestParam("paymentId") String paymentId, #RequestParam("PayerID") String payerId) {
try {
Payment payment = service.executePayment(paymentId, payerId);
System.out.println(payment.toJSON());
if (payment.getState().equals("approved")) {
return "success";
}
} catch (PayPalRESTException e) {
System.out.println(e.getMessage());
}
return "redirect:/";
}
}
{
#Autowired
private APIContext apiContext;
public Payment createPayment(
Double total,
String currency,
String method,
String intent,
String description,
String cancelUrl,
String successUrl) throws PayPalRESTException{
Amount amount = new Amount();
amount.setCurrency(currency);
total = new BigDecimal(total).setScale(2, RoundingMode.HALF_UP).doubleValue();
amount.setTotal(String.format("%.2f", total));
Transaction transaction = new Transaction();
transaction.setDescription(description);
transaction.setAmount(amount);
List<Transaction> transactions = new ArrayList<>();
transactions.add(transaction);
Payer payer = new Payer();
payer.setPaymentMethod(method);
Payment payment = new Payment();
payment.setIntent(intent);
payment.setPayer(payer);
payment.setTransactions(transactions);
RedirectUrls redirectUrls = new RedirectUrls();
redirectUrls.setCancelUrl(cancelUrl);
redirectUrls.setReturnUrl(successUrl);
payment.setRedirectUrls(redirectUrls);
return payment.create(apiContext);
}
public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException{
Payment payment = new Payment();
payment.setId(paymentId);
PaymentExecution paymentExecute = new PaymentExecution();
paymentExecute.setPayerId(payerId);
return payment.execute(apiContext, paymentExecute);
}
}
It would appear your locale is formatting decimals with a comma (,) as the decimal separator.
The PayPal API exclusively accepts numbers with a period (.) as the decimal separator
Take this line:
amount.setTotal(String.format("%.2f", total));
Change %.2f to %.3f. The final code should look like:
amount.setTotal(String.format("%.3f", total));
In my case I was sending the SubTotal on Details with a NON rounded value:
141.750
So I just round the value like this:
details.setSubtotal(subTotal.setScale(2, BigDecimal.ROUND_HALF_EVEN).toString());
(In other words)
141.75
I Want to Design a Demo Twitter Clone Application where user can follow any other user . however i am doubting my rest api design . please suggest me am i right .
Can I pass followerId in url rather than passing it as requestbody as we already know followerId in Advance and server does not create followerId here ?
and if better option could be there like put/patch or any rest api design ?
Please suggest me better design if possible
Here JwtUser is Authenticated User
public class FollowerDto {
private Long followerId;
private boolean following;
public FollowerDto() {
}
public FollowerDto(Long followerId, boolean following) {
this.followerId = followerId;
this.following = following;
}
public boolean getFollowing() {
return following;
}
public void setFollowing(boolean following) {
this.following = following;
}
public Long getFollowerId() {
return followerId;
}
public void setFollowerId(Long followerId) {
this.followerId = followerId;
}
}
#PostMapping("/follower")
#ResponseStatus(HttpStatus.CREATED)
public StatusDto addFollower(#RequestBody #Valid final FollowerDto
followerDto, #CurrentUser final JwtUser user, final
HttpServletResponse response) {
RestPreconditions.checkRequestElementNotNull(followerDto);
RestPreconditions.checkArgumentCondition(followerDto.getFollowing());
return userService.addFollower(user, followerDto.getFollowerId(),
response);
}
// Service Layer
#Override
public StatusDto addFollower(final JwtUser jwtUser, final Long followerId, final HttpServletResponse response) {
final User follower = userRepository.findById(followerId).orElse(null);
ServicePreconditions.checkEntityExists(follower, "Follower does not exist with id " + followerId);
final User currentUser = userRepository.findByEmail(jwtUser.getEmail());
if (currentUser != null) {
ServicePreconditions.checkOKArgument(!currentUser.equals(follower));
final Set<User> existingFollowers = currentUser.getFollowers();
if (existingFollowers != null) {
existingFollowers.add(follower);
} else {
currentUser.setFollowers(Sets.<User>newHashSet(follower));
}
userRepository.save(currentUser);
final URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri().path("/{idOfNewResource}").buildAndExpand(follower.getId()).toUri();
response.setHeader(HttpHeaders.LOCATION, uri.toASCIIString());
return new StatusDto("Follower Added Successfully to user having email " + jwtUser.getEmail());
}
return new StatusDto("Follower is not Added to user with email " + jwtUser.getEmail());
}
My POJO:
public class Product {
String name;
int price;
public Product(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
My Controller:
#RestController
#RequestMapping("/rest")
public class RstController {
#RequestMapping(value = "/getProductPost2", method=RequestMethod.POST, produces = {"application/json"})
public Product getProductPost2(#RequestBody Product p){
System.out.println(p.getName());
return p;
}
My Client, using JUnit(I've tried all the lines commented, the same error everytime):
public class RestTemplatePostTest {
#Test
public void testee(){
String url = "http://localhost:8443/ShoppingCartSpringMVCSpringDataHibernate/rest/getProductPost2";
Product p = new Product();
p.setName("produs");
p.setPrice(22);
String json = "{\"name\":\"pen\",\"price\":10}";
HttpEntity<Product> request = new HttpEntity<Product>(p);
RestTemplate rt = new RestTemplate();
rt.postForObject(url, json, String.class);
//rt.postForObject(url, p, String.class);
//rt.exchange(url, HttpMethod.POST, request, Product.class);
//rt.exchange(url, HttpMethod.POST, request, String.class);
//assertEquals("produs",p2.getName());
}
}
When I'm using POSTMAN, it works.
I haven't found any other option on the internet and it's been 2 days since I'm trying to do this . Any opinion or tutorial is more than welcomed.
Edit1: Also, a simple GET also doesn't work,but it works in browser or in POSTMAN.
#RequestMapping(value = "/getProductGet", method=RequestMethod.GET)
public String getProductGet{
return "hello World!!";
}
#Test
public void testGet(){
String url = "http://localhost:8443/ShoppingCartSpringMVCSpringDataHibernate/rest/getProductGet";
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response= rt.getForEntity(url, String.class);
}
When in debug mode, it stops in my breakpoint when called by browser or POSTMAN, but it doesn't get there when using rest template.
Perhaps something to do with the fact that I'm using Spring security?
I think the problem is that your request has a wrong data type which server can not parse and thus can not reply.
Since you are sending a POST request with JSON Content-Type, your Product must be JSON-encoded.
To do that, you need to add a json converter so modify your code to some thing like this:
RestTemplate rt= new RestTemplate();
rt.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
Product recievedProduct = rt.postForObject(url, p, Product.class);
Don't forget to add the jackson dependency.
This is what I am trying to achieve:
I have an update request object and user is allowed to do Partial Updates. But I want to validate the field only if it is in the request body. Otherwise, it is OK to be null. To achieve this, I am using GroupSequenceProvider to let the Validator know what groups to validate. What am I doing wrong here? If there is a blunder, how do I fix it?
Documentation: https://docs.jboss.org/hibernate/validator/5.1/reference/en-US/html/chapter-groups.html#example-implementing-using-default-group-sequence-provider
#GroupSequenceProvider(UpdateUserRegistrationGroupSequenceProvider.class)
public class UpdateUserRegistrationRequestV1 {
#NotBlank(groups = {EmailExistsInRequest.class})
#Email(groups = {EmailExistsInRequest.class})
#SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, groups = {EmailExistsInRequest.class})
private String email;
#NotNull(groups = {PasswordExistsInRequest.class})
#Size(min = 8, max = 255, groups = {PasswordExistsInRequest.class})
private String password;
#NotNull(groups = {FirstNameExistsInRequest.class})
#Size(max = 255, groups = {FirstNameExistsInRequest.class})
#SafeHtml(whitelistType = SafeHtml.WhiteListType.NONE, groups = {FirstNameExistsInRequest.class})
private String firstName;
// THERE ARE GETTERS AND SETTERS BELOW
}
Group Sequence Provider Code:
public class UpdateUserRegistrationGroupSequenceProvider implements DefaultGroupSequenceProvider<UpdateUserRegistrationRequestV1> {
public interface EmailExistsInRequest {}
public interface PasswordExistsInRequest {}
public interface FirstNameExistsInRequest {}
#Override
public List<Class<?>> getValidationGroups(UpdateUserRegistrationRequestV1 updateUserRegistrationRequestV1) {
List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();
defaultGroupSequence.add(Default.class);
defaultGroupSequence.add(UpdateUserRegistrationRequestV1.class);
if(StringUtils.hasText(updateUserRegistrationRequestV1.getEmail())) {
defaultGroupSequence.add(EmailExistsInRequest.class);
}
if(StringUtils.hasText(updateUserRegistrationRequestV1.getPassword())) {
defaultGroupSequence.add(PasswordExistsInRequest.class);
}
if(StringUtils.hasText(updateUserRegistrationRequestV1.getFirstName())) {
defaultGroupSequence.add(FirstNameExistsInRequest.class);
}
return defaultGroupSequence;
}
}
I am using Spring MVC, so this is how my controller method looks,
#RequestMapping(value = "/{userId}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void updateUser(#PathVariable("userId") Long userId,
#RequestBody #Valid UpdateUserRegistrationRequestV1 request) {
logger.info("Received update request = " + request + " for userId = " + userId);
registrationService.updateUser(userId, conversionService.convert(request, User.class));
}
Now the problem is, the parameter "updateUserRegistrationRequestV1" in the UpdateUserRegistrationGroupSequenceProvider.getValidationGroups method is null. This is the request object that I am sending in the request body and I am sending email field with it.
What am I doing wrong?
I too went through the same issue ,and hopefully solved it
You just have to check the object is null and put all your conditions inside it.
public List<Class<?>> getValidationGroups(Employee object) {
List<Class<?>> sequence = new ArrayList<>();
//first check if the object is null
if(object != null ){
if (!object.isDraft()) {
sequence.add(Second.class);
}
}
// Apply all validation rules from default group
sequence.add(Employee.class);
return sequence;
}