푸터 콘텐츠로 바로가기
IRONPDF 사용하기

핀테크 앱을 위한 C# PDF 영수증 및 거래 기록

단순한 서식 문제가 아닌 규정 준수 문제

IronPDF 홈페이지 대부분의 핀테크 애플리케이션은 거래 데이터를 관계형 데이터베이스에 저장하며, 고객이 영수증을 요청할 때 해당 레코드를 다시 조회하여 화면을 표시함으로써 영수증을 생성합니다. 이러한 방식에는 근본적인 문제가 있습니다. 결과물인 PDF 또는 "영수증"은 데이터의 현재 상태를 반영할 뿐, 거래가 완료된 순간 고객이 보았던 내용을 반영하지 않기 때문입니다.

데이터베이스 레코드는 일반적인 운영 프로세스를 통해 수정, 변경 또는 업데이트될 수 있습니다. 재조회된 영수증은 과거의 기록이 아니라, 과거의 타임스탬프가 포함된 현재의 스냅샷입니다. 결제 처리업체가 지불 거절(chargeback)에 직면했을 때, 네오뱅크의 규정 준수 팀이 규제 당국에 답변할 때, 대출 플랫폼의 감사 로그에 대한 소환장이 발부되었을 때, 또는 암호화폐 거래소가 KYC 심사관에게 문서의 무결성을 입증해야 할 때, 필요한 것은 단순한 조회 결과가 아니라 실제 문서입니다.

정산 시점에 저장되고, 변조 감지를 위해 해시 처리되며, 변경 불가능한 저장소에 기록된 PDF 문서가 바로 그 문서입니다. 이를 통해 기존 PDF 문서의 유효성이 수년 동안 유지됩니다. 재무 보고서든 간단한 첫 번째 PDF 문서든, 문서 생성은 완벽해야 합니다.

PCI-DSS, SOX 및 AML 기록 보관 의무는 특별히 PDF 문서를 프로그래밍 방식으로 처리할 것을 요구하지는 않지만, 입증 가능하고 감사 가능한 기록을 요구합니다. 렌더링되고 해시 처리되며 타임스탬프가 포함된 PDF 파일은 데이터베이스 행만으로는 충족할 수 없는 기준을 충족시킵니다. 이 글의 뒷부분에서는 새로운 PDF 문서를 생성할 때 이 과정이 어떻게 진행되는지 IronPDF 예시를 통해 살펴보겠습니다.

솔루션 개요: C#으로 HTML 콘텐츠를 PDF로 변환하기

Iron Software의 IronPDF 라이브러리는 거래 파이프라인의 일부로서, 거래가 완료되는 정확히 그 순간에 동기식으로 생성된 PDF 영수증을 생성합니다. IronPDF는 NuGet 패키지 관리자 또는 Visual Studio의 패키지 관리자 콘솔을 통해 설치할 수 있습니다. dot NET CLI를 사용하여 간단히 install package IronPDF를 실행하십시오.

영수증은 HTML 템플릿이나 HTML 파일에서 렌더링되며, 거래 ID와 타임스탬프가 찍히고 해시 처리된 후 불변 저장소에 기록됩니다. 이 문서는 최종 문서가 됩니다. 유지 관리해야 할 SSRS 정의도, 호출해야 할 타사 문서 API도, 헤드리스 브라우저 사이드카도 없습니다. IronPDF는 PDF 작업을 위한 NuGet 라이브러리로 인-프로세스(in-process) 방식으로 실행됩니다.

문서 워크플로 자동화의 주요 이점:

  • 모든 페이지 크기에서 서식 유지.

  • 문서의 무결성을 보장하기 위한 디지털 서명 지원.

  • 웹 페이지나 HTML 문자열로부터 PDF 객체를 생성할 수 있는 기능.

  • Iron Software의 고객 로고와 브랜딩을 쉽게 삽입할 수 있습니다

트랜잭션-영수증 파이프라인의 작동 방식

정상 경로

