Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 125 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,51 @@ and define some required methods:

.. code-block:: python

# orders/order_status.py
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
ON_HOLD = "on_hold"
CANCELLED = "cancelled"
REFUNDED = "refunded"
FAILED = "failed"

ORDER_STATUSES = (
(PENDING, _("PENDING")),
(PROCESSING, _("PROCESSING")),
(COMPLETED, _("COMPLETED")),
(ON_HOLD, _("ON HOLD")),
(CANCELLED, _("CANCELLED")),
(REFUNDED, _("REFUNDED")),
(FAILED, _("FAILED")),
)

# orders/models.py
from getpaid.models import AbstractOrder
from orders import order_status

class MyCustomOrder(AbstractOrder):
amount = models.DecimalField(decimal_places=2, max_digits=8)
description = models.CharField(max_length=128)
buyer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

status = FSMField(
choices=order_status.ORDER_STATUSES,
default=order_status.PENDING,
db_index=True,
protected=True,
editable=False,
)
is_paid = models.BooleanField(
default=False,
editable=False
)
paid_date = models.DateTimeField(
blank=True,
null=True,
editable=False
)
def get_absolute_url(self):
return reverse('order-detail', kwargs={"pk": self.pk})
return urljoin(settings.FRONTEND_URL, f"order/{self.pk}")

def get_total_amount(self):
return self.amount
Expand All @@ -93,15 +129,76 @@ and define some required methods:
def get_description(self):
return self.description

@transaction.atomic
@transition(
field=status,
source=[order_status.PENDING],
target=order_status.PROCESSING,
)
def set_as_paid(self):
self.is_paid = True
self.paid_date = timezone.now()

@transaction.atomic
@transition(
field=status,
source=[
order_status.PENDING,
order_status.PROCESSING,
order_status.ON_HOLD,
order_status.FAILED,
],
target=order_status.CANCELLED,
)
def set_as_cancelled(self):
pass

@transaction.atomic
@transition(
field=status, source=order_status.PROCESSING, target=order_status.COMPLETED
)
def set_as_completed(self):
pass

.. note:: If you already have an Order model and don't want to subclass ``AbstractOrder``
just make sure you implement all methods.

Inform getpaid of your Order model in ``settings.py`` and provide settings for payment backends:
Define a ``Payment`` model by subclassing ``getpaid.models.AbstractPayment``
and define some required methods:

.. code-block:: python

# payments/models.py
from getpaid.models import AbstractPayment

class MyCustomPayment(AbstractPayment):

def __str__(self):
return f"Payment #{self.id}"

def on_mark_as_paid(self, **kwargs):
self.order.set_as_paid()
self.order.save()

.. note:: If you already have an Order model and don't want to subclass ``AbstractOrder``
just make sure you implement all methods.

Inform getpaid of your Order & Payment model in ``settings.py`` and provide settings for payment backends:

.. code-block:: python

FRONTEND_URL = "https://mydomain.tld"
assert not FRONTEND_URL.endswith("/")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if that is safe to apply on all projects. Maybe we should just use urljoin and don't care about slash?

BACKEND_URL = "https://backend.mydomain.tld"

GETPAID_ORDER_MODEL = 'yourapp.MyCustomOrder'
GETPAID_PAYMENT_MODEL = "yourapp.MyCustomPayment"
GETPAID_PAYU_SLUG = "getpaid.backends.payu"
GETPAID_BACKEND_HOST = BACKEND_URL
GETPAID_FRONTEND_HOST = FRONTEND_URL

PAYMENT_CONTINUE_URL = "{frontend_host}/payment/{payment_id}/end/"
PAYMENT_RETRY_URL = "{frontend_host}/payment/{order_id}/retry/"
Comment thread
szymborski marked this conversation as resolved.

GETPAID_BACKEND_SETTINGS = {
GETPAID_PAYU_SLUG: {
Expand All @@ -110,21 +207,41 @@ Inform getpaid of your Order model in ``settings.py`` and provide settings for p
"second_key": "91ae651578c5b5aa93f2d38a9be8ce11",
"oauth_id": 12345,
"oauth_secret": "12f071174cb7eb79d4aac5bc2f07563f",
"continue_url": PAYMENT_CONTINUE_URL,
"retry_url": PAYMENT_RETRY_URL,
},
}


Write a view that will create the Payment.

An example view and its hookup to urls.py can look like this:

.. code-block:: python

# main urls.py
# orders/views.py
from rest_framework import mixins, permissions, viewsets
from getpaid.rest_framework.payment_creator import PaymentCreator

urlpatterns = [
# ...
path("gp/", include("getpaid.urls")),
]
class OrderViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = OrderSerializer
queryset = Order.objects.all()
permission_classes = (permissions.IsAuthenticated,)

@transaction.atomic()
def perform_create(self, serializer):
super().perform_create(serializer)
self.create_payment(serializer.instance)

def create_payment(self, order):
payment_data = self.request.data.get("payment", {})
return PaymentCreator(order, payment_data).create()

# orders/urls.py
router = DefaultRouter()
router.register("", OrderViewSet)

urlpatterns = router.urls

You can optionally override callback handler. Example for PayU backend:

Expand Down