在前文 國(guó)外支付對(duì)接:Braintree(一)的基礎(chǔ)上 已經(jīng)拿到了相關(guān)配置信息,接下來(lái)就是碼代碼了,這里完成的主要功能是支付與退款。
在此之前,先說(shuō)一下Briantree的支付流程:
第一步先生成clientToken,一組根據(jù) MerchantId,BraintreePublicKey,BraintreePrivateKey生成的字符串,用于前端生成初始化支付控件。第二步點(diǎn)擊支付按鈕客戶輸入用戶名密碼確定支付之后,Briantree在前端會(huì)返回nonce給我們(相當(dāng)于支付授權(quán)憑證)。第三步,將nonce傳到后臺(tái),我們進(jìn)行扣款。至此支付完成。
1.項(xiàng)目引用
后端
從官方下載的demo中可以看到,其實(shí)我們的后端需要用的dll就是一個(gè):Braintree.dll,NUGET上也能下載
前端
需要的就是引用官方j(luò)s,這個(gè)需要看個(gè)人需求吧,如果你不想麻煩自己寫樣式,可以直接使用官方的js生成的支付按鈕,那么用drop-in UI即可。使用drop-in是最直接便利的方式,我們?cè)谇岸酥苯右茫?/p>
<script src="https://js.braintreegateway.com/web/dropin/1.9.2/js/dropin.min.js"></script>
生成的樣式長(zhǎng)這樣:
如果需要自己設(shè)計(jì)樣式,按照自己的規(guī)則來(lái)控制前端的話,那就得使用Customer UI。那當(dāng)然需要引用的js就不同了,前端的寫法也就不同了,后面細(xì)說(shuō)。這塊主要的js是
<script src="https://js.braintreegateway.com/web/3.29.0/js/client.min.js"></script>
2.代碼解析
web.config配置
API keys 拿到之后需要在程序中使用,我們直接配置在web.config中即可,當(dāng)然安全著想也可以加密配置到數(shù)據(jù)庫(kù)中。
前端(這里先介紹使用drop-in UI的寫法)
html:
form只需要2個(gè)參數(shù)amount,nonce。最重要的是要定義一個(gè)div控件給生成支付控件使用,這里使用的id為bt-dropin的div
<form id="payment-form" method="post" action="/checkouts/Create">
<section>
<label for="amount">
<span class="input-label">Amount</span>
<div class="input-wrapper amount-wrapper">
<input id="amount" name="amount" type="tel" min="0.01" placeholder="Amount" value="0.01">
</div>
</label>
<div class="bt-drop-in-wrapper">
<div id="bt-dropin"></div>
</div>
</section>
<input id="nonce" name="payment_method_nonce" type="hidden" />
<button class="button" type="submit"><span>Test Transaction</span></button>
</form>
js:
<script src="https://js.braintreegateway.com/web/dropin/1.9.2/js/dropin.min.js"></script>
<script>
$(function () {
var client_token = "@ViewBag.ClientToken";
var form = document.querySelector('#payment-form');
braintree.dropin.create({//支付控件初始化開(kāi)始
authorization: client_token,//由后端傳過(guò)來(lái)的值,一組根據(jù) MerchantId,BraintreePublicKey,BraintreePrivateKey生成的字符串
container: '#bt-dropin',
paypal: {
flow: 'vault',
buttonStyle: { //可以修改一點(diǎn)點(diǎn)按鈕的樣式,限制性很多
color: 'black',
shape: 'rect',
size: 'medium'
}
},
//此處與上面的paypal設(shè)置不一樣,親么可以自己去嘗試一下,不同點(diǎn)在哪
//paypal: {
// flow: 'checkout',
// amount: document.querySelector('#amount').value,
// currency: 'USD'
//},
card: {//此項(xiàng)選填,干掉也沒(méi)關(guān)系
cardholderName: { required: true }, //必填的話,就會(huì)多生成一個(gè)持卡人姓名的輸入框
overrides: {
fields: {
number: {
placeholder: 'Card Number',
},
cvv: {
placeholder: 'CVV'
},
postalCode: {
placeholder: 'Postal Code'
}
},
}
},
//threeDSecure: {//3D安全校驗(yàn),選填,用于信用卡支付的時(shí)候,若改卡的持卡人在開(kāi)卡的時(shí)候啟用了額外的身份校驗(yàn),例如密碼,那么點(diǎn)支付的時(shí)候則會(huì)彈出一個(gè)額外的框,輸入密碼。
// amount: document.querySelector('#amount').value
//}
}, function (createErr, instance) {
form.addEventListener('submit', function (event) {
event.preventDefault();
instance.requestPaymentMethod(function (err, payload) {//客戶輸入密碼等之后,接收返回的結(jié)果,即nonce,支付授權(quán)憑證
if (err) {
console.log('Error', err);
return;
}
// Add the nonce to the form and submit
document.querySelector('#nonce').value = payload.nonce;
form.submit();
});
});
});
});
</script>
一組根據(jù) MerchantId,BraintreePublicKey,BraintreePrivateKey生成的字符串
container: '#bt-dropin',
paypal: {
flow: 'vault',
buttonStyle: { //可以修改一點(diǎn)點(diǎn)按鈕的樣式,限制性很多
color: 'black',
shape: 'rect',
size: 'medium'
}
},
//此處與上面的paypal設(shè)置不一樣,親么可以自己去嘗試一下,不同點(diǎn)在哪
//paypal: {
// flow: 'checkout',
// amount: document.querySelector('#amount').value,
// currency: 'USD'
//},
card: {//此項(xiàng)選填,干掉也沒(méi)關(guān)系
cardholderName: { required: true }, //必填的話,就會(huì)多生成一個(gè)持卡人姓名的輸入框
overrides: {
fields: {
number: {
placeholder: 'Card Number',
},
cvv: {
placeholder: 'CVV'
},
postalCode: {
placeholder: 'Postal Code'
}
},
}
},
//threeDSecure: {//3D安全校驗(yàn),選填,用于信用卡支付的時(shí)候,若改卡的持卡人在開(kāi)卡的時(shí)候啟用了額外的身份校驗(yàn),例如密碼,那么點(diǎn)支付的時(shí)候則會(huì)彈出一個(gè)額外的框,輸入密碼。
// amount: document.querySelector('#amount').value
//}
}, function (createErr, instance) {
form.addEventListener('submit', function (event) {
event.preventDefault();
instance.requestPaymentMethod(function (err, payload) {//客戶輸入密碼等之后,接收返回的結(jié)果,即nonce,支付授權(quán)憑證
if (err) {
console.log('Error', err);
return;
}
// Add the nonce to the form and submit
document.querySelector('#nonce').value = payload.nonce;
form.submit();
});
});
});
});
</script>
后端
1.生成clientToken的規(guī)則有2種,根據(jù)需要來(lái)吧。
由于braintree平臺(tái)中雖然只有一個(gè)商戶ID,即Merchant ID,但是確可以有多個(gè)Merchant Accounts,即收賬賬號(hào),設(shè)置的界面:Account-->Merchant Account Info
第一種,使用默認(rèn)配置:
每個(gè)Merchant ID都會(huì)有一個(gè)default Merchant Account,所以下面的寫法,就是默認(rèn)將款額收到默認(rèn)賬戶上
var config = new BraintreeGateway(environment, merchantId, publicKey, privateKey) ;
var gateway = config.GetGateway();
var clientToken = gateway.ClientToken.generate();
第二種:指另付款到某一個(gè)賬號(hào)
var clientToken = gateway.ClientToken.generate(new ClientTokenRequest() { MerchantAccountId = "TestAccount" });
2.綜合
支付功能:一共3個(gè)Action:
//生成clientToken 傳到前端,用于生成支付控件
public ActionResult New()
{
var gateway = config.GetGateway();
//var clientToken = gateway.ClientToken.generate();
var clientToken = gateway.ClientToken.generate(new ClientTokenRequest() { MerchantAccountId = "TestAccount" });
ViewBag.ClientToken = clientToken;
return View();
}
//form提交,得到nonce之后,在這里進(jìn)行扣款
public ActionResult Create()
{
var gateway = config.GetGateway();
Decimal amount;
try
{
amount = Convert.ToDecimal(Request["amount"]);
}
catch (FormatException e)
{
TempData["Flash"] = "Error: 81503: Amount is an invalid format.";
return RedirectToAction("New");
}
var nonce = Request["payment_method_nonce"];//得到前端傳來(lái)的nonce參數(shù)
var request = new TransactionRequest//新建交易請(qǐng)求
{
MerchantAccountId = "TestAccount",//注意這里,如果你的clientToken生成的時(shí)候設(shè)置了MerchantAccountId,那么扣款的時(shí)候也必須要加上這個(gè)參數(shù),否則是會(huì)失敗的
Amount = amount,
PaymentMethodNonce = nonce,
Options = new TransactionOptionsRequest
{
ThreeDSecure = new TransactionOptionsThreeDSecureRequest()//這里注意,如果你前端啟用了3D安全,那么這里也需要啟用
{
Required = true
},
SubmitForSettlement = true
}
};
Result<Transaction> result = gateway.Transaction.Sale(request);//扣款
if (result.IsSuccess())//成功
{
Transaction transaction = result.Target;
//transaction.Id是官方生產(chǎn)的此交易的唯一編號(hào),如果要進(jìn)行查詢和退款的話,就必須要將此ID記錄數(shù)據(jù)庫(kù).
return RedirectToAction("Show", new { id = transaction.Id });
}
else if (result.Transaction != null)
{
return RedirectToAction("Show", new { id = result.Transaction.Id, mesg = result.Message});
}
else
{
string errorMessages = "";
foreach (ValidationError error in result.Errors.DeepAll())
{
errorMessages += "Error: " + (int)error.Code + " - " + error.Message + "\n";
}
TempData["Flash"] = errorMessages;
return RedirectToAction("New3");
}
}
//支付結(jié)果頁(yè)展示
public ActionResult Show(String id, string mesg)
{
var gateway = config.GetGateway();
Transaction transaction = gateway.Transaction.Find(id);
if (transactionSuccessStatuses.Contains(transaction.Status))
{
//成功
}
else
{
//失敗
}
ViewBag.Transaction = transaction;
return View();
}
退款:
這里要說(shuō)明下,即時(shí)客戶完成了交易,已經(jīng)進(jìn)行了扣款,但是如果要立馬退款的話,是不行的。因?yàn)閎raintree內(nèi)部也要進(jìn)行交易審核,審核過(guò)程需要時(shí)間,而且是時(shí)間不固定,可能十幾分鐘,可能幾個(gè)小時(shí)。所以這里我們要根據(jù)當(dāng)前退款的訂單狀態(tài)進(jìn)行是退款還是作廢。2種操作的過(guò)程是不一樣的。退款會(huì)在briantree賬戶上生成退款交易單,但是作廢不會(huì),雖然2種操作最都會(huì)退款給客戶。
public ActionResult RefundTest(string trId, decimal amount)
{
var gateway = config.GetGateway();
try
{
Transaction transaction = gateway.Transaction.Find(trId);
if (transaction.Status == TransactionStatus.SETTLED || transaction.Status == TransactionStatus.SETTLING)
{//交易狀態(tài)為以上時(shí),方可進(jìn)行退款操作
Result<Transaction> result = gateway.Transaction.Refund(trId, amount);
if (!result.IsSuccess())
{//退款失敗
//Transaction transaction = result.Transaction;
//if (transaction.Status == TransactionStatus.SETTLEMENT_DECLINED)
//{
// //Console.WriteLine(transaction.ProcessorSettlementResponseCode);
// // e.g. "4001"
// //Console.WriteLine(transaction.ProcessorSettlementResponseText);
// // e.g. "Settlement Declined"
//}
return RedirectToAction("RefundResponce", new { msg = result.Message });
}
else
{
return RedirectToAction("RefundResponce", new { msg = "OK" });
}
}
else if (transaction.Status == TransactionStatus.AUTHORIZED || transaction.Status == TransactionStatus.SUBMITTED_FOR_SETTLEMENT ||
(transaction.PaymentInstrumentType == PaymentInstrumentType.PAYPAL_ACCOUNT && transaction.Status == TransactionStatus.SETTLEMENT_PENDING))
{//交易狀態(tài)為此狀態(tài)時(shí)不可退款,但是能void交易,即作廢,那么就可同時(shí)退款可客戶
Result<Transaction> result = gateway.Transaction.Void(trId);
if (result.IsSuccess())
{
return RedirectToAction("RefundResponce", new { msg = "transaction successfully voided" });
}
else
{
return RedirectToAction("RefundResponce", new { msg = result.Message });
//foreach (ValidationError error in result.Errors.DeepAll())
//{
// Console.WriteLine(error.Message);
//}
}
}
}catch(Exception ex)
{
return RedirectToAction("RefundResponce", new { msg = ex.Message });
}
return RedirectToAction("RefundResponce");
}
//扣款結(jié)果顯示
public ActionResult RefundResponce(string msg)
{
ViewBag.Mesg = msg;
return View();
}
至此支付和退款功能完成。
其實(shí)還有很多需要解說(shuō)和注意的地方,還是自己去多多摸索的話學(xué)到的更多。雖然都是英文的,可以鍛煉英文的說(shuō)。
關(guān)于自定義支付控件樣式,即Customer UI的使用,下篇談,官方介紹,有demo,還可以自己編碼測(cè)試的網(wǎng)站