How to Make Promotional Codes Applicable on Tickets During Ordering in Open Event Frontend

This blog illustrate how to enable application of promotional codes on tickets during ordering tickets in Open Event Frontend to avail discounts and access to special tickets. Open event allows organizers to add some promotional codes on some tickets, which can be used by users to avail additional offers on tickets while ordering. Promotional codes can be of three types:

  1. Discount Codes: Allows customers to buy a ticket at discounted rates.
  2. Access Codes: Allows customers to access some hidden tickets which are accessible only to special customers.
  3. Discount + Access Code: Allows customer to access special tickets and avail discount at the same time.

Creating a discount/access code:

Organizers and admin can create an access code or a discount code from the event dashboard. They can specify the validity period of the code and can also specify the tickets on which the code will be applicable.

Validating promotional code after user enters the code:

User is allowed to enter the promotional code on events page upon selecting the tickets. IF promotional code is valid then suitable discount is provided on applicable tickets and if promotional code is an access code then hidden tickets for which the promotional code is valid are shown.

To check the validity of the promotional code we deal with the following APIs on the open event server:

  • GET             /v1/discount-codes/{Code}              (For Discount code)
  • GET             /v1/access-codes/{Code}                  (For Access code)

Code snippet to check the validity for access code is given below:

let promotionalCode = this.get('promotionalCode');
 let order = this.get('order');
   try {
     let accessCode = await this.get('store').findRecord('access-code', promotionalCode, {});
     order.set('accessCode', accessCode);
     let tickets = await accessCode.get('tickets');
     tickets.forEach(ticket => {
     ticket.set('isHidden', false);
     this.get('tickets').addObject(ticket);
     this.get('accessCodeTickets').addObject(ticket);
     this.set('invalidPromotionalCode', false);
  });
  } catch (e) {
     this.set('invalidPromotionalCode', true);
  }

 

Full code can be seen here https://github.com/fossasia/open-event-frontend/blob/development/app/components/public/ticket-list.js

Similarly for discount code we fetch the details of the discount code via the api and then validate the code. After the validation we apply the discount to the tickets applicable. Code snippet for the discount code part is given below:

try {
  let discountCode = await this.get('store').findRecord('discount-code', promotionalCode, { include: 'tickets' });
  let discountType = discountCode.get('type');
  let discountValue = discountCode.get('value');
  order.set('discountCode', discountCode);
  let tickets = await discountCode.get('tickets');
  tickets.forEach(ticket => {
     let ticketPrice = ticket.get('price');
     if (discountType === 'amount') {
       ticket.set('discount', Math.min(ticketPrice, discountValue));
       this.get('discountedTickets').addObject(ticket);
     } else {
       ticket.set('discount', ticketPrice * (discountValue / 100));
       this.get('discountedTickets').addObject(ticket);
     }
     this.set('invalidPromotionalCode', false);
  });
} catch (e) {
   if (this.get('invalidPromotionalCode')) {
      this.set('invalidPromotionalCode', true);
   }
}

 

Full code can be seen https://github.com/fossasia/open-event-frontend/blob/development/app/components/public/ticket-list.js

After promotional codes are verified we apply them to the selected tickets. In this way we apply the promotional codes to the tickets.

Resources

 

Implement Charges Endpoint in the Open Event Android App

In the Open Event Android App we first create an attendee and then the order when a user tries to buy a ticket but the end user will only know that the transaction is successful when money gets deducted from his account. This is where we use the charges endpoint of the Open Event Server to complete the payment. Let’s see how this is being done in the Open Event Android App.

We are sending a POST request from the app to the following endpoint. The order identifier is added in the url to uniquely identify the order for which we are charging the user. The body of the POST request contains the Charge object with the required information.

@POST("orders/{orderIdentifier}/charge")
fun chargeOrder(@Path("orderIdentifier") orderIdentifier: String, @Body charge: Charge): Single<Charge>

 

