NAV Navbar
Text Python Java

Introduction

Welcome to the QFPay Android SDK documentation. This manual introduces QFPay's Android SDK access method, through which developers gain access to the QFPay payment gateway. There are language bindings available in Python and Java. You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right corner.

It is possible for third-parties to integrate multiple payment functions in the mobile app. In the offline transaction scenario, the merchant's cash register APP calls the SDK provided by QFPay to call the payment module. The merchant can select the type of payment to be used in the app. After the consumer's payment is completed, the payment result is displayed. The currently supported system is Android.

Platform Access Instructions

Unless otherwise specified, all of the following interface parameters are returned using UTF-8 encoding.

Access steps

  1. After the access party app logs in, get the token through the access party background
  2. The accessor app passes the obtained token to the SDK
  3. After the SDK gets the token, it will be initialized
  4. After the token expires, it will return a specific error code, you need to reacquire the token

process-flow

Request Description

The http header needs to set the following two items:

The X-QF-APPCODE is assigned to developers during the onboarding process. X-QF-SIGN Contains the signature generated according to signature algorithm. The request is in the form of an HTTP form, or a parameter form on the URL.

For example:
https://openapi.qfpay.com/trade/v1/payment?txamt=1&out_trade_no=1231231231231231

Response Description

The HTTP header will set the following items: X-QF-SIGN: Signature. The data returned in response is a JSON string with common return parameters.

Parameter Description
respcd 0000 = Request successful.
1143/1145 = merchants are required to continue to query the transaction result.
All other return codes indicate transaction failure.
resperr Error description
respmsg Debugging Information

Sub-Merchant Model

If you need to complete the sub-merchant mode transaction, add mchid to the post parameter of the original interface to identify the sub-merchant identity For sub-merchant mode, you can contact technical support

Signature Algorithm Description Request:

For code instructions select Python from the tabs above.
import hashlib unicode_to_utf8 = lambda s: s.encode('utf-8') if isinstance(s, unicode) else s # Request signature data is data dictionary def make_req_sign(data, key):

keys = data.keys() keys.sort() p = [] for k in keys:

k = unicode_to_utf8(k) v = unicode_to_utf8(data[k]) p.append('%s=%s'%(k,v)) unsign_str = '&'.join(p) + unicode_to_utf8(key) s = hashlib.md5(unsign_str).hexdigest() return s.upper() # Response signature data is the entire content data string returned def make_resp_sign(data, key):

unsign_str = unicode_to_utf8(data) + unicode_to_utf8(key) s = hashlib.md5(unsign_str).hexdigest() return s.upper()

1) Arrange all non-empty parameters in ascending order by parameter name. The parameter list is:

abc = value1 bcd = value2 bad = value3

Sort the parameters:

abc = value1 bad = value3 bcd = value2

2) Connect all parameters with & to get the character string to be signed.

abc = value & bad = value & bcd = value

3) Put the character string and the KEY assigned to the developer together.

abc = value & bad = value & bcd = valueMyKey

4) Do the MD5 operation on the concatenated strings

MD5(abc = value & bad = value & bcd = valueMyKey)

Store this signature result in the http header X-QF-SIGN field

Response:

Get the signature from the X-QF-SIGN field in the HTTP header. The body part of the HTTP response is spliced with the KEY, and then the entire MD5 is done. The signature value obtained is the same as the signature comparison in X-QF-SIGN.

Get Merchant Token

The interface will returns JSON structured like this:

{ 
"resperr": "", 
"respcd": "0000", 
"respmsg": "", 
"data": 
  { 
  "token": "786A842219384DE7A91890978308FDDF", 
  "expire": 7200 
  } 
}

A unique token has to be requested for each merchant.

Method: POST
URL: /auth/v1/token
Content-type: application/x-www-form-urlencoded

Request Parameters

Parameter Mandatory Description
mchid Yes Merchant mchid (mchid returned by the money when the merchant is bound
opuid No Operator id (not regarded as master account)

Response Parameters

Parameter Description
token SDK required credentials
expire How long until the token expires in seconds

SDK Access Instructions

implementation ('com.qfpay: opensdk: + @ aar') { transitive = true }

Download the SDK and add the code shown below to the dependencies in the build.gradle file of the main module of the project.

implementation ('com.qfpay: opensdk: + @ aar') { transitive = true }

Development Environment

In order to reduce the use of callbacks, this SDK uses the RxJava dependency library to write business processing procedures, and Observable <?> as the interface return value.

Add compilation option configuration

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
android { compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } }

