Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

Commit

Permalink
Adds the ability to pay invoice items using Stripe
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Krienbühl committed Oct 3, 2017
1 parent 70796e3 commit f147f30
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 8 deletions.
4 changes: 4 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Changelog
---------

- Adds the ability to pay invoice items using Stripe.
[href]

1.2.6 (2017-09-28)
~~~~~~~~~~~~~~~~~~~

Expand Down
12 changes: 12 additions & 0 deletions onegov/feriennet/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ def periods(self):

return p

@orm_cached(policy='on-table-change:periods')
def periods_by_id(self):
return {
p.id.hex: p for p in PeriodCollection(self.session()).query()
}

@orm_cached(policy='on-table-change:users')
def users_by_username(self):
return {
u.username: u for u in UserCollection(self.session()).query()
}

@cached_property
def sponsors(self):
return load_sponsors(utils.module_path('onegov.feriennet', 'sponsors'))
Expand Down
5 changes: 5 additions & 0 deletions onegov/feriennet/collections/billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from onegov.activity import Activity, Attendee, Booking, Occasion, InvoiceItem
from onegov.activity import BookingCollection, InvoiceItemCollection
from onegov.core.utils import normalize_for_url
from onegov.pay import Price
from onegov.user import User
from sortedcontainers import SortedDict

Expand Down Expand Up @@ -51,6 +52,10 @@ def tally(item):

self.id = self.item_id(self.first)

@property
def price(self):
return Price(self.outstanding, 'CHF')

@staticmethod
def item_id(item):
components = (item.invoice, item.username)
Expand Down
9 changes: 8 additions & 1 deletion onegov/feriennet/path.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from onegov.activity import Booking, BookingCollection
from onegov.activity import InvoiceItemCollection
from onegov.activity import InvoiceItemCollection, InvoiceItem
from onegov.activity import Occasion, OccasionCollection
from onegov.activity import Period, PeriodCollection
from onegov.activity import Attendee, AttendeeCollection
Expand Down Expand Up @@ -192,6 +192,13 @@ def get_my_invoies(request, app, username=None):
return InvoiceItemCollection(app.session(), username)


@FeriennetApp.path(
model=InvoiceItem,
path='/invoice-item/{id}')
def get_my_invoice_item(request, app, id):
return InvoiceItemCollection(app.session()).by_id(id)


@FeriennetApp.path(
model=OccasionAttendeeCollection,
path='/teilnehmer',
Expand Down
26 changes: 24 additions & 2 deletions onegov/feriennet/templates/invoices.pt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<div class="row">
<div class="columns small-12 medium-8">
<tal:block condition="not: bills" i18n:translate>No bills created yet.</tal:block>

<div class="bills" tal:condition="bills">
<div class="bill ${details.paid and 'paid' or 'unpaid'}" id="${details.id}" tal:repeat="(title, details) bills.items()">
<h3>${details.title}</h3>
Expand All @@ -29,7 +29,7 @@
<li class="item-group">
<strong>${group}</strong>
</li>
<li tal:repeat="item groupitems" class="${item.paid and 'paid' or 'unpaid'}">
<li tal:repeat="item groupitems" class="${item.paid and 'paid' or 'unpaid'} ${item.source and item.source or ''}">
<span class="item-text">${item.text}</span>
<span class="item-amount" tal:content="'{:.2f}'.format(item.amount)"></span>
</li>
Expand All @@ -41,6 +41,28 @@
</ul>
</div>

<div class="invoice-items-payment" tal:condition="payment_provider and not details.paid">
<div class="row">
<div class="columns small-12">
<h4 i18n:translate>Online Payment</h4>

<div class="payment-button">
<tal:b metal:use-macro="layout.macros['checkout_form']"
tal:define="
payment_method 'cc';
checkout_button payment_button(details.title, details.price);
complete_link request.url;
price details.price;
">
<tal:b metal:fill-slot="after-submit">
<input type="hidden" name="period" value="${details.first.invoice}">
</tal:b>
</tal:b>
</div>
</div>
</div>
</div>

<div class="invoice-items-payment" tal:condition="account and not details.paid">
<div class="row">
<div class="columns small-12">
Expand Down
20 changes: 18 additions & 2 deletions onegov/feriennet/templates/macros.pt
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-KM9K588');
/* eslint-enable */</script>
})(window,document,'script','dataLayer','GTM-KM9K588');/* eslint-enable */
</script>

