> For the complete documentation index, see [llms.txt](https://developer.paywall.one/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.paywall.one/payment-orchestration-integration-document/example-codes/bulk-payment-callback-method.md).

# Bulk Payment Callback Method

{% tabs %}
{% tab title="C#" %}

```csharp
using System.IO.Compression;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

namespace PaywallBulkPaymentCalback;
[ApiController]
[Route("api/[controller]")]
public class WebhookController : ControllerBase
{
    private readonly ILogger<WebhookController> _logger;

    public WebhookController(ILogger<WebhookController> logger)
    {
        _logger = logger;
    }

    public class FileUploadRequest
    {
        public int Type { get; set; }
        public int BulkPaymentId { get; set; }
        public int FileRecordId { get; set; }
        public required string FileName { get; set; }
        public long FileSize { get; set; }
        public int LineCount { get; set; }
        public required string FileFormat { get; set; }
        public bool IsCompressed { get; set; }
        public required string ErrorMessage { get; set; }
        public DateTime DateTime { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime FinishedAt { get; set; }
        public double DurationTime { get; set; }
        public required string Hash { get; set; }
        public required string HashFormat { get; set; }
        public short? HashKeyType { get; set; }
        public string? MerchantUniqueCode { get; set; }
    }

    [HttpPost("receive")]
    public async Task<IActionResult> ReceiveFileWithData()
    {
        try
        {
            if (!Request.HasFormContentType)
            {
                return BadRequest("Multipart form data expected");
            }

            var form = await Request.ReadFormAsync();

            // JSON data'yı al
            FileUploadRequest? uploadRequest = null;
            if (form.ContainsKey("data"))
            {
                var jsonData = form["data"].ToString();
                uploadRequest = JsonConvert.DeserializeObject<FileUploadRequest>(jsonData);
                _logger.LogInformation($"Received data: {jsonData}");
            }

            // Dosyayı al
            var file = form.Files.GetFile("file");
            if (file == null || file.Length == 0)
            {
                return BadRequest("No file received");
            }

            byte[] fileBytes;
            string originalFileName = file.FileName;
            long originalSize = file.Length;
            long processedSize;

            // Dosyayı işle - compressed ise decompress et
            if (uploadRequest?.IsCompressed == true)
            {
                _logger.LogInformation($"Decompressing file: {file.FileName}");
                
                using var compressedStream = file.OpenReadStream();
                using var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress);
                using var decompressedStream = new MemoryStream();
                
                await gzipStream.CopyToAsync(decompressedStream);
                fileBytes = decompressedStream.ToArray();
                processedSize = fileBytes.Length;
                
                _logger.LogInformation($"File decompressed: {originalSize} bytes -> {processedSize} bytes");
            }
            else
            {
                using var memoryStream = new MemoryStream();
                await file.CopyToAsync(memoryStream);
                fileBytes = memoryStream.ToArray();
                processedSize = fileBytes.Length;
            }

            // Dosyayı kaydet
            var savedFilePath = await SaveFile(originalFileName, fileBytes);

            // Response hazırla
            var response = new
            {
                message = "File and data received successfully",
                uploadRequest,
                file = new
                {
                    originalName = originalFileName,
                    originalSize = originalSize,
                    processedSize = processedSize,
                    contentType = file.ContentType,
                    savedPath = savedFilePath,
                    wasCompressed = uploadRequest?.IsCompressed == true
                },
                processedAt = DateTime.UtcNow
            };

            _logger.LogInformation($"File processed: {originalFileName} (Original: {originalSize} bytes, Processed: {processedSize} bytes)");
                
            return Ok(response);
        }
        catch (InvalidDataException ex)
        {
            _logger.LogError(ex, "Invalid compressed data format");
            return BadRequest("Invalid compressed file format");
        }
        catch (Newtonsoft.Json.JsonException ex)
        {
            _logger.LogError(ex, "Invalid JSON in data field");
            return BadRequest("Invalid JSON format in data field");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing upload request");
            return StatusCode(500, "Upload processing failed");
        }
    }

    private async Task<string> SaveFile(string originalFileName, byte[] fileBytes)
    {
        var uploadsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "uploads");

        if (!Directory.Exists(uploadsDirectory))
        {
            Directory.CreateDirectory(uploadsDirectory);
        }

        var fileName = $"{DateTime.UtcNow:yyyyMMdd_HHmmss}_{Path.GetFileName(originalFileName)}";
        var filePath = Path.Combine(uploadsDirectory, fileName);

        await System.IO.File.WriteAllBytesAsync(filePath, fileBytes);

        return filePath;
    }
}
```

{% endtab %}

{% tab title="Java" %}

```java
package com.paywallbulkpaymentcallback.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;

@RestController
@RequestMapping("/api/webhook")
public class WebhookController {
    
    private static final Logger logger = LoggerFactory.getLogger(WebhookController.class);
    private final ObjectMapper objectMapper = new ObjectMapper();

    public static class FileUploadRequest {
        private int type;
        private int bulkPaymentId;
        private int fileRecordId;
        private String fileName;
        private long fileSize;
        private int lineCount;
        private String fileFormat;
        private boolean isCompressed;
        private String errorMessage;
        private LocalDateTime dateTime;
        private LocalDateTime createdAt;
        private LocalDateTime finishedAt;
        private double durationTime;
        private String hash;
        private String hashFormat;
        private Short hashKeyType;
        private String merchantUniqueCode;

        // Getters and Setters
        public int getType() { return type; }
        public void setType(int type) { this.type = type; }

        public int getBulkPaymentId() { return bulkPaymentId; }
        public void setBulkPaymentId(int bulkPaymentId) { this.bulkPaymentId = bulkPaymentId; }

        public int getFileRecordId() { return fileRecordId; }
        public void setFileRecordId(int fileRecordId) { this.fileRecordId = fileRecordId; }

        public String getFileName() { return fileName; }
        public void setFileName(String fileName) { this.fileName = fileName; }

        public long getFileSize() { return fileSize; }
        public void setFileSize(long fileSize) { this.fileSize = fileSize; }

        public int getLineCount() { return lineCount; }
        public void setLineCount(int lineCount) { this.lineCount = lineCount; }

        public String getFileFormat() { return fileFormat; }
        public void setFileFormat(String fileFormat) { this.fileFormat = fileFormat; }

        public boolean isCompressed() { return isCompressed; }
        public void setCompressed(boolean compressed) { isCompressed = compressed; }

        public String getErrorMessage() { return errorMessage; }
        public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }

        public LocalDateTime getDateTime() { return dateTime; }
        public void setDateTime(LocalDateTime dateTime) { this.dateTime = dateTime; }

        public LocalDateTime getCreatedAt() { return createdAt; }
        public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }

        public LocalDateTime getFinishedAt() { return finishedAt; }
        public void setFinishedAt(LocalDateTime finishedAt) { this.finishedAt = finishedAt; }

        public double getDurationTime() { return durationTime; }
        public void setDurationTime(double durationTime) { this.durationTime = durationTime; }

        public String getHash() { return hash; }
        public void setHash(String hash) { this.hash = hash; }

        public String getHashFormat() { return hashFormat; }
        public void setHashFormat(String hashFormat) { this.hashFormat = hashFormat; }

        public Short getHashKeyType() { return hashKeyType; }
        public void setHashKeyType(Short hashKeyType) { this.hashKeyType = hashKeyType; }

        public String getMerchantUniqueCode() { return merchantUniqueCode; }
        public void setMerchantUniqueCode(String merchantUniqueCode) { this.merchantUniqueCode = merchantUniqueCode; }
    }

    @PostMapping("/receive")
    public ResponseEntity<?> receiveFileWithData(
            @RequestParam(value = "data", required = false) String jsonData,
            @RequestParam(value = "file", required = false) MultipartFile file) {
        
        try {
            // JSON verilerini işle
            FileUploadRequest uploadRequest = null;
            if (jsonData != null && !jsonData.isEmpty()) {
                uploadRequest = objectMapper.readValue(jsonData, FileUploadRequest.class);
                logger.info("Received data: {}", jsonData);
            }

            // Dosya kontrolü
            if (file == null || file.isEmpty()) {
                return ResponseEntity.badRequest().body("No file received");
            }

            String originalFileName = file.getOriginalFilename();
            long originalSize = file.getSize();
            byte[] fileBytes;
            long processedSize;

            // Dosyayı işle - compressed ise decompress et
            if (uploadRequest != null && uploadRequest.isCompressed()) {
                logger.info("Decompressing file: {}", originalFileName);
                
                try (InputStream compressedStream = file.getInputStream();
                     GZIPInputStream gzipStream = new GZIPInputStream(compressedStream);
                     ByteArrayOutputStream decompressedStream = new ByteArrayOutputStream()) {
                    
                    byte[] buffer = new byte[1024];
                    int len;
                    while ((len = gzipStream.read(buffer)) != -1) {
                        decompressedStream.write(buffer, 0, len);
                    }
                    
                    fileBytes = decompressedStream.toByteArray();
                    processedSize = fileBytes.length;
                    
                    logger.info("File decompressed: {} bytes -> {} bytes", originalSize, processedSize);
                }
            } else {
                fileBytes = file.getBytes();
                processedSize = fileBytes.length;
            }

            // Dosyayı kaydet
            String savedFilePath = saveFile(originalFileName, fileBytes);

            // Response hazırla
            Map<String, Object> fileInfo = new HashMap<>();
            fileInfo.put("originalName", originalFileName);
            fileInfo.put("originalSize", originalSize);
            fileInfo.put("processedSize", processedSize);
            fileInfo.put("contentType", file.getContentType());
            fileInfo.put("savedPath", savedFilePath);
            fileInfo.put("wasCompressed", uploadRequest != null && uploadRequest.isCompressed());

            Map<String, Object> response = new HashMap<>();
            response.put("message", "File and data received successfully");
            response.put("uploadRequest", uploadRequest);
            response.put("file", fileInfo);
            response.put("processedAt", LocalDateTime.now());

            logger.info("File processed: {} (Original: {} bytes, Processed: {} bytes)", 
                       originalFileName, originalSize, processedSize);

            return ResponseEntity.ok(response);

        } catch (IOException ex) {
            if (ex.getMessage().contains("Not in GZIP format") || ex.getMessage().contains("invalid")) {
                logger.error("Invalid compressed data format", ex);
                return ResponseEntity.badRequest().body("Invalid compressed file format");
            }
            logger.error("IO error processing upload request", ex);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Upload processing failed");
        } catch (Exception ex) {
            if (ex.getMessage().contains("JSON")) {
                logger.error("Invalid JSON in data field", ex);
                return ResponseEntity.badRequest().body("Invalid JSON format in data field");
            }
            logger.error("Error processing upload request", ex);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Upload processing failed");
        }
    }

    private String saveFile(String originalFileName, byte[] fileBytes) throws IOException {
        String uploadsDirectory = System.getProperty("user.dir") + "/uploads";
        Path uploadsPath = Paths.get(uploadsDirectory);
        
        if (!Files.exists(uploadsPath)) {
            Files.createDirectories(uploadsPath);
        }

        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"));
        String fileName = timestamp + "_" + originalFileName;
        Path filePath = uploadsPath.resolve(fileName);

        Files.write(filePath, fileBytes);

        return filePath.toString();
    }
}
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developer.paywall.one/payment-orchestration-integration-document/example-codes/bulk-payment-callback-method.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