Add the code below in the build.gradle file of the application module:

android { compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } }

Add obfuscation configuration

-keep class com.qfpay. ** {*;}

Add the following code to the proguard-rules.pro file in the project root directory:

-keep class com.qfpay. ** {*;}

Get function operation interface example

There are two ways how the interface instance can be obtained:

// 1. QFPayTradeManager

QFPayTradeManager qfPayTradeManager = 
QFPayTradeManager.getInstance (getApplicationContext ());

// 2. QFAPI

QFApi qFApi = QFApiFactory.createApi (getApplicationContext ());

1. QFPayTradeManager

QFPayTradeManager qfPayTradeManager =
QFPayTradeManager.getInstance (getApplicationContext ());

2. QFAPI

QFApi qFApi = QFApiFactory.createApi (getApplicationContext ());

The difference between the two is that the function call in the first way will include the default UI page. If the caller needs to customize the UI page, you can use the second method. In addition, you can also obtain QFApi instances through the getQFApi() method in QFPayTradeManager.

Initialize the SDK

// Set whether the SDK is in the Debug state, and the relevant logs will be printed in the Debug state boolean 

isDebug = true;

// Session token String 

token = null; qfPayTradeManager.init (token, true) .subscribeOn (Schedulers.newThread ()) .observeOn (AndroidSchedulers.mainThread ()) .subscribe (new Consumer <User> () { @Override public void accept (User user) throws Exception {  

// Caller can save user information by himself

5 } }, new Consumer<Throwable>() { @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); } });

The initialization SDK is used to verify the legality of the merchant's identity and to obtain configuration information such as the transaction type supported by the merchant.

Set whether the SDK is in the Debug state, and the relevant logs will be printed in the Debug state boolean

isDebug = true;

Session token String

token = null; qfPayTradeManager.init (token, true) .subscribeOn (Schedulers.newThread ()) .observeOn (AndroidSchedulers.mainThread ()) .subscribe (new Consumer () { @Override public void accept (User user) throws Exception {

Caller can save user information by himself

5 } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); } });

The token in the code snippet above is obtained by the caller requesting the QFPay server. See the documentation for specific operations.

The caller should judge the validity of the token by himself. When the token expires or the user configuration needs to be updated, the init interface needs to be called again.

In addition, each user is configured with different payment methods as required. Please consult technical support for specific operations.

API Requests

Collect Money

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
Activity context;
long amount = 1; // Amount in Cents 
String outTradeNo = "123"; // External order number
PayChannel payChannel = null; // Trading channel, there is a return in the init interface
ScanCodePosture scanCodePosture = ScanCodePosture.ForwardScan; // Scan code method 
Intent intent = qfPayTradeManager.collectionIntent(context, amount, outTradeNo, payChannel, scanCodePosture); 
startActivityForResult(intent, 1);

Activity context; long amount = 1; // Amount in Cents String outTradeNo = "123"; // External order number PayChannel payChannel = null; // Trading channel, there is a return in the init interface ScanCodePosture scanCodePosture = ScanCodePosture.ForwardScan; // Scan code method Intent intent = qfPayTradeManager.collectionIntent(context, amount, outTradeNo, payChannel, scanCodePosture); startActivityForResult(intent, 1);

In the payment interface, the amount and external order number are required. The transaction channel PayChannel can be left blank, and the SDK will provide a list of default transaction channel selections. The scanning method scanCodePosture is optional. The default is forward scanning.

Get the response result through the onActivityResult (int requestCode, int resultCode, @Nullable Intent data) callback method.

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
@Override 
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (resultCode == Activity.RESULT_CANCELED) {
    System.out.println("User Cancel");
  } else {
    if (data != null) { 
    String errorMsg = data.getStringExtra("error_msg"); 
    if (!TextUtils.isEmpty(errorMsg)) { 
      System.out.println(errorMsg); 
    } else {  
        Transaction transaction = data.getParcelableExtra("transaction"); 
        System.out.println(transaction.toString()); 
    }
  }
}
}