결제가 성공하면 거래 기록이 데이터베이스에 기록됩니다. 동일한 핸들러는 응답을 반환하기 전에 거래 내역(ID, UTC 타임스탬프, 금액 및 통화, 발신자 및 수신자 식별자, 수수료 내역, 동적 콘텐츠)을 HTML 콘텐츠 영수증 템플릿에 채워 넣습니다.

renderer new ChromePdfRenderer (구체적으로는 var renderer = new ChromePdfRenderer();)는 웹 콘텐츠를 PDF 형식으로 변환합니다. 생성된 PDF 바이트 배열은 즉시 SHA-256을 사용하여 해시 처리됩니다.

using IronPdf
using System.Security.Cryptography;

var renderer = new ChromePdfRenderer();

renderer.RenderingOptions.MarginTop = 15;

renderer.RenderingOptions.MarginBottom = 15;

string receiptHtml = $@"
    <h1>Transaction Receipt</h1>
    <p><strong>Transaction ID:</strong> {tx.Id}</p>
    <p><strong>Timestamp (UTC):</strong> {tx.CompletedAt:u}</p>
    <p><strong>Amount:</strong> {tx.Amount:F2} {tx.Currency}</p>
    <p><strong>Fee:</strong> {tx.Fee:F2} {tx.Currency}</p>
    <p><strong>From:</strong> {tx.SenderRef} &rarr; <strong>To:</strong> {tx.ReceiverRef}</p>
    <p><strong>Resulting Balance:</strong> {tx.ClosingBalance:F2} {tx.Currency}</p>";

var pdf = renderer.RenderHtmlAsPdf(receiptHtml);

string hash = Convert.ToHexString(SHA256.HashData(pdf.BinaryData));

await _db.StoreReceiptHashAsync(tx.Id, hash);

await _blobStorage.UploadImmutableAsync($"receipts/{tx.Id}.pdf", pdf.BinaryData);
using IronPdf
using System.Security.Cryptography;

var renderer = new ChromePdfRenderer();

renderer.RenderingOptions.MarginTop = 15;

renderer.RenderingOptions.MarginBottom = 15;

string receiptHtml = $@"
    <h1>Transaction Receipt</h1>
    <p><strong>Transaction ID:</strong> {tx.Id}</p>
    <p><strong>Timestamp (UTC):</strong> {tx.CompletedAt:u}</p>
    <p><strong>Amount:</strong> {tx.Amount:F2} {tx.Currency}</p>
    <p><strong>Fee:</strong> {tx.Fee:F2} {tx.Currency}</p>
    <p><strong>From:</strong> {tx.SenderRef} &rarr; <strong>To:</strong> {tx.ReceiverRef}</p>
    <p><strong>Resulting Balance:</strong> {tx.ClosingBalance:F2} {tx.Currency}</p>";

var pdf = renderer.RenderHtmlAsPdf(receiptHtml);

string hash = Convert.ToHexString(SHA256.HashData(pdf.BinaryData));

await _db.StoreReceiptHashAsync(tx.Id, hash);

await _blobStorage.UploadImmutableAsync($"receipts/{tx.Id}.pdf", pdf.BinaryData);
Imports IronPdf
Imports System.Security.Cryptography

Dim renderer As New ChromePdfRenderer()

renderer.RenderingOptions.MarginTop = 15

renderer.RenderingOptions.MarginBottom = 15

Dim receiptHtml As String = $"
    <h1>Transaction Receipt</h1>
    <p><strong>Transaction ID:</strong> {tx.Id}</p>
    <p><strong>Timestamp (UTC):</strong> {tx.CompletedAt:u}</p>
    <p><strong>Amount:</strong> {tx.Amount:F2} {tx.Currency}</p>
    <p><strong>Fee:</strong> {tx.Fee:F2} {tx.Currency}</p>
    <p><strong>From:</strong> {tx.SenderRef} &rarr; <strong>To:</strong> {tx.ReceiverRef}</p>
    <p><strong>Resulting Balance:</strong> {tx.ClosingBalance:F2} {tx.Currency}</p>"