</metal:favicon>

Expand Down Expand Up @@ -512,3 +512,19 @@
Your request has been completed.
</p>
</metal:ticket_status_page_message>


<metal:payment_link_fallback define-macro="payment_link_fallback" i18n:domain="onegov.feriennet">
<tal:b switch="link.__tablename__">
<tal:b case="'invoice_items'">
<div tal:condition="repeat.link.start">
<a href="${request.link(link)}">
<strong>${request.app.users_by_username[link.username].title}</strong>
</a>
<div>
${request.app.periods_by_id[link.invoice].title}
</div>
</div>
</tal:b>
</tal:b>
</metal:payment_link_fallback>
37 changes: 34 additions & 3 deletions onegov/feriennet/theme/styles/feriennet.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1292,7 +1292,7 @@ ul.tags {
margin: .5rem .25rem .5rem .75rem;
}

button {
> button {
float: right;
height: 40px;
margin: 0;
Expand Down Expand Up @@ -1525,21 +1525,26 @@ ul.tags {
font-family: $font-family-monospace;
}

li.paid .item-amount:after {
li.paid .item-amount::after {
@include icon('\f05d');
color: $green-dark;
font-weight: normal;
position: absolute;
right: 33px;
}

li.unpaid .item-amount:after {
li.unpaid .item-amount::after {
@include icon('\f06a');
color: $red-dark;
font-weight: normal;
position: absolute;
right: 33px;
}

li.paid.stripe_connect .item-amount::after {
content: '\f09d';
font-size: .875rem;
}
}

.toggled + .invoice-items-list + .invoice-items-payment > div {
Expand Down Expand Up @@ -1967,3 +1972,29 @@ ul.tags {
.message-period {
background: $blue-pastel;
}


/*
Payment button
*/
.payment-button {
button.checkout-button {
background: $white;
border: 1px solid $oil;
border-radius: 2px;
color: $body-font-color;
margin: .5rem 0 -.5rem;
outline: none;

&:hover {
background: $white;
box-shadow: 1px 1px 2px $base;
outline: none;
}

&::before {
@include icon('\f09d');
margin-right: 1ex;
}
}
}
59 changes: 59 additions & 0 deletions onegov/feriennet/views/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@
from onegov.feriennet.collections import BillingDetails
from onegov.feriennet.layout import InvoiceLayout
from onegov.feriennet.views.shared import all_users
from onegov.pay import process_payment
from sortedcontainers import SortedDict
from stdnum import iban


@FeriennetApp.view(
model=InvoiceItem,
permission=Personal,
)
def redirect_to_invoice_view(self, request):
return request.redirect(request.link(
InvoiceItemCollection(
request.app.session(), username=self.username, invoice=self.invoice
)
))


@FeriennetApp.html(
model=InvoiceItemCollection,
template='invoices.pt',
Expand Down Expand Up @@ -49,6 +62,17 @@ def view_my_invoices(self, request):

beneficiary = request.app.org.bank_beneficiary

payment_provider = request.app.default_payment_provider

def payment_button(title, price):
return request.app.checkout_button(
button_label=request.translate(_("Pay Online Now")),
title=title,
price=payment_provider.adjust_price(price),
email=self.username,
locale=request.locale
)

return {
'title': title,
'layout': InvoiceLayout(self, request, title),
Expand All @@ -57,5 +81,40 @@ def view_my_invoices(self, request):
'bills': bills,
'model': self,
'account': account,
'payment_provider': payment_provider,
'payment_button': payment_button,
'beneficiary': beneficiary
}


@FeriennetApp.view(
model=InvoiceItemCollection,
template='invoices.pt',
permission=Personal,
request_method='POST')
def handle_payment(self, request):
provider = request.app.default_payment_provider
token = request.params.get('payment_token')
period = request.params.get('period')

q = self.query()
q = q.filter(InvoiceItem.invoice == period)
q = q.filter(InvoiceItem.paid == False)

items = tuple(q)
bill = BillingDetails(period, items)
payment = process_payment('cc', bill.price, provider, token)
payment.sync()

if not payment:
request.alert(_("Your payment could not be processed"))
else:

for item in items:
item.payment = payment
item.paid = True
item.source = provider.type

request.success(_("Your payment has been received. Thank you!"))

return request.redirect(request.link(self))

0 comments on commit f147f30

Please sign in to comment.