@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_CANCELED) { System.out.println("User Cancel"); } else { if (data != null) { String errorMsg = data.getStringExtra("error_msg"); if (!TextUtils.isEmpty(errorMsg)) { System.out.println(errorMsg); } else {
Transaction transaction = data.getParcelableExtra("transaction"); System.out.println(transaction.toString()); } } } }

The payment interface described above uses the SDK's default UI interface. If you need to customize the payment UI interface, you can use the following interface to implement it.

1. Receiving payment

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
long amount = 1; // Amount in Cents
String busicd = payChannel.getBusicdQrcode(); // Service code, there is a return in the init interface
String outTradeNo; // External order number

qFApi.qrcodeTrade(amount, busicd, outTradeNo) 
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<QrcodeTradeOrder>() { 
    @Override 
    public void accept(QrcodeTradeOrder order) throws Exception { // The pre-order is being scanned and the payment can be pre-ordered, and the content of the two-dimensional code returned by the getQrcodeUrl () method in QrcodeTradeOrder can be converted into a positive scan code, and then the transaction result needs to be queried through the polling method (step 3) until successful Or timeout.
    }
  }, new Consumer<Throwable>() { 
    @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); }
});

long amount = 1; // Amount in Cents String busicd = payChannel.getBusicdQrcode(); // Service code, there is a return in the init interface String outTradeNo; // External order number

