Django library to simplify payment processing with pin

Overview

Maintainer Wanted

I no longer have any side projects that use django-pinpayments and I don't have the time or headspace to maintain an important project. If you're interested in helping maintain this project, add new features, etc then please get in touch via the relevant issue.

django-pinpayments

django-pinpayments provides helper functions for Pin Payments - a relatively new Australian payment processor that doesn't require merchant accounts and that doesn't require purchasers to have an account. Some may call it the "Australian version of Stripe".

django-pinpayments provides template tags to render the Pin.js payment form, which uses the Card API for processing. This means you can collect credit card details on your website, submit them via javascript to Pin (without them landing on your server), then process the payment on your server using the single-use card token that Pin return.

The provided Card tokens can also be used to create Customer tokens, to use for delayed or recurring billing.

django-pinpayments is designed to be a simple base for your own billing projects. It doesn't make too many assumptions, and leaves many things open for your design input.

Not Included

  • Any link to your existing models or business logic
  • Views for users to review/update their stored credit cards, or review previous transactions

Todo

  • Tests
  • More documentation
  • Signals on success or failure

Pre-requisites

Settings

  • PIN_ENVIRONMENTS - a dictionary of dictionaries containing Pin API keys & secrets
  • PIN_DEFAULT_ENVIRONMENT - a pointer to the environment to be used at runtime, if no specific environment is requested.

Warning: Make sure your settings do not end up in public source repositories, as they can be used to process payments in your name.

PIN_ENVIRONMENTS

Each environment must have the 'key' and 'secret' values.

I highly recommend at least test & production, however you can also configure other key pairs if you have eg a separate Pin account for part of your website. Perhaps you have membership sales processed by one account, and merchandise by another.

This setting, with at least one environment, is required for django-pinpayments to function. There is no default.

    PIN_ENVIRONMENTS = {
        'test': {
            'key': 'pk_qokBvPpEHIVmNETSoSdDVYP',
            'secret': 'MBjZMurpDtjDANDNFQObZmBhMg',
            'host': 'test-api.pinpayments.com',
        },
        'live': {
            'key': 'pk_yGCGLonMHJMFscFyNaLZdkEV',
            'secret': 'tOAQeMsMaBrxejJHIqHJVIObUS',
            'host': 'api.pinpayments.com',
        },
        'live_project2': {
            'key': 'pk_ByNNmfJfsMywEIEa-aCteTR',
            'secret': 'CPslpGmoakWdPuxjtrfibZVLaS',
            'host': 'api.pinpayments.com',
        },
    }

API keys and secrets are available from your Pin Account page. Hosts should not include https or a trailing slash; these will be added automatically.

PIN_DEFAULT_ENVIRONMENT

At runtime, the {% pin_headers %} template tag can define which environment to use. If you don't specify an environment in the template tag, this setting determines which account to use.

Default: PIN_DEFAULT_ENVIRONMENT = 'test'

Template Tags

Two template tags are included. One includes the Pin.js library and associated JavaScript, and the other renders a form that doesn't submit to your server. Both are required.

Both tags are in pin_payment_tags, so you should include {% load pin_payment_tags %} somewhere near the top of your template.

pin_headers - Render pin.js and helper functions

This tag should be called inside the head tag of your HTML page. It will render multiple <script> tags: one to load pin.js, the other to define a function that will run on submit of the form to load the card token from the Pin API.

    {% load pin_payment_tags %}
    <html>
        <head>
            <title>My Payment Page</title>
            <script src='/path/to/jquery.js'></script>
            {% pin_header "test" %}
        </head>
        <body>
            <!-- page content -->
        </body>
    </html>

The output of this tag can be overridden by modifying templates/pinpayments/pin_headers.html. The included sample utilises jQuery, which should be included in your page prior to the pin_headers template tag.

To switch to your live API tokens, change test to live.

pin_form - Render HTML form for Payment