This is how the model class Charge looks like. We specify the type at the top. Then we can either send a stripe or paypal token to the server which contains all the details of the payment. Message and status fields are returned from the server after the getting a success response. The status is true if the payment has been successfully made otherwise it is false. The message field gives us the feedback about what kind of error we got if the payment was not successful otherwise we get a success response.

@Type("charge")
data class Charge(
@Id(IntegerIdHandler::class)
val id: Int,
val stripe: String? = null,
val paypal: String? = null,
val message: String? = null,
val status: Boolean? = null
)

 

This is how we send the stripe token to the server. If the card details are correct then we receive the stripe token in the onSuccess method and send it to the server using the Charges endpoint.

override fun onSuccess(token: Token) {
//Send this token to server
val charge = Charge(attendeeFragmentViewModel.getId().toInt(), token.id, null)
attendeeFragmentViewModel.completeOrder(charge)

}

 

This is the function that is being used to send the POST request. It takes a charge object as an argument and then we use that in the chargeOrder method. The orderIdentifier variable that we see in the chargeOrder function is a lateinit variable that gets initialized when an order has been returned from the server. When this request is being made in the background thread we show user the progress bar. When we receive a success response from the server, we update the value of the message variable with the value returned from the server and show it to the user. If the value of the status is true that means that the payment has been successfully completed.

fun completeOrder(charge: Charge) {
compositeDisposable.add(orderService.chargeOrder(orderIdentifier.toString(), charge)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {
progress.value = true
}.doFinally {
progress.value = false
}.subscribe({
message.value = it.message
paymentCompleted.value = it.status

if (it.status != null && it.status) {
Timber.d("Successfully charged for the order!")
} else {
Timber.d("Failed charging the user")
}
}, {
message.value = "Payment not completed!"
Timber.d(it, "Failed charging the user")
}))
}

Resources

  1. ReactiveX official documentation: http://reactivex.io/
  2. Vogella RxJava 2 – Tutorial: http://www.vogella.com/tutorials/RxJava/article.html
  3. Androidhive RxJava Tutorial: https://www.androidhive.info/RxJava/

Generating a Stripe token in the Open Event Android App

To implement the payment functionality in the Open Event Android App using credit cards we are using Stripe. Let’s see how this is being done.

We are taking the sensitive information about the user like the card details and sending it to Stripe’s servers which will return a token encrypting users information which we will use to send it to the Open Event Server to complete the payment.

We need to add the library in the build.gradle file in the dependency block.

//Stripe
implementation 'com.stripe:stripe-android:6.1.2'

 

Next we add Stripe’s Card Input Widget in our layout file. This widget is used to input card details like the card number, expiry date and CVC. It automatically validates the card details. It has a minimum width of 320 px. By default we are setting it’s visibility to gone that is it won’t be visible unless the user selects the payment option.

<com.stripe.android.view.CardInputWidget
android:id="@+id/cardInputWidget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />

 

The visibility of the input field for card details is determined here. We want to show the Input widget only when the user selects Stripe as the mode of payment.

override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
selectedPaymentOption = paymentOptions[p2]
if (selectedPaymentOption == "Stripe")
rootView.cardInputWidget.visibility = View.VISIBLE
else
rootView.cardInputWidget.visibility = View.GONE
}

 

Next we store the user’s card details in a variable. If any of the details be it card number, expiry date or CVC isn’t correct, the value of the variable becomes null and then we show a message to the user.

val cardDetails: Card? = cardInputWidget.card

if (cardDetails == null)
Toast.makeText(context, "Invalid card data", Toast.LENGTH_LONG).show()

 

This is the most important part where we receive the Stripe token. We are sending a request in a background thread to the Stripe servers using the Stripe API_KEY. In the onSuccess method we receive the token if everything went successfully while in the onError method we display the errors to the user.