Dim pdf = renderer.RenderHtmlAsPdf(receiptHtml)

Dim hash As String = Convert.ToHexString(SHA256.HashData(pdf.BinaryData))

Await _db.StoreReceiptHashAsync(tx.Id, hash)

Await _blobStorage.UploadImmutableAsync($"receipts/{tx.Id}.pdf", pdf.BinaryData)
$vbLabelText   $csharpLabel

생성된 PDF 문서 예시

IronPDF 예제 PDF 출력 그런 다음 새로운 PDF 문서는 불변 저장소에 저장됩니다. 첫 번째 PDF를 생성하든 기존 PDF를 병합하든, 그 과정은 동일합니다. 번역본은 PDF 뷰어를 통해 고객에게 제공됩니다. Iron Software 제품 데모에서 제품 데모 팀은 종종 몇 줄의 코드만으로 동적 보고서 처리나 HTML을 PDF로 변환하는 작업, PDF 생성 작업 등을 수행할 수 있음을 강조합니다.

UI/UX를 돕기 위해 대시보드에서 보안 처리된 해시 파일을 나타내는 파란색 원 안에 열쇠, 회색 원 안에 열쇠, 또는 파란색 원 안에 열쇠 아이콘이 표시될 수 있습니다. 다운로드 링크와 관련된 오른쪽 화살표 아이콘은 사용자의 탐색을 돕습니다.

각 페이지 상단 또는 하단에 찍힌 로고는 문서의 출처를 명확히 보여줍니다:

var shortHash = hash[..12];

renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    HtmlFragment = $@"
        <div style='font-size:9px; color:#666; text-align:center;'>
            Generated: {tx.CompletedAt:u} &nbsp;|&nbsp;
            TX: {tx.Id} &nbsp;|&nbsp;
            SHA-256: {shortHash}...
        </div>"
};
var shortHash = hash[..12];

renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    HtmlFragment = $@"
        <div style='font-size:9px; color:#666; text-align:center;'>
            Generated: {tx.CompletedAt:u} &nbsp;|&nbsp;
            TX: {tx.Id} &nbsp;|&nbsp;
            SHA-256: {shortHash}...
        </div>"
};
HTML

예시 푸터

예제 푸터 영수증의 모든 페이지에는 거래 ID, 생성 타임스탬프 및 축약된 해시가 포함되어 있어, 규정 준수 담당자가 데이터베이스에 접근하지 않고도 무작위 검사를 통해 무결성을 확인할 수 있습니다.

예외적인 경우

취소 및 환불. 거래가 실패했거나 취소된 경우에도 문서가 필요합니다. 원본 거래 ID와 영수증 해시를 참조하여 역거래 이벤트에 대한 별도의 PDF를 생성하십시오. 취소 영수증은 발생한 내용을 기록한 독립된 문서이며, 원본을 대체하거나 수정하지 않습니다.

다중 통화 거래. HTML 템플릿은 지역 설정에 따라 통화 기호 배치, 소수점 구분자 및 환율 표시를 올바르게 처리해야 합니다. C# 포맷 문자열이 이 작업의 대부분을 처리합니다: 독일어 소수점 표기법을 위한 {amount.ToString("F2", CultureInfo.GetCultureInfo("de-DE"))} 및 소수점을 사용하지 않는 JPY와 같은 통화의 경우 기호를 명시적으로 배치하는 방식 등이 있습니다. 환율과 그 타임스탬프는 금액에서 추론되는 것이 아니라 별도로 명시된 항목으로 표시되어야 합니다.

규제 관련 워터마크. 일부 관할 구역에서는 특정 문서 유형에 "세금 계산서가 아님(NOT A TAX INVOICE)", "비공식 사본(UNOFFICIAL COPY)" 또는 해당 관할 구역에 특화된 공개 문구를 포함하도록 요구합니다. 이는 기본 트랜잭션 데이터를 수정하지 않고 템플릿 내 HTML 오버레이 또는 스타일링된 헤더 밴드로 깔끔하게 처리됩니다.

