Overview
The PaypalService provides integration with PayPal’s checkout API for processing tour reservation payments. It handles OAuth2 authentication with PayPal and order creation.
Interface
public interface IPaypalService
Location: ~/workspace/source/AndanDo/Services/Paypal/PaypalService.cs
Configuration
The service requires PayPal configuration via PaypalOptions:
public class PaypalOptions
{
public string BaseUrl { get ; set ; } // PayPal API base URL
public string ClientId { get ; set ; } // PayPal client ID
public string ClientSecret { get ; set ; } // PayPal client secret
}
Environment URLs :
Sandbox: https://api-m.sandbox.paypal.com
Production: https://api-m.paypal.com
appsettings.json
Program.cs Registration
{
"Paypal" : {
"BaseUrl" : "https://api-m.sandbox.paypal.com" ,
"ClientId" : "your-client-id" ,
"ClientSecret" : "your-client-secret"
}
}
Methods
CreateOrderAsync
Creates a PayPal order for a tour reservation payment.
Task < PaypalOrderResult ?> CreateOrderAsync (
decimal amount ,
string currency ,
string returnUrl ,
string cancelUrl )
Payment amount (formatted to 2 decimal places)
Three-letter currency code (e.g., “USD”, “EUR”, “DOP”)
URL where PayPal redirects after successful payment approval
URL where PayPal redirects if user cancels the payment
PayPal order result or null if creation failed Show PaypalOrderResult properties
OrderId (string) - PayPal order ID
ApprovalUrl (string?) - URL to redirect user for payment approval
Basic Usage
With Reservation
var result = await paypalService . CreateOrderAsync (
amount : 150.00m ,
currency : "USD" ,
returnUrl : "https://yourdomain.com/payment/success" ,
cancelUrl : "https://yourdomain.com/payment/cancel"
);
if ( result != null && result . ApprovalUrl != null )
{
// Redirect user to PayPal for payment
navigationManager . NavigateTo ( result . ApprovalUrl , forceLoad : true );
}
else
{
Console . WriteLine ( "Failed to create PayPal order" );
}
Payment Flow
The typical payment flow involves multiple steps:
Create Reservation
Create a tour reservation with payment status = pending
Create PayPal Order
Call CreateOrderAsync with the reservation amount and return URLs
Store Order Reference
Save the PayPal order ID with the reservation
Redirect to PayPal
Redirect user to the approval URL for payment
Handle Return
User is redirected back to your return URL after approval or cancellation
Capture Payment
Call PayPal’s capture API to complete the transaction (not implemented in this service)
Update Status
Update reservation payment status based on capture result
Implementation Details
OAuth2 Authentication
The service uses OAuth2 client credentials flow to authenticate with PayPal:
private async Task < string ?> AcquireAccessTokenAsync ()
{
// Base64 encode credentials
var creds = Convert . ToBase64String (
Encoding . UTF8 . GetBytes ( $" { _options . ClientId } : { _options . ClientSecret } " )
);
// Request token
var request = new HttpRequestMessage ( HttpMethod . Post ,
$" { _options . BaseUrl } /v1/oauth2/token" );
request . Headers . Authorization = new AuthenticationHeaderValue ( "Basic" , creds );
request . Content = new StringContent (
"grant_type=client_credentials" ,
Encoding . UTF8 ,
"application/x-www-form-urlencoded"
);
// Parse response for access token
.. .
}
The access token is acquired fresh for each order creation. For production use, consider implementing token caching with expiration handling.
Order Creation Request
The service creates a PayPal order with the following structure:
{
"intent" : "CAPTURE" ,
"purchase_units" : [
{
"amount" : {
"currency_code" : "USD" ,
"value" : "150.00"
}
}
],
"application_context" : {
"return_url" : "https://yourdomain.com/payment/success" ,
"cancel_url" : "https://yourdomain.com/payment/cancel"
}
}
Intent : The service uses CAPTURE intent, which means the payment is captured immediately upon user approval. For authorization-only flows, you would use AUTHORIZE intent.
Error Handling
The service returns null if any error occurs during:
Token acquisition
Order creation
Network communication
Always check for null results and handle appropriately.
var paypalOrder = await paypalService . CreateOrderAsync (
amount : 150.00m ,
currency : "USD" ,
returnUrl : returnUrl ,
cancelUrl : cancelUrl
);
if ( paypalOrder == null )
{
// Log error
logger . LogError ( "Failed to create PayPal order for reservation {ReservationId}" ,
reservationId );
// Update reservation with error status
await tourService . UpdateReservationPaymentStatusAsync (
reservationId : reservationId ,
paymentStatus : 2 , // Failed
paymentProvider : null ,
paymentReference : null
);
// Show error to user
return "Payment service unavailable. Please try again." ;
}
if ( string . IsNullOrEmpty ( paypalOrder . ApprovalUrl ))
{
logger . LogError ( "PayPal order created but no approval URL returned" );
return "Payment configuration error. Please contact support." ;
}
// Success - proceed with redirect
navigationManager . NavigateTo ( paypalOrder . ApprovalUrl , forceLoad : true );
Currency Support
PayPal supports a wide range of currencies. Common currencies for tour marketplace:
USD - US Dollar
EUR - Euro
GBP - British Pound
CAD - Canadian Dollar
AUD - Australian Dollar
MXN - Mexican Peso
The Dominican Peso (DOP) is not directly supported by PayPal. For DOP transactions, consider:
Converting to USD before creating the order
Using a local payment provider
Offering dual pricing (DOP and USD)
Complete Payment Example
Here’s a complete Blazor component example:
public class PaymentHandlerModel : ComponentBase
{
[ Inject ] private IPaypalService PaypalService { get ; set ; } = default ! ;
[ Inject ] private ITourService TourService { get ; set ; } = default ! ;
[ Inject ] private NavigationManager Navigation { get ; set ; } = default ! ;
[ Inject ] private ILogger < PaymentHandlerModel > Logger { get ; set ; } = default ! ;
public async Task < bool > ProcessPaymentAsync (
TourReservationRequest reservation ,
IEnumerable < TourReservationTicketRequest > tickets )
{
try
{
// 1. Create reservation
var reservationId = await TourService . CreateReservationAsync (
reservation ,
tickets
);
Logger . LogInformation (
"Created reservation {ReservationId} for ${Amount}" ,
reservationId ,
reservation . TotalAmount
);
// 2. Build return URLs
var baseUrl = Navigation . BaseUri . TrimEnd ( '/' );
var returnUrl = $" { baseUrl } /payment/success?reservationId= { reservationId } " ;
var cancelUrl = $" { baseUrl } /payment/cancel?reservationId= { reservationId } " ;
// 3. Create PayPal order
var paypalOrder = await PaypalService . CreateOrderAsync (
amount : reservation . TotalAmount ,
currency : reservation . CurrencyCode ,
returnUrl : returnUrl ,
cancelUrl : cancelUrl
);
if ( paypalOrder == null || string . IsNullOrEmpty ( paypalOrder . ApprovalUrl ))
{
Logger . LogError ( "Failed to create PayPal order" );
return false ;
}
// 4. Update reservation with payment reference
await TourService . UpdateReservationPaymentStatusAsync (
reservationId : reservationId ,
paymentStatus : 0 , // Pending
paymentProvider : "PayPal" ,
paymentReference : paypalOrder . OrderId
);
Logger . LogInformation (
"PayPal order {OrderId} created for reservation {ReservationId}" ,
paypalOrder . OrderId ,
reservationId
);
// 5. Redirect to PayPal
Navigation . NavigateTo ( paypalOrder . ApprovalUrl , forceLoad : true );
return true ;
}
catch ( Exception ex )
{
Logger . LogError ( ex , "Error processing payment" );
return false ;
}
}
}
Testing
Sandbox Testing
Production
Use PayPal sandbox for development:
Create sandbox accounts at PayPal Developer Dashboard
Use sandbox credentials in configuration
Test with sandbox personal/business accounts
Verify transactions in sandbox dashboard
Sandbox URL : https://api-m.sandbox.paypal.comFor production deployment:
Create production app credentials
Update configuration with production URL and credentials
Implement webhook handling for payment notifications
Add proper error logging and monitoring
Test with small real transactions first
Production URL : https://api-m.paypal.com
Security Considerations
Credential Protection :
Never commit credentials to source control
Use Azure Key Vault, AWS Secrets Manager, or similar for production
Rotate credentials regularly
Use different credentials for sandbox and production
HTTPS Required : PayPal requires HTTPS for all return URLs in production. Ensure your application uses SSL certificates.
Webhook Verification : For production, implement webhook handlers to receive payment status updates from PayPal. This service only handles order creation - capturing and webhook handling should be implemented separately.
Next Steps
To complete PayPal integration, you’ll need to implement:
Order Capture : Call PayPal’s capture endpoint after user approval
Webhook Handler : Receive and process payment notifications
Refund Support : Handle refund requests
Order Inquiry : Query PayPal for order status
Error Recovery : Handle failed captures and timeouts
Refer to PayPal’s API documentation for implementation details.