cardDetails?.let {
context?.let { contextIt ->
Stripe(contextIt).createToken(
it,
API_KEY,
object : TokenCallback {
override fun onSuccess(token: Token) {
//Send this token to server
Toast.makeText(context, "Token received from Stripe", Toast.LENGTH_LONG).show()
}

override fun onError(error: Exception) {
Toast.makeText(context, error.localizedMessage.toString(), Toast.LENGTH_LONG).show()
}
})
}
}

 

Resources

  1. Stripe Android documentation: https://stripe.com/docs/mobile/android  
  2. Stripe Documentation: https://stripe.com/docs/quickstart
  3. Stripe Android Github: https://github.com/stripe/stripe-android

Stripe Authorization in Open Event Server

Stripe is a popular software platform for online payments. Since Open Event  allows the event organizers to sell tickets, an option to accept payments through Stripe is extremely beneficial to the organizer. Stripe allows accepting payments on other’s behalf using Connect. Connect is the Stripe’s full stack solution for platforms that need to process payments and process to multiple parties. This blog post goes over how Event organizers are able to link their Stripe accounts in order to accept payments later.

Registering the platform

The Admin of the Open Event Server will create an account on Stripe and register the platform. Upon creating the  account he/she will get a secret_key and publishable_key.  Moreover on registering the platform a client_id will be provided. These keys are saved in the application settings and only the Admin is authorized to view or change them.

Connecting the Organiser’s account

The Open Event Frontend has a wizard for creating an Event. It provides the organiser an option to connect his/her Stripe account in order to accept payments.

Upon clicking the following button, the organiser is directed to Stripe where he/she can fill the required details.  

The button directs the organizer to the following URL:

https://connect.stripe.com/oauth/authorize?response_type=code&client_id=client_id&scope=read_write&redirect_uri=redirect_uri 

The above URL has the following parameters:

  • client_id – The client ID acquired when registering your platform.required.
  • response_type – Response type. The value is always code. required.
  • redirect_uri – The URL to redirect the customer to after authorization.
  • scope – We need it to be read_write in order to be able to charge on behalf of the customer later.

After successfully entering the required details, the organizer is redirected to the redirect_url as specified in the above URL with a query parameter named as authorization_code. The Frontend sends this code to the Server using the Stripe Authorization endpoint which will be discussed in detail below.

Fetching Tokens from Stripe

The Server accepts the authorization_code by exposing the Stripe Authorization endpoint. It then uses it to fetch organizer’s details and token from Stripe and stores it for future use.

The schema for Stripe Authorization is extremely simple. We require the client to send an authorization_code which will be used to fetch the details. Stripe_publishable_key of the event organizer is exposed via the endpoint and will be used by the Frontend later.

class StripeAuthorizationSchema(Schema):
    """
        Stripe Authorization Schema
    """

    class Meta:
        """
        Meta class for StripeAuthorization Api Schema
        """
        type_ = 'stripe-authorization'
        self_view = 'v1.stripe_authorization_detail'
        self_view_kwargs = {'id': '<id>'}
        inflect = dasherize

    id = fields.Str(dump_only=True)
    stripe_publishable_key = fields.Str(dump_only=True)
    stripe_auth_code = fields.Str(load_only=True, required=True)

    event = Relationship(attribute='event',
                self_view='v1.stripe_authorization_event',
                self_view_kwargs={'id': '<id>'},
                related_view='v1.event_detail',
                related_view_kwargs={'stripe_authorization_id':                      '<id>'},
                schema="EventSchema",
                type_='event')

We use the Requests library in order to fetch the results. First we fetch the client_id that we had stored in the application settings using a helper method called get_credentials. We then use it along with the authorization_code in order to make a POST request to Stripe Connect API. The full method is given below for reference.

