Backend, E-commerce

Python i Stripe. Jak zaimplementować system płatności online w e-sklepie

Zastanawiasz się nad własnym biznesem? Masz już witrynę internetową sklepu? Jeżeli tak, to świetnie trafiłeś! W tym artykule zdradzę Ci jak zaimplementować Stripe – jedną z najpopularniejszych platform do płatności online. Pssst… #python on the board!

Na początek, przedstawiam Wam jedną z najpopularniejszych platform do płatności internetowych – Stripe. Działa ona w ponad 25 krajach i nieustannie rozszerza swoją działalność o kolejne kraje. Umożliwia przyjmowanie przelewów, rozliczenia, wpłaty, a także dostarcza gotowy interfejs do zarządzania użytkownikami. Ty, jako właściciel sklepu internetowego, nie przechowujesz danych kart/kont bankowych swoich użytkowników – Stripe zapewnia całkowite wsparcie i zwalnia Cię z odpowiedzialności za wrażliwe dane.

Warto także podkreślić, że Stripe działa w oparciu o model prowizyjny – w zależności od kraju, pobierana jest odpowiednia kwota za udane obciążenie karty (w Polsce jest to 1,40% + 1 PLN od każdej transakcji).

Dla Ciebie, jako sprzedawcy, skorzystanie z płatności online oznacza naliczanie prowizji od transakcji: transferów, przelewów oraz regulowania należności kartą – jednym słowem zarabiasz. I co najważniejsze, Stripe udostępnia API, napisane w #python. Zabierajmy się za implementację!

Implementacja Stripe

Załóżmy, że podstawowe informacje o użytkownikach trzymamy w bazie (tabele stworzymy w Django). Model użytkownika (User), oprócz profilu (UserProfile), posiadać będzie także pole stripe_id, w którym przechowywać będziemy unikalne ID usera, wygenerowane przez Stripe.

class User(AbstractBaseUser):
    """
    User model.
    """

    USERNAME_FIELD = "email"

    REQUIRED_FIELDS = ["first_name", "last_name"]

    email = models.EmailField(
        verbose_name="E-mail",
        unique=True
    )

    first_name = models.CharField(
        verbose_name="First name",
        max_length=30
    )

    last_name = models.CharField(
        verbose_name="Last name",
        max_length=40
    )

    city = models.CharField(
        verbose_name="City",
        max_length=40
    )

    stripe_id = models.CharField(
        verbose_name="Stripe ID",
        unique=True,
        max_length=50,
        blank=True,
        null=True
    )

    objects = UserManager()

    @property
    def get_full_name(self):
        return f"{self.first_name} {self.last_name}"

    class Meta:
        verbose_name = "User"
        verbose_name_plural = "Users"

class Profile(models.Model):
    """
    User's profile.
    """

    phone_number = models.CharField(
        verbose_name="Phone number",
        max_length=15
    )

    date_of_birth = models.DateField(
        verbose_name="Date of birth"
    )

    postal_code = models.CharField(
        verbose_name="Postal code",
        max_length=10,
        blank=True
    )

    address = models.CharField(
        verbose_name="Address",
        max_length=255,
        blank=True
    )

    class Meta:
        abstract = True


class UserProfile(Profile):
    """
    User's profile model.
    """

    user = models.OneToOneField(
        to=User, on_delete=models.CASCADE, related_name="profile",
    )

    group = models.CharField(
        verbose_name="Group type",
        choices=GroupTypeChoices.choices(),
        max_length=20, default=GroupTypeChoices.CLIENT.name,
    )

    def __str__(self):
        return self.user.email

    class Meta:
        unique_together = ("user", "group")

Mając już tak zdefiniowane wcześniej modele, możemy stworzyć testowych użytkowników:

# user 1 - employer
user1, _ = User.objects.get_or_create(
    email="foo@bar.com",
    first_name="Employer",
    last_name="Testowy",
    city="Białystok",
)
user1.set_unusable_password()

group_name = "employer"

_profile1, _ = UserProfile.objects.get_or_create(
    user=user1,
    date_of_birth=datetime.now() - timedelta(days=6600),
    group=GroupTypeChoices(group_name).name,
    address="Myśliwska 14",
    postal_code="15-569",
    phone_number="+48100200300",
)

