Index: main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py	(revision 7018)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py	(revision 7019)
@@ -52,5 +52,5 @@
 from waeup.sirp.students.vocabularies import StudyLevelSource
 from waeup.sirp.students.utils import (
-    getPaymentDetails, getAccommodationDetails, selectBed)
+    getPaymentDetails, getAccommodationDetails, selectBed, renderPDF)
 from waeup.sirp.browser.resources import toggleall
 from waeup.sirp.authentication import get_principal_role_manager
@@ -957,5 +957,12 @@
     def label(self):
         return '%s: Online Payment Ticket %s' % (
-            self.context.__parent__.__parent__.fullname,self.context.p_id)
+            self.context.getStudent().fullname,self.context.p_id)
+
+class PaymentReceiptActionButton(ManageActionButton):
+    grok.context(IStudentOnlinePayment)
+    grok.require('waeup.viewStudent')
+    icon = 'actionicon_pdf.png'
+    text = 'Download payment receipt'
+    target = 'payment_receipt.pdf'
 
 class OnlinePaymentCallbackPage(grok.View):
@@ -1005,4 +1012,25 @@
         return
 
+class ExportPDFPaymentSlipPage(grok.View):
+    """Deliver a PDF slip of the context.
+    """
+    grok.context(IStudentOnlinePayment)
+    grok.name('payment_receipt.pdf')
+    grok.require('waeup.viewStudent')
+    form_fields = grok.AutoFields(IStudentOnlinePayment)
+    form_fields['creation_date'].custom_widget = FriendlyDateDisplayWidget('le')
+    form_fields['payment_date'].custom_widget = FriendlyDateDisplayWidget('le')
+    prefix = 'form'
+
+    @property
+    def label(self):
+        return 'Online Payment Receipt %s' % self.context.p_id
+
+    def render(self):
+        studentview = StudentBaseDisplayFormPage(self.context.getStudent(),
+            self.request)
+        return renderPDF(self,'Payment', 'payment_receipt.pdf',
+            self.context.getStudent, studentview)
+
 # We don't need the display form page yet
 #class AccommodationDisplayFormPage(WAeUPDisplayFormPage):
Index: main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py	(revision 7018)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py	(revision 7019)
@@ -696,10 +696,15 @@
         self.assertMatches('...Amount Authorized...',
                            self.browser.contents)
-
+        payment_url = self.browser.url
         # Managers can open the callback view which simulates a valid callback
         self.assertEqual(len(self.app['accesscodes']['SFE-0']),0)
-        self.browser.open(self.browser.url + '/callback')
+        self.browser.open(payment_url + '/callback')
         self.assertMatches('...Valid callback received...',
                           self.browser.contents)
+
+        # Managers can open the pdf payment receipt
+        self.browser.open(payment_url + '/payment_receipt.pdf')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
 
         # Managers can remove online school fee payment tickets
@@ -769,5 +774,6 @@
         value = ctrl.options[0]
         self.browser.getLink(value).click()
-        self.browser.open(self.browser.url + '/callback')
+        payment_url = self.browser.url
+        self.browser.open(payment_url + '/callback')
         self.assertMatches('...Valid callback received...',
                           self.browser.contents)
@@ -782,4 +788,10 @@
         ac = self.app['accesscodes']['CLR-0'][pin]
         ac.owner = self.student_id
+
+        # Students can open the pdf payment receipt
+        self.browser.open(payment_url + '/payment_receipt.pdf')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
+
         # The new CLR-0 pin can be used for starting clearance
         IWorkflowInfo(self.student).fireTransition('admit')
Index: main/waeup.sirp/trunk/src/waeup/sirp/students/utils.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/students/utils.py	(revision 7018)
+++ main/waeup.sirp/trunk/src/waeup/sirp/students/utils.py	(revision 7019)
@@ -3,5 +3,18 @@
 from grok import getSite
 from random import SystemRandom as r