@staticmethod
def get_event_organizer_credentials_from_stripe(stripe_auth_code):
        """
        Uses the stripe_auth_code to get the other credentials for the event organizer's stripe account
        :param stripe_auth_code: stripe authorization code
        :return: response from stripe
        """
        credentials = StripePaymentsManager.get_credentials()

        if not credentials:
            raise Exception('Stripe is incorrectly configured')

        data = {
            'client_secret': credentials['SECRET_KEY'],
            'code': stripe_auth_code,
            'grant_type': 'authorization_code'
        }

        response = requests.post('https://connect.stripe.com/oauth/token', data=data)
        return json.loads(response.text)

We call the above method before creating the object using the before_create_object method of Marshmallow which allows us to do data preprocessing and validations.

If the request was a success, the response from Stripe connect API includes all the details necessary to accept payments on their behalf. We add these fields to the data and save it in the database.

{
  "token_type": "bearer",
  "stripe_publishable_key": PUBLISHABLE_KEY,
  "scope": "read_write",
  "livemode": false,
  "stripe_user_id": USER_ID,
  "refresh_token": REFRESH_TOKEN,
  "access_token": ACCESS_TOKEN
}

In case there was an error, an error_description would be returned. This error_description is sent back to the frontend and shown to the event organizer.

{
  "error": "invalid_grant",
  "error_description": "Authorization code already used:                                               
                        AUTHORIZATION_CODE"
}

After successfully fetching the results, we save it inside the database and return the stripe_publishable_key which will be used by the Frontend when charging the ticket buyers later.

Lastly we can go over the Stripe Authorization model as well. The stripe_secret_key will be used to charge the customers later.

id = db.Column(db.Integer, primary_key=True)
stripe_secret_key = db.Column(db.String)
stripe_refresh_token = db.Column(db.String)
stripe_publishable_key = db.Column(db.String)
stripe_user_id = db.Column(db.String)
stripe_auth_code = db.Column(db.String)

References

 

Using Order Endpoints in Open Event API Server

The main feature i.e., Ordering API is added into API server. These endpoints provide the ability to work with the ordering system. This API is not simple like other as it checks for the discount codes and various other things as well.
The process in layman terms is very simple, first, a user must be registered or added as an attendee into Server without any order_id associated and then the attendee details will be sent to API server as a relationship.

Things needed to take care:

  1. Validating the discount code and ensure it is not exhausted
  2. Calculating the total amount on the server side by applying coupon
  3. Do not calculate amount if the user is the event admin
  4. Do not use coupon if user is event admin
  5. Handling payment modes and generating payment links
  6. Ensure that default status is always pending, unless the user is event admin

Creating Order

    • Prerequisite
      Before initiating the order, attendee records needs to be created associated with the event. These records will not have any order_id associated with them initially. The Order API will add the relationships.
    • Required Body
      Order API requires you to send event relationship and attendee records to create order_tickets
    • Permissions
      Only organizers can provide custom amount and status. Others users will get their status as pending and amount will be recalculated in server. The response will reflect the calculated amount and updated status.
      Also to initiate any order, user must be logged in. Guest can not create any order
    • Payment Modes
      There are three payment modes, free, stripe and paypal. If payment_mode is not provided then API will consider it as “free”.
    • Discount Codes
      Discount code can be sent as a relationship to the API. The Server will validate the code and will act accordingly.

Validating Discount Codes

Discount codes are checked to ensure they are valid, first check ensures that the user is not co-organizer

# Apply discount only if the user is not event admin
if data.get('discount') and not has_access('is_coorganizer', event_id=data['event']):

Second, check ensures that the discount code is active

if not discount_code.is_active:
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code")

The third, Check ensures its validity is not expired

if not (valid_from <= now <= valid_till):
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Inactive Discount Code")

Fourth Check ensure that the quantity is not exhausted

if not TicketingManager.match_discount_quantity(discount_code, data['ticket_holders']):
  raise UnprocessableEntity({'source': 'discount_code_id'}, 'Discount Usage Exceeded')

Lastly, the fifth check ensures that event id matches with given discount associated event

if discount_code.event.id != data['event'] and discount_code.user_for == TICKET:
  raise UnprocessableEntity({'source': 'discount_code_id'}, "Invalid Discount Code")

