def request_refund(self, payment): with transaction.atomic(): payment = Payment.objects.select_for_update().get( serial_number=payment.serial_number) if payment.status == PAYMENT_STATUS['FROZEN']: new_refund = Refund.objects.create( serial_number=generate_serial_number(), status=REFUND_STATUS['REQUESTED'], payment=payment) elif payment.status == PAYMENT_STATUS['REFUND_FAILED']: new_refund = Refund.objects.select_for_update().get( payment=payment) new_refund.status = REFUND_STATUS['REQUESTED'] new_refund.save() else: raise InvalidStatusError() payment.status = PAYMENT_STATUS['REFUND_REQUESTED'] payment.save() paid_price = payment.order_price - payment.coupon.discount if payment.coupon else payment.order_price try: result = self.api_instance.refund( payment_serial_number=payment.serial_number, refund_serial_number=new_refund.serial_number, refund_amount=paid_price) self.on_refund_success(new_refund) return result except (ApiRequestError, ApiReturnedError) as e: self.on_refund_fail(new_refund) raise e
def place_order(self, client, merchant, coupon, order_price, notify_url): """ Place a pre-payment order. :param client: client instance, required :param merchant: merchant instance, required :param coupon: coupon instance, optional :param order_price: :param notify_url: :return: :raises: db.DatabaseError, ApiRequestError, ApiReturnedError """ paid_price = order_price - coupon.discount if coupon else order_price with transaction.atomic(): # update coupon status if coupon: updated = Coupon.objects.filter( id=coupon.id, status=COUPON_STATUS.NOT_USED).update( status=COUPON_STATUS.USED, use_datetime=timezone.now()) if not updated: raise InvalidStatusError() # Create the payment record new_payment = Payment.objects.create( serial_number=generate_serial_number(), pay_channel=PAY_CHANNELS['ALIPAY'], status=PAYMENT_STATUS['UNPAID'], merchant=merchant, client=client, coupon=coupon, order_price=order_price, platform_share=round(max(0, paid_price) * PLATFORM_SHARE_RATE) if coupon else 0, inviter_share=round(max(0, paid_price) * INVITER_SHARE_RATE) if coupon else 0, originator_share=round( max(0, paid_price) * ORIGINATOR_SHARE_RATE) if coupon else 0, ) try: result = self.api_instance.place_order( notify_url=notify_url, order_title=merchant.name, payment_serial_number=new_payment.serial_number, total_amount=paid_price, buyer_id=client.openid) except (ApiRequestError, ApiReturnedError): # if error occurs, send a query request to check the order. sleep(1) result = self.api_instance.query_payment( new_payment.serial_number) return result
def place_order(self, client, merchant, coupon, order_price, client_ip, notify_url): """ Place an pre-payment order. :param client: client instance, required :param merchant: merchant instance, required :param coupon: coupon instance, optional :param order_price: :param client_ip: :param notify_url: :return: :raises: db.DatabaseError, ApiRequestError, ApiReturnedError """ paid_price = order_price - coupon.discount if coupon else order_price with transaction.atomic(): # update coupon status if coupon: updated = Coupon.objects.filter( id=coupon.id, status=COUPON_STATUS.NOT_USED).update( status=COUPON_STATUS.USED, use_datetime=timezone.now()) if not updated: raise InvalidStatusError() # Create the payment record new_payment = Payment.objects.create( serial_number=generate_serial_number(), pay_channel=PAY_CHANNELS['WECHAT'], status=PAYMENT_STATUS['UNPAID'], merchant=merchant, client=client, coupon=coupon, order_price=order_price, platform_share=round(max(0, paid_price) * PLATFORM_SHARE_RATE) if coupon else 0, inviter_share=round(max(0, paid_price) * INVITER_SHARE_RATE) if coupon else 0, originator_share=round( max(0, paid_price) * ORIGINATOR_SHARE_RATE) if coupon else 0, ) result = self.api_instance_without_cert.place_order( order_title=merchant.name, payment_serial_number=new_payment.serial_number, total_fee=order_price - coupon.discount if coupon else order_price, spbill_create_ip=client_ip, notify_url=notify_url, openid=client.openid) return dict(client_payment_params=self.api_instance_without_cert. generate_client_payment_params(result['prepay_id']), payment_serial_number=new_payment.serial_number)
def on_refund_fail(cls, refund): with transaction.atomic(): payment = Payment.objects.select_for_update().get( serial_number=refund.payment.serial_number) refund = Refund.objects.select_for_update().get( serial_number=refund.serial_number) if refund.status != REFUND_STATUS['REQUESTED'] \ or payment.status != PAYMENT_STATUS['REFUND_REQUESTED']: raise InvalidStatusError() refund.status = REFUND_STATUS['FAILED'] refund.save() payment.status = PAYMENT_STATUS['REFUND_FAILED'] payment.save()
def cancel_order(self, payment): """ Cancel the order :param payment: :return: InvalidStatusError, ApiRequestError, ApiReturnedError """ with transaction.atomic(): coupon = None if payment.coupon_id: coupon = Coupon.objects.select_for_update().get( id=payment.coupon_id) payment = Payment.objects.select_for_update().get( serial_number=payment.serial_number) if payment.status != PAYMENT_STATUS.UNPAID: raise InvalidStatusError() payment.status = PAYMENT_STATUS['CANCELLED'] payment.save() self.api_instance.cancel(payment.serial_number) if coupon: # 有使用优惠券的情况,设置原优惠券为销毁状态,并退给用户一张新的优惠券 coupon.status = COUPON_STATUS.DESTROYED coupon.save() new_coupon = Coupon.objects.create( rule_id=coupon.rule_id, client_id=coupon.client_id, discount=coupon.discount, min_charge=coupon.min_charge, originator_merchant_id=coupon.originator_merchant_id, status=COUPON_STATUS.NOT_USED, obtain_datetime=coupon.obtain_datetime, ) return new_coupon else: return None
def on_payment_unfreeze(cls, payment): """ unfreeze payment. :param payment: :return: :raise: InvalidStatusError """ with PayChannelContext(payment.pay_channel): with transaction.atomic(): timestamp = timezone.now() accounts = None merchant_account = None if payment.coupon: # 使用了优惠券的情况 merchant_account_id = payment.merchant.account_id originator_account_id = payment.coupon.originator_merchant.account_id inviter_account_id = payment.merchant.inviter.account_id accounts = AccountProxy.objects.select_for_update().filter( id__in=(PLATFORM_ACCOUNT_ID, merchant_account_id, originator_account_id, inviter_account_id)) else: merchant_account_id = payment.merchant.account_id merchant_account = AccountProxy.objects.select_for_update( ).get(id=merchant_account_id) payment = Payment.objects.select_for_update().select_related( 'coupon').get(serial_number=payment.serial_number) if payment.status != PAYMENT_STATUS.FROZEN: raise InvalidStatusError() if payment.coupon: # 使用了优惠券的情况 accounts = {a.id: a for a in accounts} if len(accounts) != 4: logger.error('Cannot find all the accounts:{}'.format( repr((PLATFORM_ACCOUNT_ID, merchant_account_id, originator_account_id, inviter_account_id)))) raise AccountProxy.DoesNotExist() platform_account = accounts[PLATFORM_ACCOUNT_ID] merchant_account = accounts[merchant_account_id] originator_account = accounts[originator_account_id] inviter_account = accounts[inviter_account_id] platform_account.channel_balance -= payment.originator_share + payment.inviter_share platform_account.save() originator_account.channel_balance += payment.originator_share originator_account.channel_withdrawable_balance += payment.originator_share originator_account.save() inviter_account.channel_balance += payment.inviter_share inviter_account.channel_withdrawable_balance += payment.inviter_share inviter_account.save() payment.status = PAYMENT_STATUS.FINISHED payment.save() merchant_account.channel_withdrawable_balance += payment.order_price - payment.coupon.discount - sum( (payment.platform_share, payment.originator_share, payment.inviter_share)) merchant_account.save() # 记录Transaction Transaction.objects.bulk_create([ Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_EXPEND_MERCHANT_SHARE'], datetime=timestamp, account=platform_account, amount=-payment.originator_share, balance_after_transaction=platform_account. channel_balance + payment.inviter_share), Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_EXPEND_MARKETER_SHARE'], datetime=timestamp, account=platform_account, amount=-payment.inviter_share, balance_after_transaction=platform_account. channel_balance), Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_EXPEND_PLATFORM_SHARE'], datetime=timestamp, account=platform_account, amount=-payment.platform_share, balance_after_transaction=platform_account. channel_balance - payment.platform_share), Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_SHARE'], datetime=timestamp, account=platform_account, amount=payment.platform_share, balance_after_transaction=platform_account. channel_balance), Transaction( content_object=payment, transaction_type=TRANSACTION_TYPE[ 'MERCHANT_SHARE'], datetime=timestamp, account=originator_account, amount=payment.originator_share, balance_after_transaction=originator_account. channel_balance), Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'MARKETER_SHARE'], datetime=timestamp, account=inviter_account, amount=payment.inviter_share, balance_after_transaction=inviter_account. channel_balance) ]) else: # 没有使用优惠券的情况 payment.status = PAYMENT_STATUS.FINISHED payment.save() merchant_account.channel_withdrawable_balance += payment.order_price merchant_account.save()
def on_payment_success(cls, payment): """ :param payment: :return: """ with PayChannelContext(payment.pay_channel): with transaction.atomic(): timestamp = timezone.now() platform_account = AccountProxy.objects.select_for_update( ).get(id=PLATFORM_ACCOUNT_ID) merchant_account = AccountProxy.objects.select_for_update( ).get(id=payment.merchant.account.id) payment = Payment.objects.select_for_update().get( serial_number=payment.serial_number) if payment.status != PAYMENT_STATUS['UNPAID']: raise InvalidStatusError() # Duplicated callback payment.status = PAYMENT_STATUS['FROZEN'] payment.save() if not payment.coupon: # 没有使用优惠券的情况 merchant_account.channel_balance = merchant_account.channel_balance + payment.order_price merchant_account.save() Transaction.objects.bulk_create([ Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_RECEIVE'], datetime=timestamp, account=platform_account, amount=payment.order_price, balance_after_transaction=platform_account. channel_balance + payment.order_price), Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_EXPEND_MERCHANT_RECEIVE'], datetime=timestamp, account=platform_account, amount=-payment.order_price, balance_after_transaction=platform_account. channel_balance), Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'MERCHANT_RECEIVE'], datetime=timestamp, account=merchant_account, amount=payment.order_price, balance_after_transaction=merchant_account. channel_balance) ]) else: # 使用了优惠券的情况 paid_price = payment.order_price - payment.coupon.discount total_share = payment.platform_share + payment.inviter_share + payment.originator_share platform_account.channel_balance = platform_account.channel_balance + total_share platform_account.save() merchant_account.channel_balance = merchant_account.channel_balance + paid_price - total_share merchant_account.save() Transaction.objects.bulk_create([ Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_RECEIVE'], datetime=timestamp, account=platform_account, amount=paid_price, balance_after_transaction=platform_account. channel_balance + paid_price - total_share), Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_EXPEND_MERCHANT_RECEIVE'], datetime=timestamp, account=platform_account, amount=-(paid_price - total_share), balance_after_transaction=platform_account. channel_balance), Transaction(content_object=payment, transaction_type=TRANSACTION_TYPE[ 'MERCHANT_RECEIVE'], datetime=timestamp, account=merchant_account, amount=paid_price - total_share, balance_after_transaction=merchant_account. channel_balance) ])
def on_refund_success(cls, refund): with transaction.atomic(): timestamp = timezone.now() platform_account = AccountProxy.objects.select_for_update().get( id=PLATFORM_ACCOUNT_ID) merchant_account = AccountProxy.objects.select_for_update().get( id=refund.payment.merchant.account.id) coupon = refund.payment.coupon if coupon: coupon = Coupon.objects.select_for_update().get(id=coupon.id) payment = Payment.objects.select_for_update().get( serial_number=refund.payment.serial_number) refund = Refund.objects.select_for_update().get( serial_number=refund.serial_number) with PayChannelContext(payment.pay_channel): if refund.status != REFUND_STATUS['REQUESTED'] \ or payment.status != PAYMENT_STATUS['REFUND_REQUESTED']: raise InvalidStatusError() refund.status = REFUND_STATUS['FINISHED'] refund.save() payment.status = PAYMENT_STATUS['REFUND'] payment.save() if not coupon: # 没有使用优惠券 merchant_account.channel_balance = merchant_account.channel_balance - payment.order_price merchant_account.save() Transaction.objects.bulk_create([ Transaction(content_object=refund, transaction_type=TRANSACTION_TYPE[ 'MERCHANT_REFUND'], datetime=timestamp, account=merchant_account, amount=-payment.order_price, balance_after_transaction=merchant_account. channel_balance), Transaction(content_object=refund, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_EARNING_MERCHANT_REFUND'], datetime=timestamp, account=platform_account, amount=payment.order_price, balance_after_transaction=platform_account. channel_balance + payment.order_price), Transaction(content_object=refund, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_REFUND'], datetime=timestamp, account=platform_account, amount=-payment.order_price, balance_after_transaction=platform_account. channel_balance) ]) else: # 有使用优惠券的情况 paid_price = payment.order_price - payment.coupon.discount total_share = payment.platform_share + payment.inviter_share + payment.originator_share merchant_account.channel_balance = merchant_account.channel_balance - ( paid_price - total_share) merchant_account.save() platform_account.channel_balance = platform_account.channel_balance - total_share platform_account.save() Transaction.objects.bulk_create([ Transaction(content_object=refund, transaction_type=TRANSACTION_TYPE[ 'MERCHANT_REFUND'], datetime=timestamp, account=merchant_account, amount=-(paid_price - total_share), balance_after_transaction=merchant_account. channel_balance), Transaction(content_object=refund, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_EARNING_MERCHANT_REFUND'], datetime=timestamp, account=platform_account, amount=(paid_price - total_share), balance_after_transaction=platform_account. channel_balance + paid_price), Transaction(content_object=refund, transaction_type=TRANSACTION_TYPE[ 'PLATFORM_REFUND'], datetime=timestamp, account=platform_account, amount=-paid_price, balance_after_transaction=platform_account. channel_balance) ]) # 设置原优惠券为销毁状态,并退给用户一张新的优惠券 coupon.status = COUPON_STATUS.DESTROYED coupon.save() new_coupon = Coupon.objects.create( rule_id=coupon.rule_id, client_id=coupon.client_id, discount=coupon.discount, min_charge=coupon.min_charge, originator_merchant_id=coupon.originator_merchant_id, status=COUPON_STATUS.NOT_USED, obtain_datetime=coupon.obtain_datetime, )