Use pin_form to render the payment form, which includes billing address and credit card details. Your fpage should already include a form tag, the pin_headers tag, and the csrf_token.

    <form method='post' action='.' class='pin'>
        {% csrf_token %}
        <!-- your existing form -->
        {% pin_form %}
        <input type='submit' value='Process Payment'>
    </form>

By default, the template does not include name attributes on the fields so they will not be posted to your server. You can modify templates/pinpayments/pin_form.html to customise the form layout.

Models

pinpayments.PinTransaction

This model holds attempted and processed payments, including full details of the API response from Pin.

There's an assumption that you'll have your own "Order" table, with a 1:N or N:N link to PinTransaction.

To create a new Transaction in the view that receives the form submission, use the PinTransaction model along with some custom data.

    transaction = PinTransaction()
    transaction.card_token = request.POST.get('card_token')
    transaction.ip_address = request.POST.get('ip_address')
    transaction.amount = 500 # Amount in dollars. Define with your own business logic.
    transaction.currency = 'AUD' # Pin supports AUD and USD. Fees apply for currency conversion.
    transaction.description = 'Payment for invoice #12508' # Define with your own business logic
    transaction.email_address = request.user.email_address
    transaction.save()

    result = transaction.process_transaction() # Typically "Success" or an error message
    if transaction.succeeded:
        return "We got the money!"
    else:
        return "No money today :( Error message: %s " % result

You may choose to call the process_transaction() function sometime after creation of the PinTransaction, for example from a cronjob or worker queue. This is left as an exercise for the reader.

pinpayments.CustomerToken

If you do recurring billing, or if you charge a card a significant amount of time after collecting card details (at present, Pin expire card tokens after 1 month) then you need to use the Customers API to create a Customer record. A Customer can then have multiple transactions created, without collecting card details again.

In the view that receives card_token from your payment form, create a new instance of CustomerToken using the provided create_from_card_token function.

To allow users to manage their active cards, a CustomerToken record must be tied to a contrib.auth.User record. You should provide a view to review & cancel CustomerToken records.

    card_token = request.POST.get('card_token')
    user = request.user
    customer = CustomerToken.create_from_card_token(card_token, user, environment='test')

If you later want to charge this customer, just create a new PinTransaction for them.

    # Get the first active token. Big assumption that we have one we can use.
    customer = request.user.customertoken_set.filter(active=True)[0]
    transaction = PinTransaction(
        customer_token  = customer,
        email           = request.user.email,
        ip_address      = request.META.get('REMOTE_ADDR'),
        amount          = 25, # Dollars
        currency        = 'AUD',
        description     = 'Monthly payment',
    )
    transaction.save()
    result = transaction.process_transaction()
    if transaction.succeeded:
        return "We got the money!"
    else:
        return "No money today :( Error message: %s " % result