Calculating Order Amount

The next important thing is to recalculate the order amount and it will calculated only if user is not the event admin

if not has_access('is_coorganizer', **view_kwargs):
  TicketingManager.calculate_update_amount(order)

API Response

The API response apart from general fields will provide you the payment-url depending upon the payment mode you selected.

  • Stripe : will give payment-url as stripe
  • Paypal: will provide the payment completing url in payment-url

This all explains the flow and requirements to create an order. Order API consists of many more things related with TIcketing Manager which works to create the payment url and apply discount count as well as calculate the total order amount.

Resources

  1. Stripe Payments API Docs
    https://stripe.com/docs/api
  2. Paypal Payments API docs
    https://developer.paypal.com/docs/api/
  3. Paypal Sandbox docs
    https://developer.paypal.com/docs/classic/lifecycle/ug_sandbox/

 

Implementing Payment and Tax System for Open-Event

So I implemented the payment system and tax system for the payment part in the ticketing system. The first step was making a list of available countries and currency options for the system. So I created and added the following list:

PAYMENT_COUNTRIES = {
    'United States',
    'Argentina',
    'Australia',
    'Austria',
    'Belgium',
    'Brazil',
    'Canada',
    'Cyprus',
    'Czech Republic',
    'Denmark',
    'Estonia',
    'Finland',
    'France',
    'Germany',
    'Greece',
    'Hong Kong',
    'Hungary',
    'Ireland',
    'Israel',
    'Italy',
    'Japan',
    'Latvia',
    'Lithuania',
    'Luxemborg',
    'Malaysia',
    'Malta',
    'Mexico',
    'Netherlands',
    'New Zealand',
    'Norway',
    'Philippines',
    'Poland',
    'Portugal',
    'Singapore',
    'Slovakia',
    'Slovenia',
    'Spain',
    'Sweden',
    'Switzerland',
    'Taiwan',
    'United Kingdom',
}

PAYMENT_CURRENCIES = {
    'ARS Argentine Peso $',
    'AUD Australian Dollars A$',
    'BRL Brazilian Real R$',
    'CAD Canadian Dollars C$',
    'CZK Czech Koruna Kč',
    'DKR Danish Krone Dkr',
    'EUR Euros €',
    'HKD Hong Kong Dollar HK$',
    'HUF Hungarian Forint Ft',
    'ILS Israeli Shekels ₪',
    'JPY Japanese Yen ¥',
    'MYR Malaysian Ringgits RM',
    'MXN Mexican Pesos Mex$',
    'NZD New Zealand Dollar NZ$',
    'NOK Norwegian Krone Nkr',
    'PHP Philippine Pesos ₱',
    'PLN Polish Zloty zł',
    'GBP Pounds Sterling £',
    'SGD Singapore Dollar SG$',
    'SEK Swedish Krona Skr',
    'CHF Swiss Franc Fr',
    'TWD Taiwan New Dollars NT$',
    'THB Thai baht ฿',
    'USD U.S. Dollars $',
}

This is the list of currently supported countries and currencies. Thus the user can choose from the above list in the following step on the first page of event creation:

1

If the user chooses a Paid ticket or a Donation ticket then he/she has to compulsorily choose a country and a currency for the event. Next in line is the system of payments – the way in which the organizer wants payments to be made for his/her event. They include the option of Online Payments and Offline Payments. The organizer has to tick the checkbox in order to enable the payment option. On enabling the PayPal and Stripe checkboxes he/she is represented with the following:

2

On enabling PayPal option the organizer has to enter the PayPal email and for Stripe he has to connect it with Stripe account.

And the final step is the addition of tax for the event. The organizer can choose whether he/she wants to enable tax for the event or not:

3

On choosing Yes he is presented with the tax form:

4

If the organizer wants to send invoices then on enabling invoices another form is displayed with details pertaining to Business Address, Registered Name etc….

5