+from datetime import date, datetime
+from reportlab.pdfgen import canvas
+from reportlab.lib.units import cm
+from reportlab.lib.pagesizes import A4
+from reportlab.lib.styles import getSampleStyleSheet
+from reportlab.platypus import (Frame, Paragraph, Image,
+    Table, Spacer)
+from reportlab.platypus.tables import TableStyle
+from zope.formlib.form import setUpEditWidgets
 from waeup.sirp.interfaces import academic_sessions_vocab
+
+SLIP_STYLE = TableStyle(
+    [('VALIGN',(0,0),(-1,-1),'TOP')]
+    )
 
 def generate_student_id(students,letter):
@@ -85,2 +98,82 @@
 def selectBed(available_beds):
     return available_beds[0]
+
+def setUpWidgets(view, ignore_request=False):
+    view.adapters = {}
+    view.widgets = setUpEditWidgets(
+        view.form_fields, view.prefix, view.context, view.request,
+        adapters=view.adapters, for_display=True,
+        ignore_request=ignore_request
+        )
+
+def renderStudentData(student, studentview):
+    setUpWidgets(studentview, ignore_request=True)
+    data = []
+    style = getSampleStyleSheet()
+    for widget in studentview.widgets:
+        if widget.name == 'form.adm_code':
+            continue
+        f_label = '<font size=12>%s</font>:' % widget.label.strip()
+        f_label = Paragraph(f_label, style["Normal"])
+        f_text = '<font size=12>%s</font>' % widget()
+        f_text = Paragraph(f_text, style["Normal"])
+        data.append([f_label,f_text])
+    table = Table(data,style=SLIP_STYLE)
+    return table
+
+# To be specified in customization packages, see also view which
+# calls the function.
+def renderPDF(view, subject='', filename='slip.pdf', student=None, studentview=None):
+    # (0,0),(-1,-1) = whole table
+    # (0,0),(0,-1) = first column
+    # (-1,0),(-1,-1) = last column
+    # (0,0),(-1,0) = first row
+    # (0,-1),(-1,-1) = last row
+
+    pdf = canvas.Canvas(filename,pagesize=A4)
+    pdf.setTitle(view.label)
+    pdf.setSubject(subject)
+    pdf.setAuthor('%s (%s)' % (view.request.principal.title,
+        view.request.principal.id))
+    pdf.setCreator('WAeUP SIRP')
+    width, height = A4
+    style = getSampleStyleSheet()
+    pdf.line(1*cm,height-(1.8*cm),width-(1*cm),height-(1.8*cm))
+
+    story = []
+    frame_header = Frame(1*cm,1*cm,width-(1.7*cm),height-(1.7*cm))
+    header_title = getattr(getSite(), 'name', u'Sample University')
+    story.append(Paragraph(header_title, style["Heading1"]))
+    frame_header.addFromList(story,pdf)
+
+    story = []
+    frame_body = Frame(1*cm,1*cm,width-(2*cm),height-(3.5*cm))
+    story.append(Paragraph(view.label, style["Heading2"]))
+    #story.append(HRFlowable())
+    if student:
+        story.append(Spacer(1, 18))
+        studenttable = renderStudentData(student, studentview)
+        story.append(studenttable)
+    story.append(Spacer(1, 18))
+    setUpWidgets(view)
+    data = []
+    for widget in view.widgets:
+        f_label = '<font size=12>%s</font>:' % widget.label.strip()
+        f_label = Paragraph(f_label, style["Normal"])
+        f_text = '<font size=12>%s</font>' % widget()
+        f_text = Paragraph(f_text, style["Normal"])
+        data.append([f_label,f_text])
+    table = Table(data,style=SLIP_STYLE)
+    story.append(table)
+    frame_body.addFromList(story,pdf)
+
+    story = []
+    frame_footer = Frame(1*cm,0,width-(2*cm),1*cm)
+    timestamp = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
+    f_text = '<font size=10>%s</font>' % timestamp
+    story.append(Paragraph(f_text, style["Normal"]))
+    frame_footer.addFromList(story,pdf)
+
+    view.response.setHeader(
+        'Content-Type', 'application/pdf')
+    return pdf.getpdfdata()