When the customer changes their credit card, you should collect that new card via the existing form and JavaScript, and call the new_card_token method on their token.

    # Get the first active token. Big assumption that we have one we can use.
    customer = request.user.customertoken_set.filter(active=True)[0]
    result = customer.new_card_token(request.POST.get('card_token')
    if result:
        return "Updated and ready to charge!"
    else:
        return "Unable to update card. Try again."

Because you're keeping the CustomerToken, you can re-bill them as often as is necessary (within your legal rights and your agreement with the customer, obviously).

Models related to Payouts

pinpayments.BankAccount

This model has limited functionality but as a storage tool. It houses the bank account information returned from Pin on the creation of bank account tokens.

pinpayments.PinRecipient

This model contains references to recipients, which are the objects that can be sent money. They can be saved using data returned from Pin, and there exists also helper functionality for generating the Pin requests from raw bank account details.

    # Given an accountholder name, BSB and account number:
    recipient = PinRecipient.objects.create_with_bank_account(
        request.user.email,   # required, string
        account_holder_name,  # required, string
        account_bsb,          # required, string or int
        account_number,       # required, string or int
        account_alias,        # optional, string
    )

pinpayments.PinTransfer

This is the equivalent of transaction, for when the mony is coming out of your pin account, and into a recipient's bank account.

Note that, unlike Transactions, Transfers is using the same currency unit as Pin This means the base unit of the currency (cents instead of dollars, pence instead of pounds, yen for JPY)

    # Given a PinRecipient with a valid token:
    transfer = PinTransfer(
        transfer_value,  # required, string or int
        description,     # required, string
        pin_recipient,   # required, PinRecipient
        currency,        # optional. AUD if not provided
    )

Warnings

The contributors and I are not responsible for anything this code does to your customers, your bank account, or your Pin account. We are providing it in good faith and provide no warranties. The above code samples are just that: Samples. Your production code should be full of testing and other ways to deal with the many errors and problems that arise from processing payments online.

All use is at your own risk.

Contributors

Contributing

Want to help improve django-pinpayments and see your name in ASCII above? Your help is welcomed! Please log issues and pull requests via GitHub https://github.com/rossp/django-pinpayments

License

Copyright (c) 2013, Ross Poulton [email protected] All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Changelog

1.1.0

  • Support for Django 3.0 and Python 3

1.0.9 (Jan 09, 2014)

  • Initial support for Payments

0.1.6 (June 11th, 2014)

  • Documentation update for CustomerToken.new_card_token

0.1.5 (June 11th, 2014)

  • Support custom user models
  • Better error handling
  • Ability to change card token for a customer (rather than generating a new CustomerToken)
  • Timezone aware dates on Transactions
  • Store cardholder name on CustomerToken.
Comments
  • Which TLS version is being used for requests?

    Which TLS version is being used for requests?

    I'm not certain that this is an issue, but given that PIN payments are no longer accepting TLS 1.0 as of 1 Jan, 2017, is there a way to easily confirm that the way that django-pinpayments does its requests to PIN's API is using TLS 1.2 (for preference)?

    I've seen that you're using the python requests library, which seems to support the later TLS versions for Python 2.7.x and 3.x, but is there anything we should do within the django-pinpayments module to ensure that TLS 1.0 is not used as the encryption method for requests to PIN?

    opened by molokov 2
  • Addition of transfers and recipients, modularisation

    Addition of transfers and recipients, modularisation

    I've added as much as is reasonable for the project I'm working on, in addition to the clean-up.

    Please note that I've bumped the versions in the setup.py and README.md, which you may want to move back to 1.0.8.

    opened by jadedarko 2
  • Add unit tests for models and template tags

    Add unit tests for models and template tags

    Added unit tests for the models (and model functions) and template tag functions. These tests are run against mocked requests and there are no live API integration tests.

    This branch also adds a dependency on Mock.

    In models.py I had to modify how the settings were requested so that instead of asking for 'pin_config' it always does a look up like "getattr(settings, 'PIN_ENVIRONMENTS', {})". This is so that the settings do not get cached inside the models module for the cases that settings are modified dynamically. This helps us to test with different settings and also won't break for people who aren't running with static settings.

    In 'process_transaction()' I removed the "if self.environment not in pin_config.keys():" as at the top of 'process_transaction()' we call save() which does the same validation and that check never gets called.

    Also in 'process_transaction()' if the response has an error there is check for "self.transaction_token = r['charge_token']". I could not reproduce a response including 'charge_token' but I may have not make the right request. I added 'charge_token' to the mocked request so that the tests pass, but wanted to check this was correct.

    I think they were the only changes that weren't bug fixes. Please let me know if I've missed anything confusing.

    Hope this is all helpful, let me know if there are things I need to fix up.

    opened by huwshimi 2
  • Multiple card support using customers endpoint

    Multiple card support using customers endpoint

    This set of changes adds support for managing multiple cards on a single customer. They have been used in production for approximately a year now, using Django 1.8

    Following the discussion in email, I've added some use cases to the documentation. I have left the version changes added by my former co-worker in-place for the time being, because I am uncertain how you would like to version these additions.

    opened by jadedarko 1
  • Increase the pin_reponse field size to accomodate larger response sizes

    Increase the pin_reponse field size to accomodate larger response sizes

    The field pin_response was too low to incorporate all responses from the server.

    For example the response 'No resource was found at this URL. See https://pin.net.au/docs/api for API documentation and a list of valid URLs.' was received which contains 114 characters.

    The field size should be increased accordingly to allow this. It has been set to 255 cautiously to allow future responses.

    opened by sjdines 0
  • Charge API: charge_token in error response?

    Charge API: charge_token in error response?

    Via @huwshimi:

    Also in 'process_transaction()' if the response has an error there is check for "self.transaction_token = r['charge_token']". I could not reproduce a response including 'charge_token' but I may have not make the right request. I added 'charge_token' to the mocked request so that the tests pass, but wanted to check this was correct.

    I'm unsure why this is in there; need to validate that it's part of the Charge API when an error occurs.

    opened by rossp 0
  • Maintainer Wanted

    Maintainer Wanted

    For a long while now I've not had any side projects that use django-pinpayments and I don't have the time or headspace to maintain an important project that other people rely on for processing payments.

    If you have experience maintaining an open source project, you use django-pinpayments on your own projects, and you can help nurture it into the next phase of it's life please get in touch with me or comment on this thread to start a discussion.

    opened by rossp 0
  • Error in PinEnvironment

    Error in PinEnvironment

    Hi Ross

    It seems there is an error in PinEnvironment. If you call pin_get('/customers', True), the second argument is being passed on to the _pin_request method as the payload argument. This raises:

    TypeError: Cannot mix str and non-str arguments

    opened by mattrowbum 1
  • Webhook support

    Webhook support

    Support Pin Webhooks. Add a urlconf and view to receive webhooks. Incoming hooks should be validated and Django Signals triggered so code can wait for these.

    (Raised by my review of what's new in Pin and a desire to get this project updated)

    new-feature 
    opened by rossp 1
  • Add support for subscriptions/plans

    Add support for subscriptions/plans

    Since this library was built, Pin have added Plans and Subscriptions

    django-pinpayments should have local models for each of these, and a way to keep local data in sync with the Pin data (perhaps a management command to sync Plans)

    (Raised by my review of what's new in Pin and a desire to get this project updated)

    new-feature 
    opened by rossp 0
  • Better currency handling

    Better currency handling

    Currency on a PinTransaction is currently a free text field. Can it be validated against the approved Pin currency list? When displaying values, can we format appropriately for that currency? (i.e. the right symbol, USD/AUD/CAD in the right spot, symbol before/after the text, etc)?

    (This has come out of my cursory review of this project, wanting to bring it up to date again)

    new-feature 
    opened by rossp 0
Owner
Ross Poulton
Ross Poulton
payu payment gateway integration for django projects

Django-PayU This package provides integration between Django and PayU Payment Gateway. Quick start Install 'django-payu' using the following command:

MicroPyramid 37 Nov 9, 2022
Django + Stripe Made Easy

dj-stripe Stripe Models for Django. Introduction dj-stripe implements all of the Stripe models, for Django. Set up your webhook endpoint and start rec

dj-stripe 1.3k Dec 28, 2022
Adyen package for django-oscar

Adyen package for django-oscar This package provides integration with the Adyen payment gateway. It is designed to work with the e-commerce framework

Oscar 13 Jan 10, 2022
PayPal integration for django-oscar. Can be used without Oscar too.

PayPal package for django-oscar This package provides integration between django-oscar and both PayPal REST API, PayPal Express (NVP) and PayPal Payfl

Oscar 146 Nov 25, 2022
A pluggable Django application for integrating PayPal Payments Standard or Payments Pro

Django PayPal Django PayPal is a pluggable application that integrates with PayPal Payments Standard and Payments Pro. See https://django-paypal.readt

Luke Plant 672 Dec 22, 2022
Forms, widgets, template tags and examples that make Stripe + Django easier.

Overview Zebra is a library that makes using Stripe with Django even easier. It's made of: zebra, the core library, with forms, webhook handlers, abst

GoodCloud 189 Jan 1, 2023
Fully Automated YouTube Channel ▶️with Added Extra Features.

Fully Automated Youtube Channel ▒█▀▀█ █▀▀█ ▀▀█▀▀ ▀▀█▀▀ █░░█ █▀▀▄ █▀▀ █▀▀█ ▒█▀▀▄ █░░█ ░░█░░ ░▒█░░ █░░█ █▀▀▄ █▀▀ █▄▄▀ ▒█▄▄█ ▀▀▀▀ ░░▀░░ ░▒█░░ ░▀▀▀ ▀▀▀░

sam-sepiol 249 Jan 2, 2023
Werkzeug has a debug console that requires a pin. It's possible to bypass this with an LFI vulnerability or use it as a local privilege escalation vector.

Werkzeug Debug Console Pin Bypass Werkzeug has a debug console that requires a pin by default. It's possible to bypass this with an LFI vulnerability

Wyatt Dahlenburg 23 Dec 17, 2022
A python tool one can extract the "hash" from a WINDOWS HELLO PIN

WINHELLO2hashcat About With this tool one can extract the "hash" from a WINDOWS HELLO PIN. This hash can be cracked with Hashcat, more precisely with

null 33 Dec 5, 2022
Euserv_extend captcha solver + pin code(Gmail)

Euserv_extend captcha solver + pin code(Gmail)

null 19 Nov 30, 2022
Automatically unpin old messages so you can always pin more!

PinRotate Automatically unpin old messages so you can always pin more! Installation You will need to install poetry to run this bot locally for develo

null 3 Sep 18, 2022
A python script to decrypt media files encrypted using the Android application 'Secret Calculator Photo Vault'. Supports brute force of PIN also.

A python script to decrypt media files encrypted using the Android application 'Secret Calculator Photo Vault'. Supports brute force of PIN also.

null 3 May 1, 2022
A python script to decrypt media files encrypted using the Android application 'Decrypting 'LOCKED Secret Calculator Vault''. Will identify PIN / pattern.

A python script to decrypt media files encrypted using the Android application 'Decrypting 'LOCKED Secret Calculator Vault''. Will identify PIN / pattern.

null 3 Sep 26, 2022
A Django app to accept payments from various payment processors via Pluggable backends.

Django-Merchant Django-Merchant is a django application that enables you to use multiple payment processors from a single API. Gateways Following gate

Agiliq 997 Dec 24, 2022
A Django app to accept payments from various payment processors via Pluggable backends.

Django-Merchant Django-Merchant is a django application that enables you to use multiple payment processors from a single API. Gateways Following gate

Agiliq 997 Dec 24, 2022
A Django app to accept payments from various payment processors via Pluggable backends.

Django-Merchant Django-Merchant is a django application that enables you to use multiple payment processors from a single API. Gateways Following gate

Agiliq 997 Dec 24, 2022
payu payment gateway integration for django projects

Django-PayU This package provides integration between Django and PayU Payment Gateway. Quick start Install 'django-payu' using the following command:

MicroPyramid 37 Nov 9, 2022
A simple page with paypal payment and confiramtion in django

django-paypal a simple page with paypal payment and confiramtion in django Youtube Video : Paypal Smart Button : https://developer.paypal.com/demo/che

Mahmoud Ahmed 5 Feb 19, 2022
Drf-stripe-subscription - An out-of-box Django REST framework solution for payment and subscription management using Stripe

Drf-stripe-subscription - An out-of-box Django REST framework solution for payment and subscription management using Stripe

Oscar Y Chen 68 Jan 7, 2023
Retrying is an Apache 2.0 licensed general-purpose retrying library, written in Python, to simplify the task of adding retry behavior to just about anything.

Retrying Retrying is an Apache 2.0 licensed general-purpose retrying library, written in Python, to simplify the task of adding retry behavior to just

Ray Holder 1.9k Dec 29, 2022