# user2 - employee
user2, _ = User.objects.get_or_create(
    email="bar@foo.com",
    first_name="Employee",
    last_name="Testowy",
    city="Białystok",
)
user2.set_unusable_password()

group_name = "employee"

_profile2, _ = UserProfile.objects.get_or_create(
    user=user2,
    date_of_birth=datetime.now() - timedelta(days=7600),
    group=GroupTypeChoices(group_name).name,
    address="Myśliwska 14",
    postal_code="15-569",
    phone_number="+48200300400",
)

Mamy już wszystkie potrzebne informacje, stworzyliśmy dwóch użytkowników, możemy zacząć pracę ze Stripe.

Tworzenie kont

Wszystko teraz tak naprawdę zależy od tego, z jakim typem konta chcielibyśmy naszego użytkownika powiązać. Jeżeli nasz użytkownik będzie zlecał usługi (Employer – zleceniodawca), tworzymy konto Customera:

response_customer = stripe.Customer.create(
    email=user1.email,
    description=f"EMPLOYER - {user1.get_full_name}",
    name=user1.get_full_name,
    phone=user1.profile.phone_number,
)

user1.stripe_id = response_customer.stripe_id
user1.save()

Z kolei gdy nasz użytkownik będzie wykonywał usługi (Employee – zleceniobiorca), tworzymy konto Connected Account:

mcc_code, url = "1520", "https://www.softserveinc.com/"

response_ca = stripe.Account.create(
    type="custom",
    country="PL",
    email=user2.email,
    default_currency="pln",
    business_type="individual",
    settings={"payouts": {"schedule": {"interval": "manual", }}},
    requested_capabilities=["card_payments", "transfers", ],
    business_profile={"mcc": mcc_code, "url": url},
    individual={
        "first_name": user2.first_name,
        "last_name": user2.last_name,
        "email": user2.email,
        "dob": {
            "day": user2.profile.date_of_birth.day,
            "month": user2.profile.date_of_birth.month,
            "year": user2.profile.date_of_birth.year,
        },
        "phone": user2.profile.phone_number,
        "address": {
            "city": user2.city,
            "postal_code": user2.profile.postal_code,
            "country": "PL",
            "line1": user2.profile.address,
        },
    },
)

user2.stripe_id = response_ca.stripe_id
user2.save()

W obu przypadkach API Stripe’a zwraca unikalny ID (id lub stripe_id), dzięki któremu możemy identyfikować naszych użytkowników.

Jeżeli tworzymy konto Customera, ID poprzedza prefix “cus_xxx”, a w przypadku Connected Account prefix “acct_xxx”.

O ile konto Customera nie wymaga dodatkowych kroków w kontekście rejestracji, to aby mieć w pełni zweryfikowane konto Connected Account, należy:

  • zaakceptować politykę Stripe:
tos_acceptance = {
    "date": int(time.time()), 
    "ip": user_ip
}

stripe.Account.modify(
    user2.stripe_id, 
    tos_acceptance=tos_acceptance
)

Przekazujemy tutaj w postaci timestamp aktualny czas (date) oraz adres IP użytkownika (ip).

  • załączyć dokumenty potwierdzające tożsamość (paszport, dowód osobisty lub prawo jazdy):
passport_front = stripe.File.create(
    purpose="identity_document",
    file=_file, # ContentFile object
    stripe_account=user2.stripe_id,
)

individual = {
    "verification": {
        "document": {"front": passport_front.get("id"),},
        "additional_document": {"front": passport_front.get("id"),},
    }
}

stripe.Account.modify(user2.stripe_id, individual=individual)

W tym przykładzie przekazujemy do weryfikacji paszport – oprócz frontowego zdjęcia dokumentu, należy jeszcze przekazać ten sam obiekt jako dodatkowy dokument. W innym wypadku weryfikacja się nie powiedzie…

To już wszystkie kroki, jeżeli chodzi o rejestrację klienta. Pora na dodanie karty do konta Customera:

new_card_source = stripe.Customer.create_source(
    user1.stripe_id, 
    source=token
)

stripe.SetupIntent.create(
    payment_method_types=["card"],
    customer=user1.stripe_id,
    description="some description",
    payment_method=new_card_source.id,
)

W pierwszej kolejności dodajemy Customerowi nowe źródło płatności w postaci tokena, który zawiera zakodowane informacje o karcie. Następnie musimy ustawić wcześniej utworzoną metodę płatności jako domyślną (SetupIntent).

