Compare commits

..

No commits in common. "b894a94133487a1686b2f4a35f76271c0cf9ebdf" and "31557c826c8bae36227e2380ceeb637567b4788d" have entirely different histories.

2 changed files with 131 additions and 361 deletions

View File

@ -15,7 +15,6 @@ using Nop.Services.Payments;
using Nop.Services.Plugins; using Nop.Services.Plugins;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions;
namespace Nop.Plugin.Payments.TapPay; namespace Nop.Plugin.Payments.TapPay;
@ -90,30 +89,10 @@ public class TapPayPaymentProcessor : BasePlugin, IPaymentMethod
var customer = await _customerService.GetCustomerByIdAsync(processPaymentRequest.CustomerId); var customer = await _customerService.GetCustomerByIdAsync(processPaymentRequest.CustomerId);
var customerBillingAddress = await _customerService.GetCustomerBillingAddressAsync(customer); var customerBillingAddress = await _customerService.GetCustomerBillingAddressAsync(customer);
string GetCustomValue(string key)
{
if (processPaymentRequest.CustomValues.TryGetValue(key, out var value) && value != null)
return value.ToString().Trim();
return string.Empty;
}
var prime = GetCustomValue("Prime");
if (string.IsNullOrEmpty(prime))
{
result.AddError("TapPay prime is missing.");
return result;
}
var cardholderEmail = GetCustomValue("CardholderEmail");
var cardholderPhone = GetCustomValue("CardholderPhoneNumber");
var cardholderPhoneCode = GetCustomValue("CardholderPhoneNumberCountryCode");
var cardholderNameEn = GetCustomValue("CardholderNameEn");
var requestBody = JsonSerializer.Serialize( var requestBody = JsonSerializer.Serialize(
new { new {
partner_key = _tapPayPaymentSettings.PartnerKey, partner_key = _tapPayPaymentSettings.PartnerKey,
prime = prime, prime = ((Newtonsoft.Json.Linq.JArray)processPaymentRequest.CustomValues["Prime"])[0].ToString(),
amount = (int)processPaymentRequest.OrderTotal, amount = (int)processPaymentRequest.OrderTotal,
merchant_id = _tapPayPaymentSettings.Merchant.ToString(), merchant_id = _tapPayPaymentSettings.Merchant.ToString(),
order_number = processPaymentRequest.OrderGuid.ToString(), order_number = processPaymentRequest.OrderGuid.ToString(),
@ -126,13 +105,11 @@ public class TapPayPaymentProcessor : BasePlugin, IPaymentMethod
}, },
details = "AI Hardware / APP", details = "AI Hardware / APP",
cardholder = new { cardholder = new {
phone_number = cardholderPhone, phone_number = customerBillingAddress.PhoneNumber,
phone_number_country_code = string.IsNullOrWhiteSpace(cardholderPhoneCode) ? "886" : cardholderPhoneCode, name = customerBillingAddress.LastName + customerBillingAddress.FirstName,
name_en = cardholderNameEn, email = customerBillingAddress.Email,
name = (customerBillingAddress?.LastName ?? string.Empty) + (customerBillingAddress?.FirstName ?? string.Empty), zip_code = customerBillingAddress.ZipPostalCode,
email = cardholderEmail, address = customerBillingAddress.City + customerBillingAddress.Address1 + customerBillingAddress.Address2,
zip_code = customerBillingAddress?.ZipPostalCode,
address = (customerBillingAddress?.City ?? string.Empty) + (customerBillingAddress?.Address1 ?? string.Empty) + (customerBillingAddress?.Address2 ?? string.Empty),
member_id = processPaymentRequest.CustomerId.ToString() member_id = processPaymentRequest.CustomerId.ToString()
} }
} }
@ -390,11 +367,7 @@ public class TapPayPaymentProcessor : BasePlugin, IPaymentMethod
public Task<ProcessPaymentRequest> GetPaymentInfoAsync(IFormCollection form) public Task<ProcessPaymentRequest> GetPaymentInfoAsync(IFormCollection form)
{ {
var paymentRequest = new ProcessPaymentRequest(); var paymentRequest = new ProcessPaymentRequest();
paymentRequest.CustomValues.Add("Prime", form["Prime"].ToString()); paymentRequest.CustomValues.Add("Prime", form["Prime"]);
paymentRequest.CustomValues.Add("CardholderEmail", form["CardholderEmail"].ToString());
paymentRequest.CustomValues.Add("CardholderPhoneNumber", form["CardholderPhoneNumber"].ToString());
paymentRequest.CustomValues.Add("CardholderPhoneNumberCountryCode", form["CardholderPhoneNumberCountryCode"].ToString());
paymentRequest.CustomValues.Add("CardholderNameEn", form["CardholderNameEn"].ToString());
return Task.FromResult(paymentRequest); return Task.FromResult(paymentRequest);
} }
@ -509,4 +482,4 @@ public class TapPayPaymentProcessor : BasePlugin, IPaymentMethod
public bool SkipPaymentInfo => false; public bool SkipPaymentInfo => false;
#endregion #endregion
} }

