Skip to content

Added new payment api #48

Merged
merged 1 commit into from
Oct 18, 2018
Merged
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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
buildscript {
ext.kotlin_version = '1.2.71'
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/ru/tinkoff/acquiring/sdk/CardData.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ public CardData(String pan, String expiryDate, String securityCode) {
this.securityCode = securityCode;
}

public CardData(String pan, String expiryDate, String securityCode, String cardId, String rebillId) {
this.pan = pan;
this.expiryDate = expiryDate;
this.securityCode = securityCode;
this.cardId = cardId;
this.rebillId = rebillId;
}

public CardData(String cardId, String securityCode) {
this.securityCode = securityCode;
this.cardId = cardId;
Expand Down Expand Up @@ -95,6 +103,10 @@ public void setSecurityCode(String securityCode) {
this.securityCode = securityCode;
}

public String getCardId() {
return cardId;
}

public String getRebillId() {
return rebillId;
}
Expand Down
1 change: 1 addition & 0 deletions payment/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
34 changes: 34 additions & 0 deletions payment/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 28



defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project(':core')
implementation project(':ui')
}

repositories {
mavenCentral()
}
21 changes: 21 additions & 0 deletions payment/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
2 changes: 2 additions & 0 deletions payment/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.payment" />
10 changes: 10 additions & 0 deletions payment/src/main/java/ru/tinkoff/acquiring/payment/PaymentData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ru.tinkoff.acquiring.payment

/**
* @author Stanislav Mukhametshin
*/
data class PaymentData(val orderId: String,
val coins: Long,
val recurrentPayment: Boolean = false,
val chargeMode: Boolean,
val language: String? = null)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ru.tinkoff.acquiring.payment

import ru.tinkoff.acquiring.sdk.Card
import ru.tinkoff.acquiring.sdk.PaymentInfo
import ru.tinkoff.acquiring.sdk.ThreeDsData

/**
* @author Stanislav Mukhametshin
*/
class PaymentDataUi internal constructor() {

internal var paymentInfo: PaymentInfo? = null
internal var recurrentPayment: Boolean = false
internal var card: Card? = null
internal var threeDsData: ThreeDsData? = null
var status: Status? = null

enum class Status {
REJECTED,
THREE_DS_NEEDED
}
}
162 changes: 162 additions & 0 deletions payment/src/main/java/ru/tinkoff/acquiring/payment/PaymentProcess.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package ru.tinkoff.acquiring.payment

import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import android.os.Message
import ru.tinkoff.acquiring.sdk.*
import ru.tinkoff.acquiring.sdk.requests.InitRequestBuilder

/**
* @author Stanislav Mukhametshin
*/
class PaymentProcess internal constructor() {

private val handler = PaymentHandler()
private lateinit var thread: Thread
private lateinit var requestBuilder: InitRequestBuilder
private var paymentListeners: Set<PaymentListener> = HashSet()
private val paymentDataUi = PaymentDataUi()
private var lastException: Exception? = null
private var lastKnownAction: Int? = null

private companion object {

private const val SUCCESS = 1
private const val START_3DS = 2
private const val CHARGE_REQUEST_REJECTED = 3
private const val EXCEPTION = 4
}

@JvmSynthetic
internal fun initPaymentRequest(initRequestBuilder: InitRequestBuilder,
customerKey: String,
paymentData: PaymentData): PaymentProcess {
requestBuilder = paymentData.run {
initRequestBuilder.setOrderId(orderId)
.setAmount(coins)
.setChargeFlag(chargeMode)
.setCustomerKey(customerKey)
.setRecurrent(recurrentPayment);
}
paymentDataUi.recurrentPayment = paymentData.recurrentPayment
return this
}

@JvmSynthetic
internal fun initPaymentThread(sdk: AcquiringSdk,
cardData: CardData,
email: String?,
chargeMode: Boolean): PaymentProcess {
paymentDataUi.card = cardData.map()

thread = Thread(Runnable {
try {
val paymentId = sdk.init(requestBuilder)

if (Thread.interrupted()) return@Runnable

handler.run {
if (!chargeMode) {
val threeDsData = sdk.finishAuthorize(paymentId, cardData, email)
if (Thread.interrupted()) return@Runnable

if (threeDsData.isThreeDsNeed) {
obtainMessage(START_3DS, threeDsData)
} else {
obtainMessage(SUCCESS)
}
} else {
val paymentInfo = sdk.charge(paymentId, cardData.rebillId)
if (paymentInfo.isSuccess) {
obtainMessage(SUCCESS)
} else {
obtainMessage(CHARGE_REQUEST_REJECTED, paymentInfo)
}
}
}.sendToTarget()
} catch (e: Exception) {
handler.obtainMessage(EXCEPTION, e).sendToTarget()
}
})
return this
}

private fun CardData.map(): Card {
val card = Card()
card.pan = pan
card.cardId = cardId
card.rebillId = rebillId
card.status = CardStatus.ACTIVE
return card
}

fun subscribe(paymentListener: PaymentListener) {
this.paymentListeners += paymentListener
sendToListener(lastKnownAction ?: return, paymentListener)
}

fun unSubscribe(paymentListener: PaymentListener) {
this.paymentListeners -= paymentListener
}

fun start(): PaymentProcess {
if (!thread.isAlive) {
thread.start()
}
return this
}

fun stop() {
thread.interrupt()
paymentListeners = HashSet()
}

fun clear() {
thread.interrupt()
}

private fun sendToListeners(action: Int) {
lastKnownAction = action
paymentListeners.forEach { sendToListener(action, it) }
}

private fun sendToListener(action: Int, listener: PaymentListener) {
listener.apply {
when (action) {
SUCCESS -> onCompleted()
CHARGE_REQUEST_REJECTED, START_3DS -> onUiNeeded(paymentDataUi)
EXCEPTION -> onError(lastException ?: return)
}
}
}

@SuppressLint("HandlerLeak")
inner class PaymentHandler : Handler(Looper.getMainLooper()) {

override fun handleMessage(msg: Message) {
super.handleMessage(msg)
when (msg.what) {
CHARGE_REQUEST_REJECTED -> {
paymentDataUi.paymentInfo = msg.obj as PaymentInfo
paymentDataUi.status = PaymentDataUi.Status.REJECTED
}
START_3DS -> {
paymentDataUi.threeDsData = msg.obj as ThreeDsData
paymentDataUi.status = PaymentDataUi.Status.THREE_DS_NEEDED
}
EXCEPTION -> lastException = (msg.obj as Exception)
}
sendToListeners(msg.what)
}
}

interface PaymentListener {

fun onCompleted()

fun onUiNeeded(paymentDataUi: PaymentDataUi)

fun onError(exception: Exception)
}
}
84 changes: 84 additions & 0 deletions payment/src/main/java/ru/tinkoff/acquiring/payment/TinkoffPay.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ru.tinkoff.acquiring.payment

import android.app.Activity
import ru.tinkoff.acquiring.sdk.*
import ru.tinkoff.acquiring.sdk.requests.InitRequestBuilder
import java.lang.IllegalArgumentException

/**
* @author Stanislav Mukhametshin
*/
class TinkoffPay private constructor(private var sdk: AcquiringSdk,
private val customerKey: String,
private val email: String?,
private val publicKey: String) {

var card: CardData? = null
var paymentData: PaymentData? = null

fun pay(card: CardData, paymentData: PaymentData): PaymentProcess {
val initRequestBuilder = InitRequestBuilder(sdk.password, sdk.terminalKey)
this.card = card
this.paymentData = paymentData

return PaymentProcess()
.initPaymentRequest(initRequestBuilder, customerKey, paymentData)
.initPaymentThread(sdk, card, email, paymentData.chargeMode)
}

fun launchUi(activity: Activity, paymentData: PaymentData, paymentDataUi: PaymentDataUi, requestCode: Int) {
PayFormActivity
.init(sdk.terminalKey, sdk.password, publicKey)
.prepare(paymentData.orderId,
Money.ofCoins(paymentData.coins),
"",
"",
paymentDataUi.paymentInfo?.cardId,
email,
paymentDataUi.recurrentPayment,
true)
.setCustomerKey(customerKey)
.setChargeMode(paymentDataUi.recurrentPayment)
.useFirstAttachedCard(true)
.addPaymentUiData(paymentDataUi)
.setTheme(R.style.AcquiringTheme)
.startActivityForResult(activity, requestCode)
}

private fun PayFormStarter.addPaymentUiData(paymentDataUi: PaymentDataUi): PayFormStarter {
paymentDataUi.apply {
if (card != null && paymentInfo != null) {
intent.putExtra(PayFormActivity.EXTRA_CARD_DATA, CardsArrayBundlePacker().pack(arrayOf(paymentDataUi.card)))
intent.putExtra(PayFormActivity.EXTRA_PAYMENT_INFO, PaymentInfoBundlePacker().pack(paymentDataUi.paymentInfo))
} else if (threeDsData != null) {
intent.putExtra(PayFormActivity.EXTRA_THREE_DS, ThreeDsBundlePacker().pack(threeDsData))
}
}
return this
}

class Builder(private val sdk: AcquiringSdk,
private var publicKey: String) {

private var customerKey: String? = null
private var email: String? = null

companion object {
@JvmStatic
fun init(terminalKey: String, password: String, publicKey: String): Builder {
val sdk = AcquiringSdk(terminalKey, password, publicKey)
return Builder(sdk, publicKey)
}
}

fun setCustomerKey(customerKey: String): Builder = apply { this.customerKey = customerKey }

fun setEmail(email: String) = apply { this.email = email }

fun build(): TinkoffPay {
val customerKey = customerKey
?: throw IllegalArgumentException("CustomerKey is not set")
return TinkoffPay(sdk, customerKey, email, publicKey)
}
}
}
1 change: 1 addition & 0 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
compile 'com.android.support:appcompat-v7:27.0.2'
compile project(':ui')
compile project(':card-io')
compile project(':payment')

androidTestCompile 'junit:junit:4.12'
testCompile 'junit:junit:4.12'
Expand Down
Loading