규정 준수를 넘어 중요한 이유

관계자 제공 내용
규정 준수 / 법률 감사 요청을 몇 분 만에 충족시키는 불변의 해시된 영수증으로, 재조회나 재구성이 필요 없으며, 현재 데이터베이스 기록이 고객이 본 내용과 다른 이유를 설명할 필요가 없습니다.
고객 지원 거래 시점에 고객이 수령한 정확한 문서를 바탕으로, 분쟁 해결을 주관적인 해석이 아닌 사실에 근거하여 진행합니다.
고객 모든 거래가 완료된 지 몇 초 만에 수신함으로 전송되는 Professional하고 브랜드가 반영된 영수증으로, 회사가 보관하고 있는 문서와 동일한 내용입니다.
엔지니어링 관리해야 할 HTML 템플릿은 단 하나이며, 외부 서비스에 의존하지 않고 프로세스 내에서 렌더링되며, 모니터링해야 할 API 계약도 없고, 문서별 요금을 추적할 필요도 없습니다.
재무 / 회계 별도의 아카이빙 워크플로우 없이도 문서 보존 정책에 부합하고 재무 기록 보관 요건을 충족하는 장기 보존용 PDF/A 아카이빙 출력

단일 렌더링 옵션(renderer.RenderingOptions.PdfArchiveFormat = IronPdf.Rendering.PdfArchiveFormat.PDF_A_3B)을 사용하여 PDF/A 출력을 활성화하면, 장기적인 재무 기록 보존에 적합한 ISO 표준 보관 문서를 생성할 수 있습니다.

마무리

"트랜잭션 데이터를 저장한다"와 "감사 가능한 기록을 보유한다" 사이의 차이는 겉보이는 것보다 작으며, 이는 트랜잭션 커밋 흐름에 렌더링 단계 하나가 추가된 것에 불과합니다. 이 단계는 데이터베이스 행만으로는 제공할 수 없는 출처 정보를 갖춘 문서를 생성합니다. 즉, 소급 적용할 수 없는 타임스탬프, 변조를 감지하는 해시, 그리고 고객이 열거나 감사관이 요청할 때 모두 동일하게 보이는 물리적 증거물을 포함합니다.

IronPDF는 ironpdf.com의 단일 라이브러리를 통해 HTML 영수증 렌더링부터 바닥글 스탬프 찍기, 출력물 해싱, 불변 저장소로의 스트리밍에 이르기까지 해당 단계 전반에 걸쳐 .NET 팀이 완벽한 제어권을 행사할 수 있도록 지원합니다. 트랜잭션 파이프라인을 구축하거나 보강하고 계신다면, 30일 무료 체험판을 시작하여 실제 운영에 들어가기 전에 수신 출력이 규정 준수 요건을 충족하는지 확인해 보십시오.

커티스 차우
기술 문서 작성자

커티스 차우는 칼턴 대학교에서 컴퓨터 과학 학사 학위를 취득했으며, Node.js, TypeScript, JavaScript, React를 전문으로 하는 프론트엔드 개발자입니다. 직관적이고 미적으로 뛰어난 사용자 인터페이스를 만드는 데 열정을 가진 그는 최신 프레임워크를 활용하고, 잘 구성되고 시각적으로 매력적인 매뉴얼을 제작하는 것을 즐깁니다.

커티스는 개발 분야 외에도 사물 인터넷(IoT)에 깊은 관심을 가지고 있으며, 하드웨어와 소프트웨어를 통합하는 혁신적인 방법을 연구합니다. 여가 시간에는 게임을 즐기거나 디스코드 봇을 만들면서 기술에 대한 애정과 창의성을 결합합니다.

아이언 서포트 팀

저희는 주 5일, 24시간 온라인으로 운영합니다.
채팅
이메일
전화해