View File

@ -3,75 +3,20 @@
} }
@model Nop.Plugin.Payments.TapPay.Models.PaymentInfoModel @model Nop.Plugin.Payments.TapPay.Models.PaymentInfoModel
@* <!script async src="https://js.tappaysdk.com/tpdirect/v5.1.0"></!script> *@
<input type="hidden" asp-for="AppId" /> <input type="hidden" asp-for="AppId" />
<input type="hidden" asp-for="AppKey" /> <input type="hidden" asp-for="AppKey" />
<input type="hidden" asp-for="ServerType" /> <input type="hidden" asp-for="ServerType" />
<input type="hidden" id="Prime" name="Prime" /> <input type="hidden" id="Prime" name="Prime" />
<style> <table width="100%" cellspacing="2" cellpadding="1" border="0">
.tappay-table {
width: 100%;
border-collapse: separate;
border-spacing: 2px 10px;
}
.tappay-table label {
font-weight: 600;
color: #555;
}
.tappay-table .form-control,
.tappay-table .tappay-input {
height: 38px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 8px;
box-sizing: border-box;
transition: border-color .2s ease, color .2s ease, box-shadow .2s ease;
}
.tappay-table .tappay-input {
width: 100%;
}
.tappay-table .has-error .form-control,
.tappay-table .has-error .tappay-input {
border-color: #e74c3c;
color: #e74c3c;
box-shadow: 0 0 0 1px rgba(231, 76, 60, 0.15);
}
.tappay-table .has-success .form-control,
.tappay-table .has-success .tappay-input {
border-color: #2ecc71;
color: #2ecc71;
box-shadow: 0 0 0 1px rgba(46, 204, 113, 0.15);
}
.tappay-table .phone-wrapper {
display: flex;
gap: 6px;
align-items: center;
}
.tappay-table .country-code-input {
max-width: 70px;
text-align: center;
}
.tappay-table .country-prefix {
font-weight: 600;
margin-right: 4px;
}
</style>
<table class="tappay-table" width="100%" cellspacing="2" cellpadding="1" border="0">
<tr class="card-number-group"> <tr class="card-number-group">
<td> <td>
<label><span id="cardtype"></span>@T("Payment.CardNumber"):</label> <label><span id="cardtype"></span>@T("Payment.CardNumber"):</label>
</td> </td>
<td> <td>
<div class="form-control card-number"></div> <div class="form-control card-number" style="height: 20px"></div>
</td> </td>
</tr> </tr>
<tr class="expiration-date-group"> <tr class="expiration-date-group">
@ -79,7 +24,7 @@
<label>@T("Payment.ExpirationDate"):</label> <label>@T("Payment.ExpirationDate"):</label>
</td> </td>
<td> <td>
<div class="form-control expiration-date"></div> <div class="form-control expiration-date" style="height: 20px"></div>
</td> </td>
</tr> </tr>
<tr class="ccv-group"> <tr class="ccv-group">
@ -87,288 +32,140 @@
<label>@T("Payment.CardCode"):</label> <label>@T("Payment.CardCode"):</label>
</td> </td>
<td> <td>
<div class="form-control ccv"></div> <div class="form-control ccv" style="height: 20px"></div>
</td>
</tr>
<tr class="name-en-group">
<td>
<label>@T("Payment.CardHolderName"):</label>
</td>
<td>
<input class="tappay-input" type="text" id="CardholderNameEn" name="CardholderNameEn" maxlength="45" placeholder="@T("Payment.CardHolderName")"/>
</td>
</tr>
<tr class="email-group">
<td>
<label>@T("Payment.CardHolderEmail"):</label>
</td>
<td>
<input class="tappay-input" type="text" id="CardholderEmail" name="CardholderEmail" maxlength="40" placeholder="@T("Payment.CardHolderEmail")"/>
</td>
</tr>
<tr id="phone_group">
<td>
<label>@T("Payment.CardHolderPhone"):</label>
</td>
<td>
<div class="phone-wrapper">
<div class="phone-code-group">
<span class="country-prefix">+</span>
<input class="tappay-input country-code-input" type="tel" id="CardholderPhoneNumberCountryCode" name="CardholderPhoneNumberCountryCode" minlength="1" maxlength="4" placeholder="886" value="886"/>
</div>
<div class="phone-number-group">
<input class="tappay-input phone-number-input" type="tel" id="CardholderPhoneNumber" name="CardholderPhoneNumber" maxlength="16" placeholder="912345678"/>
</div>
</div>
</td> </td>
</tr> </tr>
</table> </table>
<script asp-location="Footer"> <script asp-location="Footer">
var tappayCustomFieldState = { //$(document).ready(function() {
nameEn: { valid: false, dirty: false }, $.getScript("https://js.tappaysdk.com/tpdirect/v5.1.0", function() {
email: { valid: false, dirty: false }, TPDirect.setupSDK($('#AppId').val(), $('#AppKey').val(), $('#ServerType').val());
phoneCode: { valid: false, dirty: false }, $('.button-1.payment-info-next-step-button').attr('disabled', true);
phoneNumber: { valid: false, dirty: false } $('.button-1.payment-info-next-step-button').removeAttr('onclick');
}; $('.button-1.payment-info-next-step-button').off('click');
$('.button-1.payment-info-next-step-button').click(function() {
event.preventDefault();
// fix keyboard issue in iOS device
forceBlurIos();
const tappayStatus = TPDirect.card.getTappayFieldsStatus();
console.log(tappayStatus);
var tappayCanGetPrime = false; // Check TPDirect.card.getTappayFieldsStatus().canGetPrime before TPDirect.card.getPrime
if (tappayStatus.canGetPrime === false) {
function setGroupState(selector, isValid, isDirty) { alert('can not get prime');
var group = $(selector); return;
group.removeClass('has-error has-success'); }
if (isDirty) {
group.addClass(isValid ? 'has-success' : 'has-error'); TPDirect.card.getPrime(function (result) {
} if (result.status !== 0) {
} alert('get prime error ' + result.msg);
function validateNameEn(markDirty) {
var value = $('#CardholderNameEn').val().trim();
tappayCustomFieldState.nameEn.dirty = tappayCustomFieldState.nameEn.dirty || markDirty || value.length > 0;
tappayCustomFieldState.nameEn.valid = /^[A-Za-z]+(?:[\s'-][A-Za-z]+)*$/.test(value) && value.length > 1;
setGroupState('.name-en-group', tappayCustomFieldState.nameEn.valid, tappayCustomFieldState.nameEn.dirty);
return tappayCustomFieldState.nameEn.valid;
}
function validateEmail(markDirty) {
var value = $('#CardholderEmail').val().trim();
tappayCustomFieldState.email.dirty = tappayCustomFieldState.email.dirty || markDirty || value.length > 0;
tappayCustomFieldState.email.valid = /^[^\s@@]+@@[^\s@@]+\.[^\s@@]+$/.test(value);
setGroupState('.email-group', tappayCustomFieldState.email.valid, tappayCustomFieldState.email.dirty);
return tappayCustomFieldState.email.valid;
}
function validatePhoneCode(markDirty) {
var value = $('#CardholderPhoneNumberCountryCode').val().trim();
tappayCustomFieldState.phoneCode.dirty = tappayCustomFieldState.phoneCode.dirty || markDirty || value.length > 0;
tappayCustomFieldState.phoneCode.valid = /^\d{1,4}$/.test(value);
setGroupState('#phone_group .phone-code-group', tappayCustomFieldState.phoneCode.valid, tappayCustomFieldState.phoneCode.dirty);
return tappayCustomFieldState.phoneCode.valid;
}
function validatePhoneNumber(markDirty) {
var value = $('#CardholderPhoneNumber').val().trim();
tappayCustomFieldState.phoneNumber.dirty = tappayCustomFieldState.phoneNumber.dirty || markDirty || value.length > 0;
tappayCustomFieldState.phoneNumber.valid = /^\d{4,16}$/.test(value);
setGroupState('#phone_group .phone-number-group', tappayCustomFieldState.phoneNumber.valid, tappayCustomFieldState.phoneNumber.dirty);
return tappayCustomFieldState.phoneNumber.valid;
}
function areCustomFieldsValid(markDirty) {
var nameValid = validateNameEn(markDirty);
var emailValid = validateEmail(markDirty);
var phoneCodeValid = validatePhoneCode(markDirty);
var phoneNumberValid = validatePhoneNumber(markDirty);
return nameValid && emailValid && phoneCodeValid && phoneNumberValid;
}
function updateSubmitButton(canGetPrime) {
if (typeof canGetPrime === 'boolean') {
tappayCanGetPrime = canGetPrime;
}
var canSubmit = tappayCanGetPrime && areCustomFieldsValid(false);
$('.button-1.payment-info-next-step-button').prop('disabled', !canSubmit);
}
$(function () {
$('#CardholderNameEn').on('input blur', function () {
validateNameEn(true);
updateSubmitButton();
});
$('#CardholderEmail').on('input blur', function () {
validateEmail(true);
updateSubmitButton();
});
$('#CardholderPhoneNumberCountryCode').on('input blur', function () {
validatePhoneCode(true);
updateSubmitButton();
});
$('#CardholderPhoneNumber').on('input blur', function () {
validatePhoneNumber(true);
updateSubmitButton();
});
// initialize default validation state
validatePhoneCode(false);
updateSubmitButton(false);
});
$.ajax({
url: "https://js.tappaysdk.com/sdk/tpdirect/v5.24.0",
dataType: "script",
cache: true,
success: function() {
TPDirect.setupSDK($('#AppId').val(), $('#AppKey').val(), $('#ServerType').val());
$('.button-1.payment-info-next-step-button').prop('disabled', true);
$('.button-1.payment-info-next-step-button').removeAttr('onclick');
$('.button-1.payment-info-next-step-button').off('click');
$('.button-1.payment-info-next-step-button').click(function(event) {
event.preventDefault();
// fix keyboard issue in iOS device
forceBlurIos();
const tappayStatus = TPDirect.card.getTappayFieldsStatus();
console.log(tappayStatus);
tappayCanGetPrime = tappayStatus.canGetPrime;
if (!areCustomFieldsValid(true)) {
updateSubmitButton(tappayCanGetPrime);
alert('請完整填寫持卡人資訊');
return; return;
} }
$('#Prime').val(result.card.prime);
// Check TPDirect.card.getTappayFieldsStatus().canGetPrime before TPDirect.card.getPrime if(typeof PaymentInfo !== 'undefined')
if (tappayStatus.canGetPrime === false) { {
alert('can not get prime'); PaymentInfo.save();
return; }
else
{
$("form[action='/checkout/paymentinfo'").submit();
} }
TPDirect.card.getPrime(function (result) {
if (result.status !== 0) {
alert('get prime error ' + result.msg);
return;
}
$('#Prime').val(result.card.prime);
if(typeof PaymentInfo !== 'undefined')
{
PaymentInfo.save();
}
else
{
$("form[action='/checkout/paymentinfo'").submit();
}
});
}); });
});
TPDirect.card.setup({ TPDirect.card.setup({
fields: { fields: {
number: { number: {
element: '.form-control.card-number', element: '.form-control.card-number',
placeholder: '**** **** **** ****' placeholder: '**** **** **** ****'
},
expirationDate: {
element: '.form-control.expiration-date',
placeholder: 'MM / YY'
},
ccv: {
element: '.form-control.ccv',
placeholder: '後三碼'
}
}, },
@* cardholder: { expirationDate: {
name_en: { element: '.form-control.expiration-date',
element: document.getElementById('name_en'), placeholder: 'MM / YY'
placeholder: '持卡人姓名'
},
email: {
element: document.getElementById('email'),
placeholder: 'Email'
},
phone: {
country_code: {
element: document.getElementById('phone_country_code'),
placeholder: '886'
},
number: {
element: document.getElementById('phone_number'),
placeholder: '912345678'
}
}
}, *@
styles: {
'input': {
'color': 'gray'
},
'input.ccv': {
// 'font-size': '16px'
},
':focus': {
'color': 'black'
},
'.valid': {
'color': 'green'
},
'.invalid': {
'color': 'red'
}
}, },
// 此設定會顯示卡號輸入正確後,會顯示前六後四碼信用卡卡號 ccv: {
isMaskCreditCardNumber: true, element: '.form-control.ccv',
maskCreditCardNumberRange: { placeholder: '後三碼'
beginIndex: 6,
endIndex: 11
} }
}); },
styles: {
// listen for TapPay Field 'input': {
TPDirect.card.onUpdate(function (update) { 'color': 'gray'
/* Disable / enable submit button depend on update.canGetPrime */ },
/* ============================================================ */ 'input.ccv': {
// 'font-size': '16px'
// update.canGetPrime === true },
// --> you can call TPDirect.card.getPrime() ':focus': {
// const submitButton = document.querySelector('button[type="submit"]') 'color': 'black'
updateSubmitButton(update.canGetPrime); },
'.valid': {
/* Change card type display when card type change */ 'color': 'green'
/* ============================================== */ },
'.invalid': {
// cardTypes = ['visa', 'mastercard', ...] 'color': 'red'
var newType = update.cardType === 'unknown' ? '' : update.cardType.toUpperCase();
$('#cardtype').text(newType);
/* Change form-group style when tappay field status change */
/* ======================================================= */
// number 欄位是錯誤的
if (update.status.number === 2) {
setNumberFormGroupToError('.card-number-group');
} else if (update.status.number === 0) {
setNumberFormGroupToSuccess('.card-number-group');
} else {
setNumberFormGroupToNormal('.card-number-group');
} }
},
// 此設定會顯示卡號輸入正確後,會顯示前六後四碼信用卡卡號
isMaskCreditCardNumber: true,
maskCreditCardNumberRange: {
beginIndex: 6,
endIndex: 11
}
});
if (update.status.expiry === 2) { // listen for TapPay Field
setNumberFormGroupToError('.expiration-date-group'); TPDirect.card.onUpdate(function (update) {
} else if (update.status.expiry === 0) { /* Disable / enable submit button depend on update.canGetPrime */
setNumberFormGroupToSuccess('.expiration-date-group'); /* ============================================================ */
} else {
setNumberFormGroupToNormal('.expiration-date-group');
}
if (update.status.ccv === 2) { // update.canGetPrime === true
setNumberFormGroupToError('.ccv-group'); // --> you can call TPDirect.card.getPrime()
} else if (update.status.ccv === 0) { // const submitButton = document.querySelector('button[type="submit"]')
setNumberFormGroupToSuccess('.ccv-group'); if (update.canGetPrime) {
} else { // submitButton.removeAttribute('disabled')
setNumberFormGroupToNormal('.ccv-group'); $('.button-1.payment-info-next-step-button').removeAttr('disabled');
} } else {
}); // submitButton.setAttribute('disabled', true)
} $('.button-1.payment-info-next-step-button').attr('disabled', true);
}
/* Change card type display when card type change */
/* ============================================== */
// cardTypes = ['visa', 'mastercard', ...]
var newType = update.cardType === 'unknown' ? '' : update.cardType.toUpperCase();
$('#cardtype').text(newType);
/* Change form-group style when tappay field status change */
/* ======================================================= */
// number 欄位是錯誤的
if (update.status.number === 2) {
setNumberFormGroupToError('.card-number-group');
} else if (update.status.number === 0) {
setNumberFormGroupToSuccess('.card-number-group');
} else {
setNumberFormGroupToNormal('.card-number-group');
}
if (update.status.expiry === 2) {
setNumberFormGroupToError('.expiration-date-group');
} else if (update.status.expiry === 0) {
setNumberFormGroupToSuccess('.expiration-date-group');
} else {
setNumberFormGroupToNormal('.expiration-date-group');
}
if (update.status.ccv === 2) {
setNumberFormGroupToError('.ccv-group');
} else if (update.status.ccv === 0) {
setNumberFormGroupToSuccess('.ccv-group');
} else {
setNumberFormGroupToNormal('.ccv-group');
}
});
}); });
function setNumberFormGroupToError(selector) { function setNumberFormGroupToError(selector) {