为了支持海外的支付,我真的是找遍了各种方法,研究过google支付,最后因为手续费太高放弃。最后还是找到了支付宝海外支付。
sdk文档地址:http://www.alipay-seller.mpymnt.com/node/82(对,只有英文文档,没有中文)
该渠道支持的visa、mastercard、jcb等信用卡,既然是信用卡支付,是有一定的失败概率的,没有开通国际汇款的信用卡不能支付(这个目前我们应用测试结果是这样子的)。
国内的信用卡不能支付,不管是双币还是其他(这个很忧伤)。我在测试环境测试好后,只能麻烦海外用户帮忙测试。。。
下面我把基本步骤给贴出来,还有一些注意事项。
开发前的准备工作
申请注册webhook http://www.alipay-seller.mpymnt.com/node/35《--这里有说明
按照文档说,发送申请邮件到指定邮箱,在发之前,最好跟支付宝技术人员先联系上,说明想接入他们这个sdk。否则人家看到你发的邮件都不知道是多久以后了。
webhook的作用:主要是通知我们的服务端,支付结果。
1、选择下载哪个demo
官方网站有三个demo(mCommerce Sample App、CheckoutScreens Sample App、Tokenization Sample),我接入的是第一个,
第二个放入我们工程会报错,第三个支付宝技术人员说不要用(我就想既然建议不要用,为何要有)。
下面我说的,就是按照demo里面的代码该注意的地方重点说明。
下面的这个两个参数,测试环境下就按照官方给的值,不用改。在正式环境中,建议吧这两个值放在服务端。
在测试环境中.初始化initializaProvider(PWProviderMode.TEST,
APPLICATIONIDENTIFIER, PROFILETOKEN); 正式环境中记得改成PWProviderMode.LIVE.
创建订单,
这里我选择的是美元收款,文档里面可以有多种收款币种选择的。最后支付宝会结算成人民币给我们。信用卡类型,我总共就用两个visa和mastercard。两种充值类型逻辑都是一样的,唯一的区别就是这个PWCreditCardType.VISA还是PWCreditCardType.MASTERCARD.根据自己的需求。
创建订单后要选择对订单中哪些数据进行风控。这个风控系统是支付宝自己的系统,主要判断订单的风险性,他们自己有套过滤标准。
这个demo里面绝对没有,除非后面更新了。
这里的美元单位是要美分的
设置订单号:
paymentParams.setCustomIdentifier(orderNum+"");
发起注册预授权(pa)
_binder.createAndRegisterPreauthorizationTransaction(paymentParams);
在回调函数creationAndRegistrationSucceeded中
发起pa:
_binder.preauthorizeTransaction(transaction);
pa成功后,注册cp:
_binder.createAndRegisterCaptureTransaction(paymentParams, transaction.getMobileIdentifier());
注册cp成功,发起cp:
_binder.captureTransaction(transaction);
完整的java 代码如下:
public class ProcessTransactionActivity extends Activity implements
PWTransactionListener{//,PWTransactionStatusListener
private PWProviderBinder _binder;
private String APPLICATIONIDENTIFIER = null;//"payworks.sandbox";
private String PROFILETOKEN = null;//"20d5a0d5ce1d4501a4826a8b7e159d19";
private ServiceConnection _serviceConnection = null;
private PWPaymentParams paymentParams = null;
private PWTransaction transaction = null;
private RelativeLayout ry_btn_back,ry_btn_next;
private TextView tv_title,tv_tips;
private EditText edt_name,edt_cardNumber,edt_cvvNumber,edt_mm,edt_yyyy,edt_emailAddress;
private TextView btn_submit;
private Dialog waitDlg;
private RechargeCoinType coinType;
private int fromWhere = 0;
private String orderNum = "0"; //订单号
private String clientIPAddress = ""; //客户端的ip地址
private boolean isOperating = false;
private int clickPayWay = 0;
private final static int aplipayClientPay = 1;
private final static int aplipayWebPay = 2;
private final static int visaPay = 3;
private final static int masterCardPay = 4;
private TextView textTips;
private void setStatusText(final String string) {
runOnUiThread(new Runnable() {
public void run() {
if (textTips == null) {
textTips = ((TextView) findViewById(R.id.textView1));
}
textTips.setText(string);
}
});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_processtransaction);
Bundle bundle = this.getIntent().getExtras();
if (bundle != null) {
fromWhere = bundle.getInt("fromWhere");
PROFILETOKEN = bundle.getString("profiletoken");//可以根据自己的设计获取
APPLICATIONIDENTIFIER = bundle.getString("applicationidentifier");//可以根据自己的设计获取
orderNum = bundle.getString("ordernum");
clientIPAddress = bundle.getString("clientIPAddress");
clickPayWay = bundle.getInt("clickPayWay");
}
if (clickPayWay == visaPay) {
clickKey = "visa";
} else if (clickPayWay == masterCardPay) {
clickKey = "matercard";
}
if (PROFILETOKEN == null || APPLICATIONIDENTIFIER == null) {
finish();
return;
}
initServiceConnection();
startService(new Intent(this, com.mobile.connect.service.PWConnectService.class));
bindService(new Intent(this, com.mobile.connect.service.PWConnectService.class),
_serviceConnection, Context.BIND_AUTO_CREATE);
initView();
setListener();
}
private void showDialog(String str) {
closeDialog();
waitDlg = DialogUtil.loadingDlg(this, str);
}
private void closeDialog() {
if (waitDlg != null && !this.isFinishing()) {
waitDlg.cancel();
waitDlg = null;
}
}
private void initView() {
ry_btn_back = (RelativeLayout) findViewById(R.id.ry_btn_back);
ry_btn_next = (RelativeLayout) findViewById(R.id.ry_btn_next);
ry_btn_next.setVisibility(View.GONE);
tv_title = (TextView)findViewById(R.id.tv_title);
if (clickPayWay == visaPay) {
tv_title.setText(R.string.recharge_visa_pay);
} else if (clickPayWay == masterCardPay) {
tv_title.setText(R.string.recharge_mastercard_pay);
}
edt_name = (EditText) findViewById(R.id.edt_name);
edt_cardNumber = (EditText) findViewById(R.id.edt_cardNumber);
edt_cvvNumber = (EditText) findViewById(R.id.edt_cvvNumber);
edt_mm = (EditText) findViewById(R.id.edt_mm);
edt_yyyy = (EditText) findViewById(R.id.edt_yyyy);
edt_emailAddress = (EditText) findViewById(R.id.edt_emailAddress);
btn_submit= (TextView) findViewById(R.id.btn_submit);
tv_tips = (TextView) findViewById(R.id.tv_tips);
tv_tips.setText(getString(R.string.pay_charge_choose) + coinType.money + getString(R.string.dollar) + coinType.coinNum + getString(R.string.cointype));
}
private void setListener() {
btn_submit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!LocalUsers.isAleadyLogin()) {
ShowUtil.showToast(ProcessTransactionActivity.this, R.string.login_first);
return;
}
submitToAlipay();
}
});
}
private void initServiceConnection() {
_serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
_binder = (PWProviderBinder) service;
// we have a connection to the service
try {
_binder.initializeProvider(PWProviderMode.LIVE, APPLICATIONIDENTIFIER, PROFILETOKEN);
_binder.addTransactionListener(ProcessTransactionActivity.this);
} catch (PWException ee) {
setStatusText(getString(R.string.provider_not_init));
// error initializing the provider
ee.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
_binder = null;
}
};
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
// getMenuInflater().inflate(R.menu.activity_tokenization, menu);
return true;
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(_serviceConnection);
stopService(new Intent(this,
com.mobile.connect.service.PWConnectService.class));
}
@Override
public void creationAndRegistrationFailed(PWTransaction transaction,PWError error) {
this.transaction = transaction;
isOperating = false;
closeDialog();
String errorDetail = error.getErrorMessage();
if (errorDetail.contains("000.400.100")) {
errorDetail = "交易失敗,訂單進入風控系統,不會發生扣款,如果收到扣款短信,請以您的信用卡賬單為準,有問題聯系客服";
setStatusText(errorDetail +
"(errorcode:" + error.getErrorCode() +
",error:" + error.getErrorMessage() + ")");
} else {
setStatusText(getString(R.string.creationAndRegistrationFailed) +
",errorcode:" + error.getErrorCode() +
",error:" + error.getErrorMessage());
}
MyLog.e("TokenizationActivity",
error.getErrorMessage());
}
int isPro = 0;//0发起pa,1发起cp
@Override
public void creationAndRegistrationSucceeded(PWTransaction transaction) {
this.transaction = transaction;
isOperating = true;
closeDialog();
mHandler.sendEmptyMessage(101);
try {
if (isPro == 0) {
_binder.preauthorizeTransaction(transaction);
} else if (isPro == 1) {
_binder.captureTransaction(transaction);
}
} catch (PWException e) {
setStatusText(getString(R.string.transactionFailed));
e.printStackTrace();
}
}
@Override
public void transactionFailed(PWTransaction arg0, PWError error) {
this.transaction = arg0;
isOperating = false;
closeDialog();
setStatusText(getString(R.string.transactionFailed) +
",errorcode:" + error.getErrorCode() +
",error:" + error.getErrorMessage());
MyLog.e("TokenizationActivity", "isPro" + isPro +
error.getErrorMessage());
}
@Override
public void transactionSucceeded(PWTransaction transaction) {
this.transaction = transaction;
try {
if (isPro == 0) {
isOperating = true;
try {
_binder.checkTransactionStatus(transaction.getMobileIdentifier());
} catch (PWException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
_binder.createAndRegisterCaptureTransaction(paymentParams, transaction.getMobileIdentifier());
isPro = 1;
} else {
isOperating = false;
isPro = 0;
closeDialog();
setStatusText(getString(R.string.recharge_finish));
mHandler.sendEmptyMessage(102);
}
} catch (PWException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch(msg.what) {
case 100:
showDialog(getString(R.string.Preparing));
break;
case 101:
showDialog(getString(R.string.Processing));
break;
case 102:
ShowUtil.showToast(ProcessTransactionActivity.this, R.string.recharge_finish);
finish();
break;
}
}
};
private void submitToAlipay() {
if (coinType == null) {
return;
}
String holder = edt_name.getText().toString();
String cardnumber = edt_cardNumber.getText().toString();
String cvv = edt_cvvNumber.getText().toString();
String month = edt_mm.getText().toString();
String year = edt_yyyy.getText().toString();
String title = orderNum + "_安卓VISA";
if (clickPayWay == visaPay) {
title = orderNum + "_安卓VISA";
} else if (clickPayWay == masterCardPay) {
title = orderNum + "_安卓MASTER";
}
String emailAddress = edt_emailAddress.getText().toString();
if (emailAddress == null || emailAddress.length() <= 0 || !ToolUtil.isEmail(emailAddress)) {
setStatusText(getString(R.string.mail_type_wrong));
return;
}
try {
if (clickPayWay == visaPay) {
paymentParams = _binder.getPaymentParamsFactory()
.createCreditCardPaymentParams(coinType.money,
PWCurrency.US_DOLLAR, title, holder,
PWCreditCardType.VISA, cardnumber, year,
month, cvv);
} else if (clickPayWay == masterCardPay) {
paymentParams = _binder.getPaymentParamsFactory()
.createCreditCardPaymentParams(coinType.money,
PWCurrency.US_DOLLAR, title, holder,
PWCreditCardType.MASTERCARD, cardnumber, year,month, cvv);
}
if (paymentParams != null) {
paymentParams.addCriterion("CRITERION.ALIRISK_orderNo", orderNum+"");//订单号
paymentParams.addCriterion("CRITERION.ALIRISK_item1ItemProductName", "coins");//商品名称
paymentParams.addCriterion("CRITERION.ALIRISK_item1ItemQuantity", "1");//商品数量
paymentParams.addCriterion("CRITERION.ALIRISK_item1ItemUnitPrice", coinType.money*100 + "");//商品单价
paymentParams.addCriterion("CRITERION.ALIRISK_item1ItemUnitPriceCurrency", "US_DOLLAR");//币种
paymentParams.addCriterion("CRITERION.ALIRISK_txnAmount", coinType.money*100 + "");//订单总金额
paymentParams.addCriterion("CRITERION.ALIRISK_txnCurrency", "US_DOLLAR");//订单总金额币种
paymentParams.addCriterion("CRITERION.ALIRISK_billToEmail", emailAddress);//账单邮箱地址
paymentParams.setCustomIdentifier(orderNum+""); //customid
if (clientIPAddress != null && clientIPAddress.length() > 0) {
paymentParams.addCriterion("CRITERION.ALIRISK_clientIP", clientIPAddress);//客户端的ip地址
}
}
} catch (PWProviderNotInitializedException e) {
setStatusText(getString(R.string.provider_not_init));
e.printStackTrace();
return;
} catch (PWException e) {
int errorCode = e.getError().getErrorCode();
int str = R.string.invalid_parameters;
if (errorCode == 1111) { //卡号填写有误
str = R.string.invalid_parameters_accountname;
} else if (errorCode == 1112) { //持卡人姓名填写有误
str = R.string.invalid_parameters_username;
} else if (errorCode == 1122) { //到期月份填写有误
str = R.string.invalid_parameters_mm;
} else if (errorCode == 1123) { //到期年份填写有误
str = R.string.invalid_parameters_yyyy;
} else if (errorCode == 1124) { //cvv填写错误
str = R.string.invalid_parameters_cvv;
}
setStatusText(getString(str));
e.printStackTrace();
return;
}
ToolUtil.closeKeyBoard(ProcessTransactionActivity.this);
setStatusText("");
mHandler.sendEmptyMessage(100);
try {
//发起pa
_binder.createAndRegisterPreauthorizationTransaction(paymentParams);
} catch (PWException e) {
setStatusText(getString(R.string.cant_contact_gateway));
e.printStackTrace();
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
if ((keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0)) {
if (isOperating) {
ShowUtil.showToast(ProcessTransactionActivity.this, R.string.Processing_notClose);
}
finish();
}
}
return super.onKeyDown(keyCode, event);
}
下面讲讲服务端的处理的时候注意事项(.net)
服务端测试的通知回调地址是:https://test.payworks.io
服务端正式的通知回调地址是:https://api.payworks.io
服务端需要有ssl证书的,我测试的时候没有ssl证书,有时候会出现因为这个失败
下面是我们服务端的主要代码
/// <summary>
/// 验证外卡支付订单并给予充值金币
/// </summary>
/// <param name="customIdentifier">自己系统的订单Id</param>
/// <param name="identifier">EventId</param>
/// <param name="tid">transactionId</param>
public static void NewMethod(string customIdentifier, string identifier, string tid)
{
if (!string.IsNullOrWhiteSpace(identifier) && !string.IsNullOrWhiteSpace(tid))
{
LogHelper.Record("Mpymnt==>identifier:" + identifier + ",,,,tid:" + tid);
string url = "https://api.payworks.io/events/" + identifier;//正式的验证地址
string heads = "payworks-apiIdentifier apiIdentifier=" + MpymntConfig.APIIdentifier + ",apiSecretKey=" + MpymntConfig.Key; //MpymntConfig.APIIdentifier和MpymntConfig.Key需要支付宝配置的
string Trade_no = identifier + "," + tid;
var info = HttpGetMpy(url, "", heads, "yanzheng", int.Parse(customIdentifier), Trade_no);
LogHelper.Record("Mpymnt==>去验证获得==>info:" + info);
if (!string.IsNullOrWhiteSpace(info))
{
MpymntCheckModel infomodel = HttpUtil.ParseJson<MpymntCheckModel>(info);
if (infomodel != null)
{
string out_trade_no = customIdentifier;
decimal total_fee = infomodel.data.transaction.amount; //订单金额
//如果是成功的时候,去加金币
if (infomodel.status == "ok" && infomodel.data.transaction.transactionType.ToLower() == "cp")
{
// LogHelper.Record("Mpymnt==>subject:" + subject + ",,out_trade_no:" + out_trade_no + ",,total_fee:" + total_fee);
var row = OrdersBll.Da.UpdateState(int.Parse(out_trade_no), "订单完成", (int)OrdersStatecDescType.SuccessOrder, Trade_no);
if (row > 0) //成功更改表 状态 step=2
{
Core.LogResult("【外卡支付】【时间:】" + DateTime.Now.ToString() + "【交易金额:】" + total_fee + "【订单编号:】" + out_trade_no + ",外卡订单号:【" + tid + "】EventId:" + identifier + "【备注:】--交易成功--");
}
else
{
if (row == -25)
{
Core.LogResult("【外卡支付】【时间:】" + DateTime.Now.ToString() + "||欢唱订单号:【" + out_trade_no + "】,外卡订单号:【" + tid + "】EventId:" + identifier + ",交易状态:【" + infomodel.status + "】【交易金额:】" + total_fee + "--支付重复");
}
else
{
Core.LogResult("【外卡支付】【时间:】" + DateTime.Now.ToString() + "||欢唱订单号:【" + out_trade_no + "】,外卡订单号:【" + tid + "】EventId:" + identifier + ",交易状态:【" + infomodel.status + "】【交易金额:】" + total_fee + "--支付成功 ,给用户加币失败");
OrdersBll.Da.UpdateState(int.Parse(out_trade_no), "支付成功,加币失败", (int)OrdersStatecDescType.AddCoinError, Trade_no);
}
}
}
else
{
Core.LogResult("【外卡支付】【时间:】" + DateTime.Now.ToString() + "||欢唱订单号:【" + customIdentifier + "】,外卡订单号:【" + tid + "】EventId:" + identifier + ",交易状态:【" + infomodel.status + "】 infomodel.status:" + infomodel.status + ",,transactionType(CP才加):" + infomodel.data.transaction.transactionType.ToLower());
// RefundOrders(int.Parse(out_trade_no), tid);
}
}
else
{
Core.LogResult("【外卡支付】【时间:】" + DateTime.Now.ToString() + "||欢唱订单号:【" + customIdentifier + "】,外卡订单号:【" + tid + "】EventId:" + identifier + " --外卡验证请求链接返回格式Info为Json,Model为NULL。");
OrdersBll.Da.UpdateState(int.Parse(customIdentifier), "外卡验证请求格式Json为NULL", (int)OrdersStatecDescType.CheckMpymntError, Trade_no);
}
}
else
{
LogHelper.Record("Mpymnt==>验证identifier返回的信息空。");
}
}
else
{
LogHelper.Record("Mpymnt==>POST过来的Json解析值为空。identifier:" + identifier + ",,,,tid:" + tid);
}
}