Skip to content

[WIP] feat: Add PayPal payment configuration #1813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ apply plugin: 'androidx.navigation.safeargs.kotlin'

def STRIPE_API_TOKEN = System.getenv('STRIPE_API_TOKEN') ?: "YOUR_API_KEY"
def MAPBOX_KEY = System.getenv('MAPBOX_KEY') ?: "pk.eyJ1IjoiYW5nbWFzMSIsImEiOiJjanNqZDd0N2YxN2Q5NDNuNTBiaGt6eHZqIn0.BCrxjW6rP_OuOuGtbhVEQg"
def PAYPAL_CLIENT_ID = System.getenv('PAYPAL_CLIENT_ID') ?: "NOT_PROVIDED"

android {
dataBinding {
Expand Down Expand Up @@ -39,11 +40,13 @@ android {
buildConfigField "String", "DEFAULT_BASE_URL", '"https://api.eventyay.com/v1/"'
buildConfigField "String", "MAPBOX_KEY", '"'+MAPBOX_KEY+'"'
buildConfigField "String", "STRIPE_API_KEY", '"'+STRIPE_API_TOKEN+'"'
buildConfigField "String", "PAYPAL_CLIENT_ID", '"'+PAYPAL_CLIENT_ID+'"'
}
debug {
buildConfigField "String", "DEFAULT_BASE_URL", '"https://open-event-api-dev.herokuapp.com/v1/"'
buildConfigField "String", "MAPBOX_KEY", '"'+MAPBOX_KEY+'"'
buildConfigField "String", "STRIPE_API_KEY", '"'+STRIPE_API_TOKEN+'"'
buildConfigField "String", "PAYPAL_CLIENT_ID", '"'+PAYPAL_CLIENT_ID+'"'
}
}

Expand Down Expand Up @@ -163,6 +166,9 @@ dependencies {
// Stripe
implementation 'com.stripe:stripe-android:9.0.1'

//PayPal
implementation 'com.paypal.sdk:paypal-android-sdk:2.16.0'

// QR Code
implementation 'com.journeyapps:zxing-android-embedded:3.6.0'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.fossasia.openevent.general.attendees

import android.app.Activity
import androidx.appcompat.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.telephony.TelephonyManager
import android.text.Editable
Expand All @@ -18,6 +20,7 @@ import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
Expand All @@ -26,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.navigation.Navigation.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.textfield.TextInputLayout
import com.paypal.android.sdk.payments.*
import com.stripe.android.Stripe
import com.stripe.android.TokenCallback
import com.stripe.android.model.Card
Expand Down Expand Up @@ -82,6 +86,11 @@ import java.util.Currency
import org.fossasia.openevent.general.utils.Utils.setToolbar
import org.jetbrains.anko.design.longSnackbar
import org.jetbrains.anko.design.snackbar
import timber.log.Timber
import java.math.BigDecimal

private const val STRIPE = "Stripe"
private const val PAYPAL = "PayPal"

class AttendeeFragment : Fragment() {

Expand All @@ -94,23 +103,26 @@ class AttendeeFragment : Fragment() {

private lateinit var eventId: EventId
private var ticketIdAndQty: List<Pair<Int, Int>>? = null
private var selectedPaymentOption: Int = -1
private lateinit var selectedPaymentOption: String
private lateinit var paymentCurrency: String
private var expiryMonth: Int = -1
private var expiryYear: String? = null
private var cardBrand: String? = null
private lateinit var API_KEY: String
private lateinit var STRIPE_API_KEY: String
private lateinit var PAYPAL_API_KEY: String
private var singleTicket = false
private var identifierList = ArrayList<String>()
private var editTextList = ArrayList<EditText>()
private var amount: Float = 0.0f
private val PAYPAL_REQUEST_CODE = 1

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
eventId = EventId(safeArgs.eventId)
ticketIdAndQty = safeArgs.ticketIdAndQty?.value
singleTicket = ticketIdAndQty?.map { it.second }?.sum() == 1
API_KEY = BuildConfig.STRIPE_API_KEY
STRIPE_API_KEY = BuildConfig.STRIPE_API_KEY
PAYPAL_API_KEY = BuildConfig.PAYPAL_CLIENT_ID
}

override fun onCreateView(
Expand Down Expand Up @@ -198,8 +210,8 @@ class AttendeeFragment : Fragment() {
}

override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
selectedPaymentOption = position
if (position == paymentOptions.indexOf(getString(R.string.stripe)))
selectedPaymentOption = paymentOptions[position]
if (selectedPaymentOption == STRIPE)
rootView.stripePayment.visibility = View.VISIBLE
else
rootView.stripePayment.visibility = View.GONE
Expand Down Expand Up @@ -461,12 +473,14 @@ class AttendeeFragment : Fragment() {

if (attendeeViewModel.areAttendeeEmailsValid(attendees)) {
val country = rootView.countryPicker.selectedItem.toString()
attendeeViewModel.createAttendees(attendees, country, paymentOptions[selectedPaymentOption])
attendeeViewModel.createAttendees(attendees, country, selectedPaymentOption)

attendeeViewModel.isAttendeeCreated.observe(viewLifecycleOwner, Observer { isAttendeeCreated ->
if (isAttendeeCreated && selectedPaymentOption ==
paymentOptions.indexOf(getString(R.string.stripe))) {
sendToken()
if (isAttendeeCreated) {
if (selectedPaymentOption == STRIPE)
sendToken()
if (selectedPaymentOption == PAYPAL)
startPayPal()
}
})
} else {
Expand Down Expand Up @@ -518,7 +532,7 @@ class AttendeeFragment : Fragment() {
rootView.snackbar(getString(R.string.invalid_card_data_message))
else
Stripe(requireContext())
.createToken(card, API_KEY, object : TokenCallback {
.createToken(card, STRIPE_API_KEY, object : TokenCallback {
override fun onSuccess(token: Token) {
// Send this token to server
val charge = Charge(attendeeViewModel.getId().toInt(), token.id, null)
Expand All @@ -531,6 +545,36 @@ class AttendeeFragment : Fragment() {
})
}

private fun startPayPal() {
val payPalConfiguration = PayPalConfiguration()
.environment(PayPalConfiguration.ENVIRONMENT_PRODUCTION) //Set to LIVE API CREDENTIALS
.clientId(PAYPAL_API_KEY) //LIVE API Client ID
Toast.makeText(context, amount.toString(), Toast.LENGTH_SHORT).show()
val payment = PayPalPayment(BigDecimal(amount.toString()), paymentCurrency, "Pay for tickets",
PayPalPayment.PAYMENT_INTENT_SALE)
val intent = Intent(context, PaymentActivity::class.java)
intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, payPalConfiguration)
activity?.startService(intent)
intent.putExtra(PaymentActivity.EXTRA_PAYMENT, payment)
startActivityForResult(intent, PAYPAL_REQUEST_CODE)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == PAYPAL_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
val paymentConfirmation = data?.getParcelableExtra<PaymentConfirmation>(PaymentActivity.EXTRA_RESULT_CONFIRMATION)
if (paymentConfirmation != null) {
val paymentInfo = paymentConfirmation.toJSONObject()
Timber.d(paymentInfo.toString(4))
// TODO: Send token to server and redirect to tickets
Copy link
Member Author

@liveHarshit liveHarshit May 22, 2019

Choose a reason for hiding this comment

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

@iamareebjamal I need help here. I'm getting following response -

{
                        "client": {
                            "environment": "sandbox",
                            "paypal_sdk_version": "2.16.0",
                            "platform": "Android",
                            "product_name": "PayPal-Android-SDK"
                        },
                        "response": {
                            "create_time": "2018-12-30T19:59:20Z",
                            "id": "PAY-0JW58086WS3706240LQUSHEQ",
                            "intent": "sale",
                            "state": "approved"
                        },
                        "response_type": "payment"
                    }

And according to api documentation for paypal payment request -

Request

Body
{
  "data": {
    "attributes": {
      "return_url": "https://localhost:5000/return_url",
      "cancel_url": "https://localhost:5000/cancel_url"
    },
    "type": "paypal-payment"
  }
}

How to send confirmation token to the server?

Copy link
Member

Choose a reason for hiding this comment

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

Are you using Braintree SDK?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, It's PayPal Android SDK.

Copy link
Member

Choose a reason for hiding this comment

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

Will see

Copy link
Contributor

Choose a reason for hiding this comment

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

@liveHarshit from what I understand, the server already implements everything it needs, for now, For a Paypal payment, 2 things are needed: A Payer ID and A Payment ID. The Payment ID is generated by the server and I can see that you can get it from PaymentConfirmation above. For Payer ID, please check if you can generate it with the Paypal SDK, probably there should be a Paypal button for the user to log in or some similar method. After you have the Payer ID and the Payment ID, send a Charge object to the server. I think it should work, I will try Paypal SDK for Android to see if I understand it correctly

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it is the ID of who pay the payment, and it is unchanged while for PayPal user email or relevant information can be changed

Copy link
Member Author

@liveHarshit liveHarshit Jun 29, 2019

Choose a reason for hiding this comment

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

I think we should Braintree. As PayPal andorid SDK is already Deprecated and suggesting for Braintree.
Read comments in this question: https://stackoverflow.com/questions/38229929/getting-the-payerid-from-paypal-android-sdk

Copy link
Member Author

Choose a reason for hiding this comment

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

As it is configured on the server, I don't think we can use it on Android. It is configured to accept approval url.
https://github.com/paypal/PayPal-node-SDK/issues/24

Copy link
Contributor

Choose a reason for hiding this comment

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

let's me spend a bit more time on this and then we can discuss what solution should we go on this tomorrow meeting, besides, I also think Braintree is better as the old documentation is deleted

Copy link
Member Author

Choose a reason for hiding this comment

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

I also think Braintree is better as the old documentation is deleted

Exactly, that is the problem. PayPal has deleted the documentation related to that and archive the sdk repo.

}
} else if (resultCode == Activity.RESULT_CANCELED)
rootView.snackbar("Payment cancelled")
else if (resultCode == PaymentActivity.RESULT_EXTRAS_INVALID)
rootView.snackbar("Invalid Payment Configuration")
}
}

private fun loadEventDetails(event: Event) {
val dateString = StringBuilder()
val startsAt = EventUtils.getEventDateTime(event.startsAt, event.timezone)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ val apiModule = module {
factory { TicketService(get(), get()) }
factory { SocialLinksService(get(), get()) }
factory { AttendeeService(get(), get(), get()) }
factory { OrderService(get(), get(), get()) }
factory { OrderService(get(), get(), get(), get()) }
factory { SessionService(get(), get()) }
factory { NotificationService(get()) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package org.fossasia.openevent.general.order
import io.reactivex.Single
import org.fossasia.openevent.general.attendees.Attendee
import org.fossasia.openevent.general.attendees.AttendeeDao
import org.fossasia.openevent.general.paypal.CreatePaypalPaymentResponse
import org.fossasia.openevent.general.paypal.Paypal
import org.fossasia.openevent.general.paypal.PaypalApi

class OrderService(
private val orderApi: OrderApi,
private val orderDao: OrderDao,
private val attendeeDao: AttendeeDao
private val attendeeDao: AttendeeDao,
private val paypalApi: PaypalApi
) {
fun placeOrder(order: Order): Single<Order> {
return orderApi.placeOrder(order)
Expand Down Expand Up @@ -53,4 +57,8 @@ class OrderService(
attendeeDao.getAttendeesWithIds(attendeesIds)
}
}

fun createPayPal(orderIdentifier: String, paypal: Paypal): Single<CreatePaypalPaymentResponse> {
return paypalApi.createPaypalPayment(orderIdentifier, paypal)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming
data class CreatePaypalPaymentResponse(
val status: Boolean,
val paymentId: String,
val error: String
val error: String? = null
)
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package org.fossasia.openevent.general.paypal

import io.reactivex.Single
import retrofit2.http.Body
import retrofit2.http.POST
import retrofit2.http.Path

interface PaypalApi {

@POST("orders/{orderIdentifier}/create-paypal-payment")
fun createPaypalPayment(@Path("orderIdentifier") orderIdentifier: String, @Body paypal: Paypal)
fun createPaypalPayment(@Path("orderIdentifier") orderIdentifier: String, @Body paypal: Paypal):
Single<CreatePaypalPaymentResponse>
}