Finally we have two options that whether we want to display the tax as separate fee or include in the price of tickets.

PayPal Express Checkout in Python

As per the PayPal documentation …

Express Checkout is a fast, easy way for buyers to pay with PayPal. Express Checkout eliminates one of the major causes of checkout abandonment by giving buyers all the transaction details at once, including order details, shipping options, insurance choices, and tax totals.

The basic steps for using express checkout to receive one-time payments are:

  1. Getting the PayPal API credentials.
  2. Making a request to the API with the transaction details to get a token
  3. Using the token to send the users to the PayPal payment page
  4. Capturing the payment and charging the user after the user completes the payment at PayPal.

We will be using PayPal’s Classic NVP (Name-value pair) API for implementing this.

Getting PayPal API Credentials

To begin with, we’ll need API Credentials.
We’ll be using the Signature API credentials which consists of

  • API Username
  • API Password
  • Signature

To obtain these, you can follow the steps at Creating and managing NVP/SOAP API credentials – PayPal Developer.

You’ll be getting two sets of credentials. Sandbox and Live. We’ll just stick to the Sandbox for now.

Now, we need sandbox test accounts for making and receiving payments. Head over to Creating Sandbox Test Accounts – PayPal Developer and create two sandbox test accounts. One would be the facilitator and one would be the buyer.

PayPal NVP Servers

All the API actions will take place by making a request to the PayPal server. PayPal has 4 different NVP servers for 4 different purposes.

  1. https://api-3t.sandbox.paypal.com/nvp – Sandbox “testing” server for use with API signature credentials.
  2. https://api-3t.paypal.com/nvp– PayPal “live” production server for use with API signature credentials.
  3. https://api.sandbox.paypal.com/nvp – Sandbox “testing” server for use with API certificate credentials.
  4. https://api.paypal.com/nvp – PayPal “live” production server for use with API certificate credentials.

We’ll be using the Sandbox “testing” server for use with API signature credentials.

Creating a transaction and obtaining the token

To create a transaction, we’ll need to make a request with all the transaction details. We can use Python requests library to easily make the requests. All requests are POST.

We’ll be calling the SetExpressCheckout method of the NVP API to obtain the token.

import requests  
import urlparse

data = {  
    'USER': credentials['USER'],
    'PWD': credentials['PWD'],
    'SIGNATURE': credentials['SIGNATURE'],
    'SUBJECT': credentials['FACILITATOR_EMAIL'],
    'METHOD': 'SetExpressCheckout',
    'VERSION': 93,
    'PAYMENTREQUEST_0_PAYMENTACTION': 'SALE',
    'PAYMENTREQUEST_0_AMT': 100,
    'PAYMENTREQUEST_0_CURRENCYCODE': 'USD',
    'RETURNURL': 'http://localhost:5000/paypal/return/',
    'CANCELURL': 'http://localhost:5000/paypal/cancel/'
}
response = requests.post('https://api-3t.sandbox.paypal.com/nvp', data=data)  
token = dict(urlparse.parse_qsl(response.text))['TOKEN']

Here,

  • USER represents your Sandbox API Username.
  • PWD represents your Sanbox API Password.
  • SIGNATURE represents your Sandbox Signature.
  • SUBJECT represents the facilitator’s email ID.
  • PAYMENTREQUEST_0_AMT is the total transaction amount.
  • PAYMENTREQUEST_0_CURRENCYCODE is the 3 digit ISO 4217 Currency code.
  • RETURNURL is where the user will be sent to after the transaction
  • CANCELURL is where the user will be sent to if he/she cancels the transaction.

A URL-Encoded, Name-value pair response would be obtained. We can decode that into a dict by using Python’s urlparse modules.

From the response, we’re extracting the TOKEN which we will use to generate the payment URL for the user.

This token has to be retained since we’ll be using it in further steps of the process.

Redirecting the user to PayPal for Approval

With the token we obtained, we can form the payment URL.