qFApi.qrcodeTrade(amount, busicd, outTradeNo) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(QrcodeTradeOrder order) throws Exception { // The pre-order is being scanned and the payment can be pre-ordered, and the content of the two-dimensional code returned by the getQrcodeUrl () method in QrcodeTradeOrder can be converted into a positive scan code, and then the transaction result needs to be queried through the polling method (step 3) until successful Or timeout. } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); } });

2. Consumer Present Mode (CPM)

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
long amount = 1; // Amount in Cents
String busicd = payChannel.getBusicdScan(); // Service code, there is a return in the init interface
String outTradeNo; // External order number
String walletQrcode; // Consumer code content, users need to realize the code recognition process by themselves

qFApi.scanTrade(amount, busicd, outTradeNo, walletQrcode) 
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<Transaction>() { 
    @Override 
    public void accept(Transaction transaction) throws Exception { // After the request is completed, you still need to judge whether the transaction is completed according to the waitingForTrade () method in Transaction. If it returns true, you need to query the transaction result through the polling method (step 3) until it succeeds or times out. }
  }, new Consumer<Throwable>() { 
    @Override 
    public void accept(Throwable throwable) throws Exception { 
    throwable.printStackTrace(); }
});

long amount = 1; // Amount in Cents String busicd = payChannel.getBusicdScan(); // Service code, there is a return in the init interface String outTradeNo; // External order number String walletQrcode; // Consumer code content, users need to realize the code recognition process by themselves

qFApi.scanTrade(amount, busicd, outTradeNo, walletQrcode) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(Transaction transaction) throws Exception { // After the request is completed, you still need to judge whether the transaction is completed according to the waitingForTrade () method in Transaction. If it returns true, you need to query the transaction result through the polling method (step 3) until it succeeds or times out. } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); } });

3. Transaction Results

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
String transId = 20190621000200020060244565; // OFPay order number
int pollInterval = 2; // Query interval, unit in seconds
int timeout = 30; // Query timeout, unit in seconds

qFApi.tradePoll(transId, pollInterval, timeout)
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<Transaction>() {
    @Override
    public void accept(Transaction trans) throws Exception {
      //查询到交易结果
    } 
  }, new Consumer<Throwable>() {
      @Override
      public void accept(Throwable throwable) throws Exception { 
      if (e instanceof TradeTimeoutException) { 
      // If the query times out, a TradeTimeoutException is thrown } 
      else { // Other error messages
    }
  }
);

String transId = “20190621000200020060244565”; // OFPay order number int pollInterval = 2; // Query interval, unit in seconds int timeout = 30; // Query timeout, unit in seconds

qFApi.tradePoll(transId, pollInterval, timeout) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(Transaction trans) throws Exception { //查询到交易结果 } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { if (e instanceof TradeTimeoutException) { // If the query times out, a TradeTimeoutException is thrown } else { // Other error messages } } );

4. Close Order

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
String transId = "201901010101010"; // Pre-order transaction number 
long transAmt = 1L; // Pre-order transaction amount
mQFApi.tradeClose(transId, transAmt)
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<Transaction>() {
    @Override
    public void accept(Transaction trans) throws Exception {
      // Return the transaction record of a new closed order
    } 
  }, new Consumer<Throwable>() {
    @Override
    public void accept(Throwable throwable) throws Exception { 
      if(throwable instanceOf TradeException) { 
        // For some trading channels, if you do not support the order closing operation, a TradeException will be thrown
      }
});

In the sweeping payment collection scenario, for users to cancel the transaction, in order to avoid consumers' repeated payment, it is necessary to call the interface to close the order.

String transId = "201901010101010"; // Pre-order transaction number long transAmt = 1L; // Pre-order transaction amount mQFApi.tradeClose(transId, transAmt) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(Transaction trans) throws Exception { // Return the transaction record of a new closed order } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { if(throwable instanceOf TradeException) { // For some trading channels, if you do not support the order closing operation, a TradeException will be thrown } });

5. Transaction Rectification

In the scenario of anti-scanning transactions, in order to avoid inconsistent transaction results obtained by merchants and consumers. If no clear transaction results are queried and the final transaction is required, you need to call the positive connection.

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
String transId = "201901010101"; // Original transaction number 
long transAmt = 20L; // Transaction amount

mQFApi.tradeReversal(transId, transAmt)
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<Transaction>() {
    @Override
    public void accept(Transaction trans) throws Exception {
      // Return a record of a new transaction
    } 
  }, new Consumer<Throwable>() {
    @Override
    public void accept(Throwable throwable) throws Exception { 
    if(throwable instanceOf TradeException) { 
    // For some trading channels, if you do not support the transaction correction operation, a TradeException will be thrown
    }
});

String transId = "201901010101"; // Original transaction number long transAmt = 20L; // Transaction amount

mQFApi.tradeReversal(transId, transAmt) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(Transaction trans) throws Exception { // Return a record of a new transaction } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { if(throwable instanceOf TradeException) { // For some trading channels, if you do not support the transaction correction operation, a TradeException will be thrown } });

Refunds

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
String outTradeNo;//外部订单号 
String qfTradeNo;//QFPay订单号 
long amount;//退款⾦额,单位分

qfPayTradeManager.refund(outTransId, qfTransId, amount)
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<Transaction>() { 
    @Override 
    public void accept(Transaction transaction) throws Exception { 
      //退款交易信息 
    } 
  }, new Consumer<Throwable>() { 
    @Override 
    public void accept(Throwable throwable) throws Exception { 
      throwable.printStackTrace(); 
    } 
  });

Note that the external order number in this interface is not the order number of the original transaction, but a new one, which is used to mark the order number of the refund transaction.

String outTradeNo;//外部订单号 String qfTradeNo;//QFPay订单号 long amount;//退款⾦额,单位分

qfPayTradeManager.refund(outTransId, qfTransId, amount) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(Transaction transaction) throws Exception { //退款交易信息 } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); } });

Transaction Query

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
// List of transaction channels to be queried. If it is empty, all types of transaction records will be queried. 
List<PayChannel> payChannels; 
// Query copies, the format is yyyy-MM. If it is empty, the query will be
String month = "2019-06"; 
// Query start time, the format is dd hh: mm: ss. If it is empty, it defaults to 0: 0: 0 on the 1st
String startTime = "01 00:00:00"; 
// Query end time, the format is dd hh: mm: ss. If it is empty, the default is 23:59:59 on the last day
String endTime = "30 23:59:59"; 
// Number of sub-query 
int pageNum = 1; 
// Every degree of sub-query
int pageSize = 10;

qfPayTradeManager.tradeList(payChannels, month, startTime, endTime, pageNum, pageSize) 
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<List<Transaction>>() { 
    @Override 
    public void accept(List<Transaction> transList) throws Exception { 
      // Back to transaction list } 
    }, new Consumer<Throwable>() { 
      @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); 
    } 
});

The transaction record query interface can specify the order number or time period to query a single or multiple transaction records.

Query multiple transaction records

Make a query based on transaction date or time intervall

// List of transaction channels to be queried. If it is empty, all types of transaction records will be queried. List payChannels; // Query copies, the format is yyyy-MM. If it is empty, the query will be String month = "2019-06"; // Query start time, the format is dd hh: mm: ss. If it is empty, it defaults to 0: 0: 0 on the 1st String startTime = "01 00:00:00"; // Query end time, the format is dd hh: mm: ss. If it is empty, the default is 23:59:59 on the last day String endTime = "30 23:59:59"; // Number of sub-query int pageNum = 1; // Every degree of sub-query int pageSize = 10;

qfPayTradeManager.tradeList(payChannels, month, startTime, endTime, pageNum, pageSize) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer>() { @Override public void accept(List transList) throws Exception { // Back to transaction list } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); } });

Make a query with transaction number range

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
// QFPay order number or external order number, multiple order numbers are separated by commas, the two cannot be empty at the same time
String qfTransIdArray = "123,456,789"; 
String outTradeNoArray = "321,654,987";

qfPayTradeManager.tradeList(qfTransIdArray, outTradeNoArray)
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<List<Transaction>>() {
    @Override
    public void accept(List<Transaction> transList) throws Exception {
      //返回交易记录列表
    } 
});

// QFPay order number or external order number, multiple order numbers are separated by commas, the two cannot be empty at the same time String qfTransIdArray = "123,456,789"; String outTradeNoArray = "321,654,987";

qfPayTradeManager.tradeList(qfTransIdArray, outTradeNoArray) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer>() { @Override public void accept(List transList) throws Exception { //返回交易记录列表 } });

Query a single transaction record

For code instructions select Java from the tabs above.
For code instructions select Java from the tabs above.
String qfTransId = "123"; 
String outTradeNo= "321"; 
qfPayTradeManager.trade(qfTransId, outTradeNo)
  .subscribeOn(Schedulers.newThread()) 
  .observeOn(AndroidSchedulers.mainThread()) 
  .subscribe(new Consumer<Transaction>() { 
    @Override 
    public void accept(Transaction trans) throws Exception { 
      //返回交易记录 
    } 
  }, new Consumer<Throwable>() { 
    @Override public void accept(Throwable throwable) throws Exception { 
    throwable.printStackTrace(); 
    } 
});

Specify a QFPay transaction number to query a single transaction record.

String qfTransId = "123"; String outTradeNo= "321"; qfPayTradeManager.trade(qfTransId, outTradeNo) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer() { @Override public void accept(Transaction trans) throws Exception { //返回交易记录 } }, new Consumer() { @Override public void accept(Throwable throwable) throws Exception { throwable.printStackTrace(); } });

Appendix

Transaction data

Parameter Description Data type Mandatory
id transaction ID String Yes
outTradeNo External order number String Yes
amount transaction amount, in units of the smallest denomination of the current currency Long integer string Yes
orderTime Order time, format yyyy-MM-dd HH: mm: ss String Yes
time transaction time, format yyyy-MM-dd HH: mm: ss String Yes
busicd transaction type business code String No
currencyCode Transaction currency code, such as RMB to CNY String Yes
status Transaction status, such as normal, failed, refunded, pending confirmation Integer Yes
type Transaction type, such as payment, refund String No
originId The original transaction id. If the current transaction is a refund transaction, this parameter does not show String No
payChannel Transaction Channel PayChannel No

Transaction status

Transaction status Description
-1 Failure
0 Unknown
1 Normal trading
2 Transaction cancelled
3 The transaction is fully refunded
4 The transaction was partially refunded
5 Waiting for payment

Transaction types

Transaction type
payment
refund

PayChannel data

Parameter Description Data type Mandatory
name Trading channel name, for example, weixin, alipay String Yes
busicdQrocde Business code 800101 String Yes
busicdScan Anti-scanning transaction cancellation business code 800103 String Yes
desc Transaction channel description String Yes