Przechodząc pomyślnie przez powyższe kroki, możemy spróbować ściągnąć kwotę z konta Customera:

payment_method = stripe.Customer.retrieve(
    user1.stripe_id
).default_source

payment_intent = stripe.PaymentIntent.create(
    amount=amount,
    currency="pln",
    payment_method_types=["card"],
    capture_method="manual",
    customer=user1.stripe_id,
    payment_method=payment_method,
    application_fee_amount=application_fee_amount,
    transfer_data={"destination": user2.stripe_id}, # connected account
    description=description,
    metadata=metadata,
)

payment_intent_confirm = stripe.PaymentIntent.confirm(
    payment_intent.stripe_id, 
    payment_method=payment_method
)

W powyższym przykładzie karta Customera jest obciążana o kwotę podaną w amount. Nasza prowizja to application_fee_amount – tutaj musimy podać już wyliczoną kwotę prowizji, która powinna trafić na nasze konto.

Za docelowe konto (tam, gdzie mają trafić środki za wykonane zlecenie) odpowiada transfer_data – w destination podajemy Stripe ID klienta (Connected Account).

Na koniec musimy potwierdzić obciążenie karty Customera (payment_intent_confirm) – efektem będzie tymczasowa blokada kwoty na karcie Customera.

W momencie, gdy chcemy ściągnąć środki z karty Customera, robimy:

stripe.PaymentIntent.capture(
    payment_intent.id, 
    amount_to_capture=amount
)

Po wykonaniu tej metody środki powinny pojawić się na koncie klienta (Connected Account).

Aby sprawdzić jego balance, wpisujemy:

stripe.Balance.retrieve(stripe_account=user2.stripe_id)

W odpowiedzi uzyskamy informację o aktualnie dostępnych środkach na koncie:

  • pending – w toku, co oznacza że środki nie są jeszcze dostępne do wypłaty,
  • available – dostępne, co oznacza że środki można wypłacić teraz.

Może dojść czasem do sytuacji, w której będziemy musieli obciążyć jedną ze stron, np. za brak wykonanego zlecenia, a wcześniej obciążyliśmy już kartę Customera:

stripe.Charge.create(
    amount=amount, 
    currency="pln", 
    source=user2.stripe_id,
    description=description
)

W ten sposób zleceniobiorca (Connected Account) może być obciążony kwotą podaną w amount. Efektem jest zmniejszenie aktualnego balansu na koncie.

No i musimy zwrócić z powrotem środki zleceniodawcy (Customer):

stripe.PaymentIntent.cancel(payment_intent.id)

Podsumowanie

Stripe dostarcza API, dzięki któremu możemy w szybki i prosty sposób zbudować system wspierający płatności online. W zależności od naszego “modelu biznesowego”, w łatwy sposób jesteśmy w stanie dobrać odpowiednio zaimplementowane metody.

Dokumentacja API jest na tyle przejrzysta, że po odpowiedniej lekturze, zabieramy się za pisanie kilkulinijkowego kodu, który robi za nas całą robotę – oczywiście wcześniej musimy odpowiednio przetestować napisaną przez nas logikę, aby mieć pewność, że każda dowolna transakcja będzie przetwarzana wedle naszych oczekiwań. Służą temu tokeny testowe, a także testowe numery kont bankowych i kart, dzięki którym możemy wykonywać realne transakcje.

Po odpowiednio wykonanych testach, możemy przejść z trybu deweloperskiego na produkcyjny. Jedyną różnicą będzie klucz API, który będzie wykorzystywany przy każdym zapytaniu do Stripe.


Zdjęcie główne artykułu pochodzi z unsplash.com.

Entuzjasta Pythona, specjalizujący się w aplikacjach internetowych typu backend, przeszukiwaniu, eksploracji/analizie danych i ich wizualizacji. Pasjonat prostego i czytelnego kodu, ceni praktyki TDD. Miłośnik bibliotek związanych z uczeniem maszynowym i rozwiązywaniem problemów medycznych (Kaggle). W wolnym czasie uczestniczy w biegach długodystansowych i podróżuje po świecie.

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://justjoin.it/blog/implementacja-stripe" order_type="social" width="100%" count_of_comments="8" ]