Integrating Paypal payment in Springboot backend— full guide with all the steps — charge and payout

Khouloud Khezami
6 min readSep 22, 2020

Hi everyone.

It became almost rare that a software engineer does not integrate a payment system in his/her backend at least once in his/her lifetime, therefore I decided to share what I learned in this article and explain how to implement your own payment system successfully.

So, let’s get started. There are many payment API and SDKs that could be used such as Stripe, Paypal, Noodlio Pay, Square API, and the list goes on.

Now in this tutorial, I will be focusing on integrating Paypal to your spring boot app.

The first step for you is to visit the PayPal developer website https://developer.paypal.com/developer/accounts/ and login to the dashboard (the button should be on the upper right of your screen :D)

Once logged in, click the “My apps & Credentials” (should be the first option on the menu on the left) this will take you to the following page.

Here, I have already created an app for myself, go ahead, and create one on your own. As soon as you do that, click on your app and this will lead you to this page:

what I highlighted in the picture is very important the Client Id and the Secret, these will be used in our app later.

Note: click on the show button to get the Secret :D

Now let’s go to our IDE, create a new project and add the following to your pom file

<dependency>
<groupId>com.paypal.sdk</groupId>
<artifactId>rest-api-sdk</artifactId>
<version>LATEST</version>
</dependency>

Now go to your application.properties file under the resources folder and paste the following:

paypal.mode=sandbox
paypal.client.id=<YOUR_CLIENT_ID>
paypal.client.secret=<YOUR_SECRET>

It is important to keep those keys safe and not to push them to public repositories.

Once you are ready, go ahead and create this folder structure:

Let’s start by configuring PayPal.

First of all, don’t forget to annotate your class with the configuration annotation:

@Configuration
public class PayPalConfig {
}

we need to extract our clientId and secret from the application.properties using the @ Value annotation as follow:

@Value("${paypal.client.id}")
private String clientId;
@Value("${paypal.client.secret}")
private String clientSecret;
@Value("${paypal.mode}")
private String mode;

This will automatically map to the values in the application.properties file.

Now let’s create our beans that will be later used in our service

@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;
}

Once we are done let’s move to our actual service. Do not forget to annotate your class with the Service annotation.

Remember the APIContext bean we have just created? We will use it now, so we need to autowire it:

@Autowired
private APIContext apiContext;

We will also need the following variables:

public static final String SUCCESS_URL = "YOUR_SUCCESS_URL";
public static final String CANCEL_URL = "YOUR_CANCEL_URL";
private static final String INTENT = "sale";
private static final String METHOD = "paypal";

cancelUrl: the URL where the payer after he/she cancels the payment.

successUrl: the URL where the payer after the payment is approved.

method: is an enum, the possible values are: credit_card, paypal, pay_upon_invoice, carrier, alternate_payment, bank

intent : is an enum and could have either of these values : sale -> makes an immediate payment, authorize -> authorizes a payment for capture later Or order -> creates an order.

(you can check these parameters more in the PayPal documentation https://developer.paypal.com/docs/api/payments/v1/)

In order to charge users, we should create the following method in our service.

public Payment createPayment(
Double total,
String currency,
String method,
String intent,
String description) 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(CANCEL_URL);
redirectUrls.setReturnUrl(SUCCESS_URL);
payment.setRedirectUrls(redirectUrls);

return payment.create(apiContext);
}

Let’s go through the parameters:

total is the amount you will charge your customer.

currency refers to the currency you will charge with

description: the name is self-explanatory.

The code in itself is self-explanatory as well, the only part worth highlighting is:

amount.setTotal(String.format("%.2f", total));

The PayPal API accepts only a double with at most two decimal numbers.

After creating a payment we should be able to execute it. The following method has that responsibility.

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);
}

Now what we need to do is creating the RestController that would call our service and execute these methods.

@RestController
public class PayPalPaymentController {

@Autowired
private PayPalPaymentService service;

@RequestMapping(value = "/pay", method = RequestMethod.POST)
public String payment(@RequestBody Order order) {
try {
Payment payment = service.createPayment(order.getPrice(), order.getCurrency(), order.getDescription());
for(Links link:payment.getLinks()) {
if(link.getRel().equals("approval_url")) {
return "redirect:"+link.getHref();
}
}

} catch (PayPalRESTException e) {

e.printStackTrace();
}
return "redirect:/";
}

@RequestMapping(value = CANCEL_URL, method = RequestMethod.GET)
public String cancelPay() {
return "cancel";
}

@RequestMapping(value = SUCCESS_URL, method = RequestMethod.GET)
public String successPay(@RequestParam("paymentId") String paymentId, @RequestParam("PayerID") String payerId) {
try {
Payment payment = service.executePayment(paymentId, payerId);
if (payment.getState().equals("approved")) {
return "success";
}
} catch (PayPalRESTException e) {
}
return "redirect:/";
}
}

Noting that the Order class is under model folder and has these attributes:

@Getter
@ToString
@Builder
public class Order {
private double price;
private String currency;
private String description;
}

Once we are all set up with charging customers, let’s move to paying out customers.

Going back to the service we add this method.

public PayoutBatch payout(double total, String currency, String receiverEmail) throws PayPalRESTException {
Date currentDate = new Date(System.currentTimeMillis());
PayoutSenderBatchHeader payoutSenderBatchHeader = new PayoutSenderBatchHeader();
payoutSenderBatchHeader.setSenderBatchId(SENDER_BATCH + " " + currentDate.toString());
payoutSenderBatchHeader.setEmailSubject(EMAIL_SUBJECT);
payoutSenderBatchHeader.setRecipientType(RECIPIENT_TYPE);
List<PayoutItem> payoutItems = new ArrayList<>();

payoutItems.add(new PayoutItem(new Currency(currency, String.format("%.2f", total)), receiverEmail));
Payout payout = new Payout();

payout.setSenderBatchHeader(payoutSenderBatchHeader);
payout.setItems(payoutItems);

return payout.create(apiContext, null);
}

With this method, we create a PayoutBatch. We also can add as many payoutItem as you want (less than 15.000 to be clear)

Going back to the rest controller, we just have to execute the service method

@RequestMapping(value = "/payout", method = RequestMethod.POST)
public PayoutBatch payout(@RequestBody PayoutOrder payoutOrder) {
try {
return service.payout(payoutOrder.getTotalAmount(), payoutOrder.getCurrency(),
payoutOrder.getReceiver());
} catch (PayPalRESTException e) {
e.printStackTrace();
}
return null;
}

AND VOILA YOU ARE DONE.

Some tips:

You can create as many sandbox accounts as you need and you can experiment with them.

If one of your sandbox users is running out of money, click on the three dots the edit profile, you will have a tab, click on fundings, click on edit at the end of the popup and change the balance of the account ;)

To access you sandbox accounts, use the following link https://www.sandbox.paypal.com/hu/signin

if you have any questions, you can send me an email on this address: khouloud.social.media@gmail.com

Here is the github repo as well: https://github.com/whiteKhoukha/paypaldemo

Peace.

--

--