-
Notifications
You must be signed in to change notification settings - Fork 20
08. 实现支付服务(0.0.5)
我们使用ServiceComb Java Chassis构建第二个微服务——支付服务,并实现缴纳定金功能:
模块名 | 描述 |
---|---|
common | 在这个模块中增加支付服务的接口和DTO |
payment-service | 支付微服务 |
在common模块中,我们定义PaymentService接口:
public interface PaymentService {
//缴纳定金
ResponseEntity<Boolean> deposit(PaymentDTO payment);
}
为了能够未来更好的扩展请求和响应的内容,我们使用DTO类包装参数。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.servicecomb.scaffold</groupId>
<artifactId>common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.apache.servicecomb</groupId>
<artifactId>spring-boot-starter-provider</artifactId>
</dependency>
模拟与银行成功完成一笔支付后需要将支付的信息保存下来(可以理解为刷卡后的签名单),定义存储Payment信息的实体PaymentEntity:
@Entity
@Table(name = "T_Payment")
public class PaymentEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String transactionId;
private String userName;
private String bankName;
private String cardNumber;
private double amount;
private Date time;
private String type;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getBankName() {
return bankName;
}
public void setBankName(String bankName) {
this.bankName = bankName;
}
public String getCardNumber() {
return cardNumber;
}
public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public Date getTime() {
return time;
}
public void setTime(Date time) {
this.time = time;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public PaymentEntity() {
}
public PaymentEntity(String transactionId, String userName, String bankName, String cardNumber,
double amount, Date time, String type) {
this.transactionId = transactionId;
this.userName = userName;
this.bankName = bankName;
this.cardNumber = cardNumber;
this.amount = amount;
this.time = time;
this.type = type;
}
}
在CodeFist模式下,会在数据库中自动创建T_Payment表与此实体映射,id通过@GeneratedValue(strategy = GenerationType.AUTO)标记为自增长字段,userName存储支付用户,bankName是付款银行,cardNumber是付款银行卡号,amount是支付金额,time保存支付时间,type是交易的类型。
public class PaymentType {
//支付定金
public static final String DEPOSIT = "deposit";
//交易
public static final String TRADE = "trade";
}
然后我们继承JPA的PagingAndSortingRepository来实现ORM操作:
@Repository
@EnableTransactionManagement
public interface PaymentRepository extends PagingAndSortingRepository<PaymentEntity, Long> {
List<PaymentEntity> findByUserName(String userName);
PaymentEntity findByTransactionId(String TransactionId);
}
用户支付完定金后,需要保存已支付的定金总额,定义存储Deposit信息的实体DepositEntity:
@Entity
@Table(name = "T_Deposit")
public class DepositEntity {
@Id
private String userName;
private double amount;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public DepositEntity() {
}
public DepositEntity(String userName, double amount) {
this.userName = userName;
this.amount = amount;
}
}
在CodeFist模式下,会在数据库中自动创建T_Deposit表与此实体映射,userName是用户名,amount保存了已经支付的定金总额。
然后我们继承JPA的PagingAndSortingRepository来实现ORM操作:
@Repository
@EnableTransactionManagement
public interface DepositRepository extends PagingAndSortingRepository<DepositEntity, String> {
DepositEntity findByUserName(String userName);
}
@Override
@PostMapping(path = "deposit")
@Transactional(isolation = Isolation.SERIALIZABLE)
public ResponseEntity<Boolean> deposit(@RequestBody PaymentDTO payment) {
if (validatePayment(payment)) {
if (checkBalance(payment)) {
if (recordPayment(payment, PaymentType.DEPOSIT)) {
if (recordDeposit(payment)) {
if (cutWithBank(payment)) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
throw new InvocationException(BAD_REQUEST, "cut with bank failed");
}
throw new InvocationException(BAD_REQUEST, "record deposit failed");
}
throw new InvocationException(BAD_REQUEST, "record payment failed");
}
throw new InvocationException(BAD_REQUEST, "check balance failed");
}
throw new InvocationException(BAD_REQUEST, "incorrect payment request");
}
private boolean validatePayment(PaymentDTO payment) {
if (StringUtils.isNotEmpty(payment.getUserName()) && payment.getAmount() > 0 && StringUtils
.isNotEmpty(payment.getTransactionId())
&& StringUtils.isNotEmpty(payment.getBankName()) && StringUtils.isNotEmpty(payment.getCardNumber())) {
//TransactionId需要不重复,未被使用过
PaymentEntity pay = paymentRepository.findByTransactionId(payment.getTransactionId());
return pay == null;
}
return false;
}
//检查用户的余额,这里我们假设每一个用户都有一百万授信
private boolean checkBalance(PaymentDTO payment) {
//我们先要查一下系统里面已经用了多少
List<PaymentEntity> pays = paymentRepository.findByUserName(payment.getUserName());
double used = 0;
for (PaymentEntity pay : pays) {
used += pay.getAmount();
}
//预估一下账户余额够不够
return payment.getAmount() <= (CREDIT_LIMIT - used);
}
//本地记账保留扣款凭据
private boolean recordPayment(PaymentDTO payment, String paymentType) {
paymentRepository
.save(new PaymentEntity(payment.getTransactionId(), payment.getUserName(), payment.getBankName(),
payment.getCardNumber(), payment.getAmount(), new Date(), paymentType));
return true;
}
//登记缴纳的定金
private boolean recordDeposit(PaymentDTO payment) {
DepositEntity deposit = depositRepository.findByUserName(payment.getUserName());
if (deposit == null) {
deposit = new DepositEntity(payment.getUserName(), payment.getAmount());
} else {
deposit.setAmount(deposit.getAmount() + payment.getAmount());
}
depositRepository.save(deposit);
return true;
}
//走银行接口请求银行划账,Demo不对接直接返回为true
private boolean cutWithBank(PaymentDTO payment) {
return true;
}
可以看到,支付定金会先后调用五个函数:
- validatePayment:首先我们需要检查一下调用入参是否正确;
- checkBalance:然后我们检查一下这个用户是否有足够的余额。模拟的逻辑比较简单,我们假设每一个用户都有一百万授信,扣掉在本系统中支付(包含支付定金)历史的总款项,如果大于需要支付的款项,那么我们认为有足够的余额;
- recordPayment:本地记录扣款凭据,注意:deposit方法标记了@Transactional,因此事务在成功返回前不会提交;
- recordDeposit:记录支付的定金,注意:deposit方法标记了@Transactional,因此事务在成功返回前不会提交;
- cutWithBank:请求银行划款(扣款),由于我们不走银行系统,直接返回true,代表扣款成功。如果将这一步的返回结果设置为false,则recordPayment会回滚不会写入数据库。
数据库的连接配置在application.properties中,包含了目标数据库,数据库用户名,数据库密码和JPA配置。
ServiceComb Java Chassis微服务的所有配置都在microservice.yaml中,主要包括ServiceCenter(服务注册&发现)地址,Rest Provider发布地址以及治理策略等。
- Spring Boot中的JPA示例:
https://spring.io/guides/gs/accessing-data-jpa/
- 在Spring Boot中使用ServiceComb:
http://servicecomb.incubator.apache.org/cn/users/use-servicecomb-in-spring-boot/
- ServiceComb Java Chassis微服务配置:
http://servicecomb.incubator.apache.org/cn/users/service-definition/
http://servicecomb.incubator.apache.org/cn/users/service-configurations/
http://servicecomb.incubator.apache.org/cn/users/communicate-protocol/