https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=<TOKEN>

We’ll have to send the user to that URL. Once the user completes the transaction at PayPal, he/she will be returned to the RETURNURL where we’ll further process the transaction.

Obtaining approved payment details and capturing the payment

Once the user completes the transaction and gets redirected back to RETURNURL, we’ll have to obtain the confirmed payment details from PayPal. For that we can again use the token ID that we obtained before.

We’ll now be making a request to the GetExpressCheckoutDetails method of the API.

import requests  
import urlparse

data = {  
    'USER': credentials['USER'],
    'PWD': credentials['PWD'],
    'SIGNATURE': credentials['SIGNATURE'],
    'SUBJECT': credentials['FACILITATOR_EMAIL'],
    'METHOD': 'GetExpressCheckoutDetails',
    'VERSION': 93,
    'TOKEN': TOKEN
}

response = requests.post('https://api-3t.sandbox.paypal.com/nvp', data=data)  
result = dict(urlparse.parse_qsl(response.text))  
payerID = result['PAYERID']

A URL-Encoded, Name-value pair response would be obtained. We can decode that into a dict by using Python’s urlparse modules.

This will provide us with information about the transaction such as transaction time, transaction amount, charges, transaction mode, etc.

But, we’re more interested in the PAYERID which we’ll need to capture/collect the payment. The money is not transferred to the facilitators account until it is captured/collected. So, be sure to collect it.

To collect it, we’ll be making another request to the DoExpressCheckoutPaymentmethod of the API using the token and the PAYERID.

import requests  
import urlparse

data = {  
    'USER': credentials['USER'],
    'PWD': credentials['PWD'],
    'SIGNATURE': credentials['SIGNATURE'],
    'SUBJECT': credentials['FACILITATOR_EMAIL'],
    'METHOD': 'DoExpressCheckoutPayment',
    'VERSION': 93,
    'TOKEN': TOKEN,
    'PAYERID': payerID,
    'PAYMENTREQUEST_0_PAYMENTACTION': 'SALE',
    'PAYMENTREQUEST_0_AMT': 100,
    'PAYMENTREQUEST_0_CURRENCYCODE': 'USD',
}

response = requests.post('https://api-3t.sandbox.paypal.com/nvp', data=data)  
result = dict(urlparse.parse_qsl(response.text))  
status = result['ACK']

All the details have to be the same as the ones provided while obtaining the token. Once we make the request, we’ll again get a URL-Encoded, Name-value pair response. We can decode that into a dict by using Python’s urlparsemodules.

From the response, ACK (Acknowledgement status) will provide us with the status of the payment.

  • Success — A successful operation.
  • SuccessWithWarning — A successful operation; however, there are messages returned in the response that you should examine.
  • Failure — The operation failed; the response also contains one or more error messages explaining the failure.
  • FailureWithWarning — The operation failed and there are messages returned in the response that you should examine.

And, we have completed the PayPal transaction flow for Express Checkout. These are just the basics and might miss a few stuff. I suggest you go through the following links too for a better understanding of everything:

For Reference:
  1. PayPal Name-Value Pair API Basics – PayPal Developer
  2. How to Create One-Time Payments Using Express Checkout – PayPal Developer

Ticketing System in Open-Event

So we implemented the ticketing system in the open-event. Basically we provide the user with two options – either add his/her own ticket url or use our own ticketing system. If the ticketing module is turned off then there is no option and the user has to add a Ticket URL.

1

2

Thus only Add Ticket URL is shown if the ticketing switch is turned off. However if the ticket switch is turned ON then we display our own ticketing system i.e. provide with an option to choose to the user.

3

Now the ticket feature can be either Free, Paid or by donation. If the ticket feature is free then just the normal ticketing details are entered by the user. However if Paid option is selected then a payment system is displayed to the user  where he/she has to choose the country and the currency in which the user will make the payment.

4

The user can pay through PayPal and we can also decide whether we want to add Tax to the event or not.